API Lifecycle Implementation, Pt. 1 (#211)

* allow owner api to start up without a wallet, begin to add lifecycle functions

* rustfmt

* test and typos fix

* updated with lifecycle functions

* rustfmt

* updates to allow owner api tests to be executed against local wallet proxy

* rustfmt

* fix for windows test

* add ability to pass configuration to , begin to add documentation and doctests

* add ability to pass configuration to , begin to add documentation and doctests

* doctests for lifecycle functions

* rustfmt

* ensure foreign API also has mask updated when being run along owner api, add more tests to lifecycle

* rustfmt

* documentation for lifecycle methods + init_secure_api

* rustfmt

* failing doctest
This commit is contained in:
Yeastplume 2019-09-02 16:03:35 +01:00 committed by GitHub
parent 22dfa9b3f5
commit 41c0058e84
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 1557 additions and 132 deletions

74
Cargo.lock generated
View file

@ -581,16 +581,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "grin_api"
version = "2.0.1-beta.1"
source = "git+https://github.com/mimblewimble/grin#ea023387bfa4f1493cc57f120205a09251468e60"
dependencies = [
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)",
"grin_chain 2.0.1-beta.1",
"grin_core 2.0.1-beta.1",
"grin_p2p 2.0.1-beta.1",
"grin_pool 2.0.1-beta.1",
"grin_store 2.0.1-beta.1",
"grin_util 2.0.1-beta.1",
"grin_chain 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)",
"grin_core 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)",
"grin_p2p 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)",
"grin_pool 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)",
"grin_store 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)",
"grin_util 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)",
"http 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper 0.12.19 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper-rustls 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -612,6 +613,7 @@ dependencies = [
[[package]]
name = "grin_chain"
version = "2.0.1-beta.1"
source = "git+https://github.com/mimblewimble/grin#ea023387bfa4f1493cc57f120205a09251468e60"
dependencies = [
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
@ -619,10 +621,10 @@ dependencies = [
"croaring 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"grin_core 2.0.1-beta.1",
"grin_keychain 2.0.1-beta.1",
"grin_store 2.0.1-beta.1",
"grin_util 2.0.1-beta.1",
"grin_core 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)",
"grin_keychain 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)",
"grin_store 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)",
"grin_util 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"lru-cache 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
@ -634,6 +636,7 @@ dependencies = [
[[package]]
name = "grin_core"
version = "2.0.1-beta.1"
source = "git+https://github.com/mimblewimble/grin#ea023387bfa4f1493cc57f120205a09251468e60"
dependencies = [
"blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
@ -642,8 +645,8 @@ dependencies = [
"enum_primitive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"grin_keychain 2.0.1-beta.1",
"grin_util 2.0.1-beta.1",
"grin_keychain 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)",
"grin_util 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"lru-cache 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
@ -660,11 +663,12 @@ dependencies = [
[[package]]
name = "grin_keychain"
version = "2.0.1-beta.1"
source = "git+https://github.com/mimblewimble/grin#ea023387bfa4f1493cc57f120205a09251468e60"
dependencies = [
"blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
"grin_util 2.0.1-beta.1",
"grin_util 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)",
"hmac 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
@ -682,15 +686,16 @@ dependencies = [
[[package]]
name = "grin_p2p"
version = "2.0.1-beta.1"
source = "git+https://github.com/mimblewimble/grin#ea023387bfa4f1493cc57f120205a09251468e60"
dependencies = [
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
"enum_primitive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"grin_chain 2.0.1-beta.1",
"grin_core 2.0.1-beta.1",
"grin_store 2.0.1-beta.1",
"grin_util 2.0.1-beta.1",
"grin_chain 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)",
"grin_core 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)",
"grin_store 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)",
"grin_util 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
"num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
@ -703,15 +708,16 @@ dependencies = [
[[package]]
name = "grin_pool"
version = "2.0.1-beta.1"
source = "git+https://github.com/mimblewimble/grin#ea023387bfa4f1493cc57f120205a09251468e60"
dependencies = [
"blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"grin_core 2.0.1-beta.1",
"grin_keychain 2.0.1-beta.1",
"grin_store 2.0.1-beta.1",
"grin_util 2.0.1-beta.1",
"grin_core 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)",
"grin_keychain 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)",
"grin_store 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)",
"grin_util 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.98 (registry+https://github.com/rust-lang/crates.io-index)",
@ -736,14 +742,15 @@ dependencies = [
[[package]]
name = "grin_store"
version = "2.0.1-beta.1"
source = "git+https://github.com/mimblewimble/grin#ea023387bfa4f1493cc57f120205a09251468e60"
dependencies = [
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"croaring 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"grin_core 2.0.1-beta.1",
"grin_util 2.0.1-beta.1",
"grin_core 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)",
"grin_util 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)",
"libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)",
"lmdb-zero 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
@ -756,6 +763,7 @@ dependencies = [
[[package]]
name = "grin_util"
version = "2.0.1-beta.1"
source = "git+https://github.com/mimblewimble/grin#ea023387bfa4f1493cc57f120205a09251468e60"
dependencies = [
"backtrace 0.3.34 (registry+https://github.com/rust-lang/crates.io-index)",
"base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
@ -918,12 +926,12 @@ name = "grin_wallet_util"
version = "2.1.0-beta.1"
dependencies = [
"dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"grin_api 2.0.1-beta.1",
"grin_chain 2.0.1-beta.1",
"grin_core 2.0.1-beta.1",
"grin_keychain 2.0.1-beta.1",
"grin_store 2.0.1-beta.1",
"grin_util 2.0.1-beta.1",
"grin_api 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)",
"grin_chain 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)",
"grin_core 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)",
"grin_keychain 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)",
"grin_store 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)",
"grin_util 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)",
"pretty_assertions 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.98 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2878,7 +2886,15 @@ dependencies = [
"checksum getrandom 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "cd8e190892c840661957ba9f32dacfb3eb405e657f9f9f60485605f0bb37d6f8"
"checksum git2 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8cb400360e8a4d61b10e648285bbfa919bbf9519d0d5d5720354456f44349226"
"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
"checksum grin_api 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)" = "<none>"
"checksum grin_chain 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)" = "<none>"
"checksum grin_core 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)" = "<none>"
"checksum grin_keychain 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)" = "<none>"
"checksum grin_p2p 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)" = "<none>"
"checksum grin_pool 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)" = "<none>"
"checksum grin_secp256k1zkp 0.7.7 (registry+https://github.com/rust-lang/crates.io-index)" = "23027a7673df2c2b20fb9589d742ff400a10a9c3e4c769a77e9fa3bd19586822"
"checksum grin_store 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)" = "<none>"
"checksum grin_util 2.0.1-beta.1 (git+https://github.com/mimblewimble/grin)" = "<none>"
"checksum h2 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)" = "a5b34c246847f938a410a03c5458c7fee2274436675e76d8b903c08efc29c462"
"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
"checksum hmac 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "733e1b3ac906631ca01ebb577e9bb0f5e37a454032b9036b5eaea4013ed6f99a"

View file

@ -144,7 +144,7 @@ where
///
/// // The top level wallet directory should be set manually (in the reference implementation,
/// // this is provided in the WalletConfig)
/// lc.set_wallet_directory(&wallet_config.data_file_dir);
/// let _ = lc.set_top_level_directory(&wallet_config.data_file_dir);
///
/// // Wallet must be opened with the password (TBD)
/// let pw = ZeroingString::from("wallet_password");
@ -506,7 +506,7 @@ macro_rules! doctest_helper_setup_doc_env_foreign {
>,
>;
let lc = wallet.lc_provider().unwrap();
lc.set_wallet_directory(&wallet_config.data_file_dir);
let _ = lc.set_top_level_directory(&wallet_config.data_file_dir);
lc.open_wallet(None, pw, false, false);
let mut $wallet = Arc::new(Mutex::new(wallet));
};

View file

@ -624,8 +624,8 @@ pub fn run_doctest_foreign(
>,
>;
let lc = wallet1.lc_provider().unwrap();
lc.set_wallet_directory(&format!("{}/wallet1", test_dir));
lc.create_wallet(None, Some(rec_phrase_1), 32, empty_string.clone())
let _ = lc.set_top_level_directory(&format!("{}/wallet1", test_dir));
lc.create_wallet(None, Some(rec_phrase_1), 32, empty_string.clone(), false)
.unwrap();
let mask1 = lc
.open_wallet(None, empty_string.clone(), use_token, true)
@ -659,8 +659,8 @@ pub fn run_doctest_foreign(
>,
>;
let lc = wallet2.lc_provider().unwrap();
lc.set_wallet_directory(&format!("{}/wallet2", test_dir));
lc.create_wallet(None, Some(rec_phrase_2), 32, empty_string.clone())
let _ = lc.set_top_level_directory(&format!("{}/wallet2", test_dir));
lc.create_wallet(None, Some(rec_phrase_2), 32, empty_string.clone(), false)
.unwrap();
let mask2 = lc
.open_wallet(None, empty_string.clone(), use_token, true)

View file

@ -22,6 +22,7 @@
#![deny(unused_mut)]
#![warn(missing_docs)]
use grin_wallet_config as config;
use grin_wallet_util::grin_core as core;
use grin_wallet_util::grin_keychain as keychain;
use grin_wallet_util::grin_util as util;

View file

@ -17,7 +17,9 @@
use chrono::prelude::*;
use uuid::Uuid;
use crate::config::WalletConfig;
use crate::core::core::Transaction;
use crate::core::global;
use crate::impls::create_sender;
use crate::keychain::{Identifier, Keychain};
use crate::libwallet::api_impl::owner;
@ -27,7 +29,7 @@ use crate::libwallet::{
WalletLCProvider,
};
use crate::util::secp::key::SecretKey;
use crate::util::Mutex;
use crate::util::{from_hex, static_secp_instance, LoggingConfig, Mutex, ZeroingString};
use std::sync::Arc;
/// Main interface into all wallet API functions.
@ -125,7 +127,7 @@ where
///
/// // The top level wallet directory should be set manually (in the reference implementation,
/// // this is provided in the WalletConfig)
/// lc.set_wallet_directory(&wallet_config.data_file_dir);
/// let _ = lc.set_top_level_directory(&wallet_config.data_file_dir);
///
/// // Wallet must be opened with the password (TBD)
/// let pw = ZeroingString::from("wallet_password");
@ -1231,6 +1233,338 @@ where
let _ = w.keychain(keychain_mask)?;
owner::node_height(&mut **w, keychain_mask)
}
// LIFECYCLE FUNCTIONS
/// Retrieve the top-level directory for the wallet. This directory should contain the
/// `grin-wallet.toml` file and the `wallet_data` directory that contains the wallet
/// seed + data files. Future versions of the wallet API will support multiple wallets
/// within the top level directory.
///
/// The top level directory defaults to (in order of precedence):
///
/// 1) The current directory, from which `grin-wallet` or the main process was run, if it
/// contains a `grin-wallet.toml` file.
/// 2) ~/.grin/<chaintype>/ otherwise
///
/// # Arguments
///
/// * None
///
/// # Returns
/// * Ok with a String value representing the full path to the top level wallet dierctory
/// * or [`libwallet::Error`](../grin_wallet_libwallet/struct.Error.html) if an error is encountered.
///
/// # Example
/// Set up as in [`new`](struct.Owner.html#method.new) method above.
/// ```
/// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config);
///
/// let api_owner = Owner::new(wallet.clone());
/// let result = api_owner.get_top_level_directory();
///
/// if let Ok(dir) = result {
/// println!("Top level directory is: {}", dir);
/// //...
/// }
/// ```
pub fn get_top_level_directory(&self) -> Result<String, Error> {
let mut w_lock = self.wallet_inst.lock();
let lc = w_lock.lc_provider()?;
if self.doctest_mode {
Ok("/doctest/dir".to_owned())
} else {
lc.get_top_level_directory()
}
}
/// Set the top-level directory for the wallet. This directory can be empty, and will be created
/// during a subsequent calls to [`create_config`](struct.Owner.html#method.create_config)
///
/// Set [`get_top_level_directory`](struct.Owner.html#method.get_top_level_directory) for a
/// description of the top level directory and default paths.
///
/// # Arguments
///
/// * `dir`: The new top-level directory path (either relative to current directory or
/// absolute.
///
/// # Returns
/// * Ok if successful
/// * or [`libwallet::Error`](../grin_wallet_libwallet/struct.Error.html) if an error is encountered.
///
/// # Example
/// Set up as in [`new`](struct.Owner.html#method.new) method above.
/// ```
/// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config);
///
/// let dir = "path/to/wallet/dir";
///
/// # let dir = tempdir().map_err(|e| format!("{:#?}", e)).unwrap();
/// # let dir = dir
/// # .path()
/// # .to_str()
/// # .ok_or("Failed to convert tmpdir path to string.".to_owned())
/// # .unwrap();
///
/// let api_owner = Owner::new(wallet.clone());
/// let result = api_owner.set_top_level_directory(dir);
///
/// if let Ok(dir) = result {
/// //...
/// }
/// ```
pub fn set_top_level_directory(&self, dir: &str) -> Result<(), Error> {
let mut w_lock = self.wallet_inst.lock();
let lc = w_lock.lc_provider()?;
lc.set_top_level_directory(dir)
}
/// Create a `grin-wallet.toml` configuration file in the top-level directory for the
/// specified chain type.
/// A custom [`WalletConfig`](../grin_wallet_config/types/struct.WalletConfig.html)
/// and/or grin `LoggingConfig` may optionally be provided, otherwise defaults will be used.
///
/// Paths in the configuration file will be updated to reflect the top level directory, so
/// path-related values in the optional configuration structs will be ignored.
///
/// # Arguments
///
/// * `chain_type`: The chain type to use in creation of the configuration file. This can be
/// * `AutomatedTesting`
/// * `UserTesting`
/// * `Floonet`
/// * `Mainnet`
///
/// # Returns
/// * Ok if successful
/// * or [`libwallet::Error`](../grin_wallet_libwallet/struct.Error.html) if an error is encountered.
///
/// # Example
/// Set up as in [`new`](struct.Owner.html#method.new) method above.
/// ```
/// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config);
///
/// use grin_core::global::ChainTypes;
///
/// let dir = "path/to/wallet/dir";
///
/// # let dir = tempdir().map_err(|e| format!("{:#?}", e)).unwrap();
/// # let dir = dir
/// # .path()
/// # .to_str()
/// # .ok_or("Failed to convert tmpdir path to string.".to_owned())
/// # .unwrap();
///
/// let api_owner = Owner::new(wallet.clone());
/// let _ = api_owner.set_top_level_directory(dir);
///
/// let result = api_owner.create_config(&ChainTypes::Mainnet, None, None);
///
/// if let Ok(_) = result {
/// //...
/// }
/// ```
pub fn create_config(
&self,
chain_type: &global::ChainTypes,
wallet_config: Option<WalletConfig>,
logging_config: Option<LoggingConfig>,
) -> Result<(), Error> {
let mut w_lock = self.wallet_inst.lock();
let lc = w_lock.lc_provider()?;
lc.create_config(
chain_type,
"grin-wallet.toml",
wallet_config,
logging_config,
)
}
/// Creates a new wallet seed and empty wallet database in the `wallet_data` directory of
/// the top level directory.
///
/// Paths in the configuration file will be updated to reflect the top level directory, so
/// path-related values in the optional configuration structs will be ignored.
///
/// The wallet files must not already exist, and ~The `grin-wallet.toml` file must exist
/// in the top level directory (can be created via a call to
/// [`create_config`](struct.Owner.html#method.create_config))
///
/// # Arguments
///
/// * `name`: Reserved for future use, use `None` for the time being.
/// * `mnemonic`: If present, restore the wallet seed from the given mnemonic instead of creating
/// a new random seed.
/// * `mnemonic_length`: Desired length of mnemonic in bytes (16 or 32, either 12 or 24 words).
/// Use 0 if mnemonic isn't being used.
/// * `password`: The password used to encrypt/decrypt the `wallet.seed` file
///
/// # Returns
/// * Ok if successful
/// * or [`libwallet::Error`](../grin_wallet_libwallet/struct.Error.html) if an error is encountered.
///
/// # Example
/// Set up as in [`new`](struct.Owner.html#method.new) method above.
/// ```
/// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config);
///
/// use grin_core::global::ChainTypes;
///
/// // note that the WalletInst struct does not necessarily need to contain an
/// // instantiated wallet
///
/// let dir = "path/to/wallet/dir";
///
/// # let dir = tempdir().map_err(|e| format!("{:#?}", e)).unwrap();
/// # let dir = dir
/// # .path()
/// # .to_str()
/// # .ok_or("Failed to convert tmpdir path to string.".to_owned())
/// # .unwrap();
/// let api_owner = Owner::new(wallet.clone());
/// let _ = api_owner.set_top_level_directory(dir);
///
/// // Create configuration
/// let result = api_owner.create_config(&ChainTypes::Mainnet, None, None);
///
/// // create new wallet wirh random seed
/// let pw = ZeroingString::from("my_password");
/// let result = api_owner.create_wallet(None, None, 0, pw);
///
/// if let Ok(r) = result {
/// //...
/// }
/// ```
pub fn create_wallet(
&self,
name: Option<&str>,
mnemonic: Option<ZeroingString>,
mnemonic_length: u32,
password: ZeroingString,
) -> Result<(), Error> {
let mut w_lock = self.wallet_inst.lock();
let lc = w_lock.lc_provider()?;
lc.create_wallet(
name,
mnemonic,
mnemonic_length as usize,
password,
self.doctest_mode,
)
}
/// `Opens` a wallet, populating the internal keychain with the encrypted seed, and optionally
/// returning a `keychain_mask` token to the caller to provide in all future calls.
/// If using a mask, the seed will be stored in-memory XORed against the `keychain_mask`, and
/// will not be useable if the mask is not provided.
///
/// # Arguments
///
/// * `name`: Reserved for future use, use `None` for the time being.
/// * `password`: The password to use to open the wallet
/// a new random seed.
/// * `use_mask`: Whether to create and return a mask which much be provided in all future
/// API calls.
///
/// # Returns
/// * Ok if successful
/// * or [`libwallet::Error`](../grin_wallet_libwallet/struct.Error.html) if an error is encountered.
///
/// # Example
/// Set up as in [`new`](struct.Owner.html#method.new) method above.
/// ```
/// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config);
///
/// use grin_core::global::ChainTypes;
///
/// // note that the WalletInst struct does not necessarily need to contain an
/// // instantiated wallet
/// let dir = "path/to/wallet/dir";
///
/// # let dir = tempdir().map_err(|e| format!("{:#?}", e)).unwrap();
/// # let dir = dir
/// # .path()
/// # .to_str()
/// # .ok_or("Failed to convert tmpdir path to string.".to_owned())
/// # .unwrap();
/// let api_owner = Owner::new(wallet.clone());
/// let _ = api_owner.set_top_level_directory(dir);
///
/// // Create configuration
/// let result = api_owner.create_config(&ChainTypes::Mainnet, None, None);
///
/// // create new wallet wirh random seed
/// let pw = ZeroingString::from("my_password");
/// let _ = api_owner.create_wallet(None, None, 0, pw.clone());
///
/// let result = api_owner.open_wallet(None, pw, true);
///
/// if let Ok(m) = result {
/// // use this mask in all subsequent calls
/// let mask = m;
/// }
/// ```
pub fn open_wallet(
&self,
name: Option<&str>,
password: ZeroingString,
use_mask: bool,
) -> Result<Option<SecretKey>, Error> {
// just return a representative string for doctest mode
if self.doctest_mode {
let secp_inst = static_secp_instance();
let secp = secp_inst.lock();
return Ok(Some(SecretKey::from_slice(
&secp,
&from_hex(
"d096b3cb75986b3b13f80b8f5243a9edf0af4c74ac37578c5a12cfb5b59b1868".to_owned(),
)
.unwrap(),
)?));
}
let mut w_lock = self.wallet_inst.lock();
let lc = w_lock.lc_provider()?;
lc.open_wallet(name, password, use_mask, self.doctest_mode)
}
/// `Close` a wallet, removing the master seed from memory.
///
/// # Arguments
///
/// * `name`: Reserved for future use, use `None` for the time being.
///
/// # Returns
/// * Ok if successful
/// * or [`libwallet::Error`](../grin_wallet_libwallet/struct.Error.html) if an error is encountered.
///
/// # Example
/// Set up as in [`new`](struct.Owner.html#method.new) method above.
/// ```
/// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config);
///
/// use grin_core::global::ChainTypes;
///
/// // Set up as above
/// # let api_owner = Owner::new(wallet.clone());
///
/// let res = api_owner.close_wallet(None);
///
/// if let Ok(_) = res {
/// // ...
/// }
/// ```
pub fn close_wallet(&self, name: Option<&str>) -> Result<(), Error> {
let mut w_lock = self.wallet_inst.lock();
let lc = w_lock.lc_provider()?;
lc.close_wallet(name)
}
}
#[doc(hidden)]
@ -1241,6 +1575,7 @@ macro_rules! doctest_helper_setup_doc_env {
use grin_wallet_config as config;
use grin_wallet_impls as impls;
use grin_wallet_libwallet as libwallet;
use grin_wallet_util::grin_core;
use grin_wallet_util::grin_keychain as keychain;
use grin_wallet_util::grin_util as util;
@ -1278,7 +1613,7 @@ macro_rules! doctest_helper_setup_doc_env {
>,
>;
let lc = wallet.lc_provider().unwrap();
lc.set_wallet_directory(&wallet_config.data_file_dir);
let _ = lc.set_top_level_directory(&wallet_config.data_file_dir);
lc.open_wallet(None, pw, false, false);
let mut $wallet = Arc::new(Mutex::new(wallet));
};

View file

@ -1413,8 +1413,8 @@ pub fn run_doctest_owner(
>,
>;
let lc = wallet1.lc_provider().unwrap();
lc.set_wallet_directory(&format!("{}/wallet1", test_dir));
lc.create_wallet(None, Some(rec_phrase_1), 32, empty_string.clone())
let _ = lc.set_top_level_directory(&format!("{}/wallet1", test_dir));
lc.create_wallet(None, Some(rec_phrase_1), 32, empty_string.clone(), false)
.unwrap();
let mask1 = lc
.open_wallet(None, empty_string.clone(), use_token, true)
@ -1448,8 +1448,8 @@ pub fn run_doctest_owner(
>,
>;
let lc = wallet2.lc_provider().unwrap();
lc.set_wallet_directory(&format!("{}/wallet2", test_dir));
lc.create_wallet(None, Some(rec_phrase_2), 32, empty_string.clone())
let _ = lc.set_top_level_directory(&format!("{}/wallet2", test_dir));
lc.create_wallet(None, Some(rec_phrase_2), 32, empty_string.clone(), false)
.unwrap();
let mask2 = lc
.open_wallet(None, empty_string.clone(), use_token, true)

View file

@ -15,7 +15,9 @@
//! JSON-RPC Stub generation for the Owner API
use uuid::Uuid;
use crate::config::WalletConfig;
use crate::core::core::Transaction;
use crate::core::global;
use crate::keychain::{Identifier, Keychain};
use crate::libwallet::slate_versions::v2::TransactionV2;
use crate::libwallet::{
@ -24,13 +26,16 @@ use crate::libwallet::{
WalletLCProvider,
};
use crate::util::secp::key::{PublicKey, SecretKey};
use crate::util::static_secp_instance;
use crate::util::{static_secp_instance, LoggingConfig, ZeroingString};
use crate::{ECDHPubkey, Owner, Token};
use easy_jsonrpc_mw;
use rand::thread_rng;
/// Public definition used to generate Owner jsonrpc api.
/// Secure version, that should be used when running the owner API in 'Secure' Mode
/// Secure version containing wallet lifecycle functions. All calls to this API must be encrypted.
/// See [`init_secure_api`](#tymethod.init_secure_api) for details of secret derivation
/// and encryption.
#[easy_jsonrpc_mw::rpc]
pub trait OwnerRpcS {
/**
@ -1274,7 +1279,6 @@ pub trait OwnerRpcS {
/**
Networked version of [Owner::node_height](struct.Owner.html#method.node_height).
```
# grin_wallet_api::doctest_helper_json_rpc_owner_assert_response!(
# r#"
@ -1306,11 +1310,283 @@ pub trait OwnerRpcS {
fn node_height(&self, token: Token) -> Result<NodeHeightResult, ErrorKind>;
/**
Initializes the secure RPC-JSON API
(Documentation TBD)
Initializes the secure JSON-RPC API. This function must be called and a shared key
established before any other OwnerAPI JSON-RPC function can be called.
The shared key will be derived using ECDH with the provided public key on the secp256k1 curve. This
function will return its public key used in the derivation, which the caller should multiply by its
private key to derive the shared key.
Once the key is established, all further requests and responses are encrypted and decrypted with the
following parameters:
* AES-256 in GCM mode with 128-bit tags and 96 bit nonces
* 12 byte nonce which must be included in each request/response to use on the decrypting side
* Empty vector for additional data
* Suffix length = AES-256 GCM mode tag length = 16 bytes
*
Fully-formed JSON-RPC requests (as documented) should be encrypted using these parameters, encoded
into base64 and included with the one-time nonce in a request for the `encrypted_request_v3` method
as follows:
```
# let s = r#"
{
"jsonrpc": "2.0",
"method": "encrypted_request_v3",
"id": "1",
"params": {
"nonce": "ef32...",
"body_enc": "e0bcd..."
}
}
# "#;
```
With a typical response being:
```
# let s = r#"{
{
"jsonrpc": "2.0",
"method": "encrypted_response_v3",
"id": "1",
"Ok": {
"nonce": "340b...",
"body_enc": "3f09c..."
}
}
# }"#;
```
*/
fn init_secure_api(&self, ecdh_pubkey: ECDHPubkey) -> Result<ECDHPubkey, ErrorKind>;
/**
Networked version of [Owner::get_top_level_directory](struct.Owner.html#method.get_top_level_directory).
```
# grin_wallet_api::doctest_helper_json_rpc_owner_assert_response!(
# r#"
{
"jsonrpc": "2.0",
"method": "get_top_level_directory",
"params": {
},
"id": 1
}
# "#
# ,
# r#"
{
"id": 1,
"jsonrpc": "2.0",
"result": {
"Ok": "/doctest/dir"
}
}
# "#
# , true, 5, false, false, false);
```
*/
fn get_top_level_directory(&self) -> Result<String, ErrorKind>;
/**
Networked version of [Owner::set_top_level_directory](struct.Owner.html#method.set_top_level_directory).
```
# grin_wallet_api::doctest_helper_json_rpc_owner_assert_response!(
# r#"
{
"jsonrpc": "2.0",
"method": "set_top_level_directory",
"params": {
"dir": "/home/wallet_user/my_wallet_dir"
},
"id": 1
}
# "#
# ,
# r#"
{
"id": 1,
"jsonrpc": "2.0",
"result": {
"Ok": null
}
}
# "#
# , true, 5, false, false, false);
```
*/
fn set_top_level_directory(&self, dir: String) -> Result<(), ErrorKind>;
/**
Networked version of [Owner::create_config](struct.Owner.html#method.create_config).
Both the `wallet_config` and `logging_config` parameters can be `null`, the examples
below are for illustration. Note that the values provided for `log_file_path` and `data_file_dir`
will be ignored and replaced with the actual values based on the value of `get_top_level_directory`
```
# grin_wallet_api::doctest_helper_json_rpc_owner_assert_response!(
# r#"
{
"jsonrpc": "2.0",
"method": "create_config",
"params": {
"chain_type": "Mainnet",
"wallet_config": {
"chain_type": null,
"api_listen_interface": "127.0.0.1",
"api_listen_port": 3415,
"owner_api_listen_port": 3420,
"api_secret_path": null,
"node_api_secret_path": null,
"check_node_api_http_addr": "http://127.0.0.1:3413",
"owner_api_include_foreign": false,
"data_file_dir": "/path/to/data/file/dir",
"no_commit_cache": null,
"tls_certificate_file": null,
"tls_certificate_key": null,
"dark_background_color_scheme": null,
"keybase_notify_ttl": null
},
"logging_config": {
"log_to_stdout": false,
"stdout_log_level": "Info",
"log_to_file": true,
"file_log_level": "Debug",
"log_file_path": "/path/to/log/file",
"log_file_append": true,
"log_max_size": null,
"log_max_files": null,
"tui_running": null
}
},
"id": 1
}
# "#
# ,
# r#"
{
"id": 1,
"jsonrpc": "2.0",
"result": {
"Ok": null
}
}
# "#
# , true, 5, false, false, false);
```
*/
fn create_config(
&self,
chain_type: global::ChainTypes,
wallet_config: Option<WalletConfig>,
logging_config: Option<LoggingConfig>,
) -> Result<(), ErrorKind>;
/**
Networked version of [Owner::create_wallet](struct.Owner.html#method.create_wallet).
```
# grin_wallet_api::doctest_helper_json_rpc_owner_assert_response!(
# r#"
{
"jsonrpc": "2.0",
"method": "create_wallet",
"params": {
"name": null,
"mnemonic": null,
"mnemonic_length": 0,
"password": "my_secret_password"
},
"id": 1
}
# "#
# ,
# r#"
{
"id": 1,
"jsonrpc": "2.0",
"result": {
"Ok": null
}
}
# "#
# , true, 0, false, false, false);
```
*/
fn create_wallet(
&self,
name: Option<String>,
mnemonic: Option<String>,
mnemonic_length: u32,
password: String,
) -> Result<(), ErrorKind>;
/**
Networked version of [Owner::open_wallet](struct.Owner.html#method.open_wallet).
```
# grin_wallet_api::doctest_helper_json_rpc_owner_assert_response!(
# r#"
{
"jsonrpc": "2.0",
"method": "open_wallet",
"params": {
"name": null,
"password": "my_secret_password"
},
"id": 1
}
# "#
# ,
# r#"
{
"id": 1,
"jsonrpc": "2.0",
"result": {
"Ok": "d096b3cb75986b3b13f80b8f5243a9edf0af4c74ac37578c5a12cfb5b59b1868"
}
}
# "#
# , true, 0, false, false, false);
```
*/
fn open_wallet(&self, name: Option<String>, password: String) -> Result<Token, ErrorKind>;
/**
Networked version of [Owner::close_wallet](struct.Owner.html#method.close_wallet).
```
# grin_wallet_api::doctest_helper_json_rpc_owner_assert_response!(
# r#"
{
"jsonrpc": "2.0",
"method": "close_wallet",
"params": {
"name": null
},
"id": 1
}
# "#
# ,
# r#"
{
"id": 1,
"jsonrpc": "2.0",
"result": {
"Ok": null
}
}
# "#
# , true, 0, false, false, false);
```
*/
fn close_wallet(&self, name: Option<String>) -> Result<(), ErrorKind>;
}
impl<'a, L, C, K> OwnerRpcS for Owner<'a, L, C, K>
@ -1520,4 +1796,51 @@ where
ecdh_pubkey: pub_key,
})
}
fn get_top_level_directory(&self) -> Result<String, ErrorKind> {
Owner::get_top_level_directory(self).map_err(|e| e.kind())
}
fn set_top_level_directory(&self, dir: String) -> Result<(), ErrorKind> {
Owner::set_top_level_directory(self, &dir).map_err(|e| e.kind())
}
fn create_config(
&self,
chain_type: global::ChainTypes,
wallet_config: Option<WalletConfig>,
logging_config: Option<LoggingConfig>,
) -> Result<(), ErrorKind> {
Owner::create_config(self, &chain_type, wallet_config, logging_config).map_err(|e| e.kind())
}
fn create_wallet(
&self,
name: Option<String>,
mnemonic: Option<String>,
mnemonic_length: u32,
password: String,
) -> Result<(), ErrorKind> {
let n = name.as_ref().map(|s| s.as_str());
let m = match mnemonic {
Some(s) => Some(ZeroingString::from(s)),
None => None,
};
Owner::create_wallet(self, n, m, mnemonic_length, ZeroingString::from(password))
.map_err(|e| e.kind())
}
fn open_wallet(&self, name: Option<String>, password: String) -> Result<Token, ErrorKind> {
let n = name.as_ref().map(|s| s.as_str());
let sec_key = Owner::open_wallet(self, n, ZeroingString::from(password), true)
.map_err(|e| e.kind())?;
Ok(Token {
keychain_mask: sec_key,
})
}
fn close_wallet(&self, name: Option<String>) -> Result<(), ErrorKind> {
let n = name.as_ref().map(|s| s.as_str());
Owner::close_wallet(self, n).map_err(|e| e.kind())
}
}

View file

@ -31,4 +31,4 @@ pub mod config;
pub mod types;
pub use crate::config::{initial_setup_wallet, GRIN_WALLET_DIR, WALLET_CONFIG_FILE_NAME};
pub use crate::types::{ConfigError, GlobalWalletConfig, WalletConfig};
pub use crate::types::{ConfigError, GlobalWalletConfig, GlobalWalletConfigMembers, WalletConfig};

View file

@ -74,12 +74,13 @@ where
{
let mut w_lock = wallet.lock();
let p = w_lock.lc_provider()?;
p.create_config(&g_args.chain_type, WALLET_CONFIG_FILE_NAME)?;
p.create_config(&g_args.chain_type, WALLET_CONFIG_FILE_NAME, None, None)?;
p.create_wallet(
None,
args.recovery_phrase,
args.list_length,
args.password.clone(),
false,
)?;
let m = p.get_mnemonic(None, args.password)?;
@ -121,7 +122,7 @@ pub struct ListenArgs {
pub fn listen<'a, L, C, K>(
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
keychain_mask: Option<SecretKey>,
keychain_mask: Arc<Mutex<Option<SecretKey>>>,
config: &WalletConfig,
args: &ListenArgs,
g_args: &GlobalArgs,
@ -170,9 +171,12 @@ where
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
// keychain mask needs to be a sinlge instance, in case the foreign API is
// also being run at the same time
let km = Arc::new(Mutex::new(keychain_mask));
let res = controller::owner_listener(
wallet,
keychain_mask,
km,
config.owner_api_listen_addr().as_str(),
g_args.node_api_secret.clone(),
g_args.tls_conf.clone(),

View file

@ -21,7 +21,7 @@ use crate::libwallet::{
CURRENT_SLATE_VERSION, GRIN_BLOCK_HEADER_VERSION,
};
use crate::util::secp::key::SecretKey;
use crate::util::{to_base64, Mutex};
use crate::util::{from_hex, static_secp_instance, to_base64, Mutex};
use failure::ResultExt;
use futures::future::{err, ok};
use futures::{Future, Stream};
@ -29,6 +29,7 @@ use hyper::header::HeaderValue;
use hyper::{Body, Request, Response, StatusCode};
use serde::{Deserialize, Serialize};
use serde_json;
use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::Arc;
@ -44,10 +45,6 @@ lazy_static! {
HeaderValue::from_str("Basic realm=GrinOwnerAPI").unwrap();
}
lazy_static! {
pub static ref OWNER_API_SHARED_KEY: Arc<Mutex<Option<SecretKey>>> = Arc::new(Mutex::new(None));
}
fn check_middleware(
name: ForeignCheckMiddlewareFn,
node_version_info: Option<NodeVersionInfo>,
@ -122,7 +119,7 @@ where
/// in the same wallet instance
pub fn owner_listener<L, C, K>(
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K> + 'static>>>,
keychain_mask: Option<SecretKey>,
keychain_mask: Arc<Mutex<Option<SecretKey>>>,
addr: &str,
api_secret: Option<String>,
tls_config: Option<TLSConfig>,
@ -143,9 +140,14 @@ where
));
router.add_middleware(basic_auth_middleware);
}
let mut running_foreign = false;
if owner_api_include_foreign.unwrap_or(false) {
running_foreign = true;
}
let api_handler_v2 = OwnerAPIHandlerV2::new(wallet.clone());
let api_handler_v3 = OwnerAPIHandlerV3::new(wallet.clone());
let api_handler_v3 =
OwnerAPIHandlerV3::new(wallet.clone(), keychain_mask.clone(), running_foreign);
router
.add_route("/v2/owner", Arc::new(api_handler_v2))
@ -156,7 +158,7 @@ where
.map_err(|_| ErrorKind::GenericError("Router failed to add route".to_string()))?;
// If so configured, add the foreign API to the same port
if owner_api_include_foreign.unwrap_or(false) {
if running_foreign {
warn!("Starting HTTP Foreign API on Owner server at {}.", addr);
let foreign_api_handler_v2 = ForeignAPIHandlerV2::new(wallet, keychain_mask);
router
@ -182,7 +184,7 @@ where
/// port and wrapping the calls
pub fn foreign_listener<L, C, K>(
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K> + 'static>>>,
keychain_mask: Option<SecretKey>,
keychain_mask: Arc<Mutex<Option<SecretKey>>>,
addr: &str,
tls_config: Option<TLSConfig>,
) -> Result<(), Error>
@ -299,6 +301,16 @@ where
{
/// Wallet instance
pub wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K> + 'static>>>,
/// ECDH shared key
pub shared_key: Arc<Mutex<Option<SecretKey>>>,
/// Keychain mask (to change if also running the foreign API)
pub keychain_mask: Arc<Mutex<Option<SecretKey>>>,
/// Whether we're running the foreign API on the same port, and therefore
/// have to store the mask in-process
pub running_foreign: bool,
}
pub struct OwnerV3Helpers;
@ -316,6 +328,18 @@ impl OwnerV3Helpers {
}
}
/// Checks whether a request is to open the wallet
pub fn is_open_wallet(val: &serde_json::Value) -> bool {
if let Some(m) = val["method"].as_str() {
match m {
"open_wallet" => true,
_ => false,
}
} else {
false
}
}
/// Checks whether a request is an encrypted request
pub fn is_encrypted_request(val: &serde_json::Value) -> bool {
if let Some(m) = val["method"].as_str() {
@ -329,15 +353,17 @@ impl OwnerV3Helpers {
}
/// whether encryption is enabled
pub fn encryption_enabled() -> bool {
let share_key_ref = OWNER_API_SHARED_KEY.lock();
pub fn encryption_enabled(key: Arc<Mutex<Option<SecretKey>>>) -> bool {
let share_key_ref = key.lock();
share_key_ref.is_some()
}
/// If incoming is an encrypted request, check there is a shared key,
/// Otherwise return an error value
pub fn check_encryption_started() -> Result<(), serde_json::Value> {
match OwnerV3Helpers::encryption_enabled() {
pub fn check_encryption_started(
key: Arc<Mutex<Option<SecretKey>>>,
) -> Result<(), serde_json::Value> {
match OwnerV3Helpers::encryption_enabled(key) {
true => Ok(()),
false => Err(EncryptionErrorResponse::new(
1,
@ -349,18 +375,42 @@ impl OwnerV3Helpers {
}
/// Update the statically held owner API shared key
pub fn update_owner_api_shared_key(val: &serde_json::Value, new_key: Option<SecretKey>) {
pub fn update_owner_api_shared_key(
key: Arc<Mutex<Option<SecretKey>>>,
val: &serde_json::Value,
new_key: Option<SecretKey>,
) {
if let Some(_) = val["result"]["Ok"].as_str() {
let mut share_key_ref = OWNER_API_SHARED_KEY.lock();
let mut share_key_ref = key.lock();
*share_key_ref = new_key;
}
}
/// Update the shared mask, in case of foreign API being run
pub fn update_mask(mask: Arc<Mutex<Option<SecretKey>>>, val: &serde_json::Value) {
if let Some(key) = val["result"]["Ok"].as_str() {
let key_bytes = match from_hex(key.to_owned()) {
Ok(k) => k,
Err(_) => return,
};
let secp_inst = static_secp_instance();
let secp = secp_inst.lock();
let sk = match SecretKey::from_slice(&secp, &key_bytes) {
Ok(s) => s,
Err(_) => return,
};
let mut shared_mask_ref = mask.lock();
*shared_mask_ref = Some(sk);
}
}
/// Decrypt an encrypted request
pub fn decrypt_request(
key: Arc<Mutex<Option<SecretKey>>>,
req: &serde_json::Value,
) -> Result<(u32, serde_json::Value), serde_json::Value> {
let share_key_ref = OWNER_API_SHARED_KEY.lock();
let share_key_ref = key.lock();
let shared_key = share_key_ref.as_ref().unwrap();
let enc_req: EncryptedRequest = serde_json::from_value(req.clone()).map_err(|e| {
EncryptionErrorResponse::new(
@ -380,10 +430,11 @@ impl OwnerV3Helpers {
/// Encrypt a response
pub fn encrypt_response(
key: Arc<Mutex<Option<SecretKey>>>,
id: u32,
res: &serde_json::Value,
) -> Result<serde_json::Value, serde_json::Value> {
let share_key_ref = OWNER_API_SHARED_KEY.lock();
let share_key_ref = key.lock();
let shared_key = share_key_ref.as_ref().unwrap();
let enc_res = EncryptedResponse::from_json(id, res, &shared_key).map_err(|e| {
EncryptionErrorResponse::new(1, -32003, &format!("EncryptionError: {}", e.kind()))
@ -399,6 +450,74 @@ impl OwnerV3Helpers {
})?;
Ok(res)
}
/// convert an internal error (if exists) as proper JSON-RPC
pub fn check_error_response(val: &serde_json::Value) -> (bool, serde_json::Value) {
// check for string first. This ensures that error messages
// that are just strings aren't given weird formatting
let err_string = if val["result"]["Err"].is_object() {
let mut retval;
let hashed: Result<HashMap<String, String>, serde_json::Error> =
serde_json::from_value(val["result"]["Err"].clone());
retval = match hashed {
Err(e) => {
debug!("Can't cast value to Hashmap<String> {}", e);
None
}
Ok(h) => {
let mut r = "".to_owned();
for (k, v) in h.iter() {
r = format!("{}: {}", k, v);
}
Some(r)
}
};
// Otherwise, see if error message is a map that needs
// to be stringified (and accept weird formatting)
if retval.is_none() {
let hashed: Result<HashMap<String, serde_json::Value>, serde_json::Error> =
serde_json::from_value(val["result"]["Err"].clone());
retval = match hashed {
Err(e) => {
debug!("Can't cast value to Hashmap<Value> {}", e);
None
}
Ok(h) => {
let mut r = "".to_owned();
for (k, v) in h.iter() {
r = format!("{}: {}", k, v);
}
Some(r)
}
}
}
retval
} else if val["result"]["Err"].is_string() {
let parsed = serde_json::from_value::<String>(val["result"]["Err"].clone());
match parsed {
Ok(p) => Some(p),
Err(_) => None,
}
} else {
None
};
match err_string {
Some(s) => {
return (
true,
serde_json::json!({
"jsonrpc": "2.0",
"id": val["id"],
"error": {
"message": s,
"code": -32099
}
}),
)
}
None => (false, val.clone()),
}
}
}
impl<L, C, K> OwnerAPIHandlerV3<L, C, K>
@ -410,8 +529,15 @@ where
/// Create a new owner API handler for GET methods
pub fn new(
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K> + 'static>>>,
keychain_mask: Arc<Mutex<Option<SecretKey>>>,
running_foreign: bool,
) -> OwnerAPIHandlerV3<L, C, K> {
OwnerAPIHandlerV3 { wallet }
OwnerAPIHandlerV3 {
wallet,
shared_key: Arc::new(Mutex::new(None)),
keychain_mask: keychain_mask,
running_foreign,
}
}
fn call_api(
@ -419,6 +545,9 @@ where
req: Request<Body>,
api: Owner<'static, L, C, K>,
) -> Box<dyn Future<Item = serde_json::Value, Error = Error> + Send> {
let key = self.shared_key.clone();
let mask = self.keychain_mask.clone();
let running_foreign = self.running_foreign;
Box::new(parse_body(req).and_then(move |val: serde_json::Value| {
let mut val = val;
let owner_api_s = &api as &dyn OwnerRpcS;
@ -426,10 +555,10 @@ where
let mut was_encrypted = false;
let mut encrypted_req_id = 0;
if !is_init_secure_api {
if let Err(v) = OwnerV3Helpers::check_encryption_started() {
if let Err(v) = OwnerV3Helpers::check_encryption_started(key.clone()) {
return ok(v);
}
let res = OwnerV3Helpers::decrypt_request(&val);
let res = OwnerV3Helpers::decrypt_request(key.clone(), &val);
match res {
Err(e) => return ok(e),
Ok(v) => {
@ -441,11 +570,21 @@ where
}
// check again, in case it was an encrypted call to init_secure_api
is_init_secure_api = OwnerV3Helpers::is_init_secure_api(&val);
// also need to intercept open/close wallet requests
let is_open_wallet = OwnerV3Helpers::is_open_wallet(&val);
match owner_api_s.handle_request(val) {
MaybeReply::Reply(mut r) => {
let unencrypted_intercept = r.clone();
let (_was_error, unencrypted_intercept) =
OwnerV3Helpers::check_error_response(&r.clone());
if is_open_wallet && running_foreign {
OwnerV3Helpers::update_mask(mask, &r.clone());
}
if was_encrypted {
let res = OwnerV3Helpers::encrypt_response(encrypted_req_id, &r);
let res = OwnerV3Helpers::encrypt_response(
key.clone(),
encrypted_req_id,
&unencrypted_intercept,
);
r = match res {
Ok(v) => v,
Err(v) => return ok(v),
@ -455,6 +594,7 @@ where
// in case it was an encrypted call to 'init_api_secure')
if is_init_secure_api {
OwnerV3Helpers::update_owner_api_shared_key(
key.clone(),
&unencrypted_intercept,
api.shared_key.lock().clone(),
);
@ -510,7 +650,7 @@ where
/// Wallet instance
pub wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K> + 'static>>>,
/// Keychain mask
pub keychain_mask: Option<SecretKey>,
pub keychain_mask: Arc<Mutex<Option<SecretKey>>>,
}
impl<L, C, K> ForeignAPIHandlerV2<L, C, K>
@ -522,7 +662,7 @@ where
/// Create a new foreign API handler for GET methods
pub fn new(
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K> + 'static>>>,
keychain_mask: Option<SecretKey>,
keychain_mask: Arc<Mutex<Option<SecretKey>>>,
) -> ForeignAPIHandlerV2<L, C, K> {
ForeignAPIHandlerV2 {
wallet,
@ -549,11 +689,8 @@ where
}
fn handle_post_request(&self, req: Request<Body>) -> WalletResponseFuture {
let api = Foreign::new(
self.wallet.clone(),
self.keychain_mask.clone(),
Some(check_middleware),
);
let mask = self.keychain_mask.lock();
let api = Foreign::new(self.wallet.clone(), mask.clone(), Some(check_middleware));
Box::new(
self.call_api(req, api)
.and_then(|resp| ok(json_response_pretty(&resp))),

View file

@ -121,8 +121,8 @@ pub fn create_local_wallet(
>,
>;
let lc = wallet.lc_provider().unwrap();
lc.set_wallet_directory(&format!("{}/{}", test_dir, name));
lc.create_wallet(None, mnemonic, 32, ZeroingString::from(""))
let _ = lc.set_top_level_directory(&format!("{}/{}", test_dir, name));
lc.create_wallet(None, mnemonic, 32, ZeroingString::from(""), false)
.unwrap();
let mask = lc
.open_wallet(None, ZeroingString::from(""), create_mask, false)
@ -160,7 +160,7 @@ pub fn open_local_wallet(
>,
>;
let lc = wallet.lc_provider().unwrap();
lc.set_wallet_directory(&format!("{}/{}", test_dir, name));
let _ = lc.set_top_level_directory(&format!("{}/{}", test_dir, name));
let mask = lc
.open_wallet(None, ZeroingString::from(""), create_mask, false)
.unwrap();

View file

@ -367,7 +367,7 @@ impl SlateReceiver for KeybaseAllChannels {
>,
>;
let lc = wallet.lc_provider().unwrap();
lc.set_wallet_directory(&config.data_file_dir);
lc.set_top_level_directory(&config.data_file_dir)?;
let mask = lc.open_wallet(None, passphrase, true, false)?;
let wallet_inst = lc.wallet_inst()?;
wallet_inst.set_parent_key_id_by_name(account)?;

View file

@ -14,7 +14,9 @@
//! Default wallet lifecycle provider
use crate::config::{config, GlobalWalletConfig, GRIN_WALLET_DIR};
use crate::config::{
config, GlobalWalletConfig, GlobalWalletConfigMembers, WalletConfig, GRIN_WALLET_DIR,
};
use crate::core::global;
use crate::keychain::Keychain;
use crate::libwallet::{Error, ErrorKind, NodeClient, WalletBackend, WalletLCProvider};
@ -23,6 +25,8 @@ use crate::util::secp::key::SecretKey;
use crate::util::ZeroingString;
use crate::LMDBBackend;
use failure::ResultExt;
use grin_wallet_util::grin_util::LoggingConfig;
use std::fs;
use std::path::PathBuf;
pub struct DefaultLCProvider<'a, C, K>
@ -55,15 +59,51 @@ where
C: NodeClient + 'a,
K: Keychain + 'a,
{
fn set_wallet_directory(&mut self, dir: &str) {
fn set_top_level_directory(&mut self, dir: &str) -> Result<(), Error> {
self.data_dir = dir.to_owned();
Ok(())
}
fn create_config(&self, chain_type: &global::ChainTypes, file_name: &str) -> Result<(), Error> {
fn get_top_level_directory(&self) -> Result<String, Error> {
Ok(self.data_dir.to_owned())
}
fn create_config(
&self,
chain_type: &global::ChainTypes,
file_name: &str,
wallet_config: Option<WalletConfig>,
logging_config: Option<LoggingConfig>,
) -> Result<(), Error> {
let mut default_config = GlobalWalletConfig::for_chain(chain_type);
let logging = match logging_config {
Some(l) => Some(l),
None => match default_config.members.as_ref() {
Some(m) => m.clone().logging.clone(),
None => None,
},
};
let wallet = match wallet_config {
Some(w) => w,
None => match default_config.members {
Some(m) => m.wallet,
None => WalletConfig::default(),
},
};
default_config = GlobalWalletConfig {
members: Some(GlobalWalletConfigMembers { wallet, logging }),
..default_config
};
let mut config_file_name = PathBuf::from(self.data_dir.clone());
config_file_name.push(file_name);
// create top level dir if it doesn't exist
let dd = PathBuf::from(self.data_dir.clone());
if !dd.exists() {
// try create
fs::create_dir_all(dd)?;
}
let mut data_dir_name = PathBuf::from(self.data_dir.clone());
data_dir_name.push(GRIN_WALLET_DIR);
@ -81,7 +121,10 @@ where
return Ok(());
}
default_config.update_paths(&PathBuf::from(self.data_dir.clone()));
let mut abs_path = std::env::current_dir()?;
abs_path.push(self.data_dir.clone());
default_config.update_paths(&PathBuf::from(abs_path));
let res = default_config.write_to_file(config_file_name.to_str().unwrap());
if let Err(e) = res {
let msg = format!(
@ -114,10 +157,18 @@ where
mnemonic: Option<ZeroingString>,
mnemonic_length: usize,
password: ZeroingString,
test_mode: bool,
) -> Result<(), Error> {
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();
let exists = WalletSeed::seed_file_exists(&data_dir_name);
if !test_mode {
if let Ok(true) = exists {
let msg = format!("Wallet seed already exists at: {}", data_dir_name);
return Err(ErrorKind::WalletSeedExists(msg))?;
}
}
let _ = WalletSeed::init_file(&data_dir_name, mnemonic_length, mnemonic, password);
info!("Wallet seed file created");
let _wallet: LMDBBackend<'a, C, K> =

View file

@ -86,7 +86,7 @@ impl WalletSeed {
pub fn seed_file_exists(data_file_dir: &str) -> Result<bool, Error> {
let seed_file_path = &format!("{}{}{}", data_file_dir, MAIN_SEPARATOR, SEED_FILE,);
println!("Seed file path: {}", seed_file_path);
debug!("Seed file path: {}", seed_file_path);
if Path::new(seed_file_path).exists() {
Ok(true)
} else {
@ -123,9 +123,9 @@ impl WalletSeed {
password: util::ZeroingString,
) -> Result<(), Error> {
let seed_file_path = &format!("{}{}{}", data_file_dir, MAIN_SEPARATOR, SEED_FILE,);
println!("data file dir: {}", data_file_dir);
debug!("data file dir: {}", data_file_dir);
if let Ok(true) = WalletSeed::seed_file_exists(data_file_dir) {
println!("seed file exists");
debug!("seed file exists");
WalletSeed::backup_seed(data_file_dir)?;
}
if !Path::new(&data_file_dir).exists() {
@ -157,7 +157,11 @@ impl WalletSeed {
let seed_file_path = &format!("{}{}{}", data_file_dir, MAIN_SEPARATOR, SEED_FILE,);
warn!("Generating wallet seed file at: {}", seed_file_path);
let _ = WalletSeed::seed_file_exists(data_file_dir)?;
let exists = WalletSeed::seed_file_exists(data_file_dir)?;
if exists {
let msg = format!("Wallet seed already exists at: {}", data_file_dir);
return Err(ErrorKind::WalletSeedExists(msg))?;
}
let seed = match recovery_phrase {
Some(p) => WalletSeed::from_mnemonic(p)?,

View file

@ -26,6 +26,4 @@ strum = "0.15"
strum_macros = "0.15"
grin_wallet_util = { path = "../util", version = "2.1.0-beta.1" }
[dev-dependencies]
grin_wallet_config = { path = "../config", version = "2.1.0-beta.1" }

View file

@ -274,6 +274,7 @@ where
// recieve the transaction back
{
let mut batch = w.batch(keychain_mask)?;
println!("Saving private context: {:?}", slate.id.as_bytes());
batch.save_private_context(slate.id.as_bytes(), 1, &context)?;
batch.commit()?;
}

View file

@ -126,8 +126,8 @@ pub enum ErrorKind {
DuplicateTransactionId,
/// Wallet seed already exists
#[fail(display = "Wallet seed exists error")]
WalletSeedExists,
#[fail(display = "Wallet seed exists error: {}", _0)]
WalletSeedExists(String),
/// Wallet seed doesn't exist
#[fail(display = "Wallet seed doesn't exist error")]

View file

@ -22,6 +22,7 @@
#![deny(unused_mut)]
#![warn(missing_docs)]
use grin_wallet_config as config;
use grin_wallet_util::grin_core;
use grin_wallet_util::grin_keychain;
use grin_wallet_util::grin_store;

View file

@ -37,7 +37,7 @@ pub enum SlateVersion {
V2,
}
#[derive(Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
/// Versions are ordered newest to oldest so serde attempts to
/// deserialize newer versions first, then falls back to older versions.

View file

@ -15,6 +15,7 @@
//! Types and traits that should be provided by a wallet
//! implementation
use crate::config::WalletConfig;
use crate::error::{Error, ErrorKind};
use crate::grin_core::core::hash::Hash;
use crate::grin_core::core::{Output, Transaction, TxKernel};
@ -23,7 +24,7 @@ use crate::grin_core::{global, ser};
use crate::grin_keychain::{Identifier, Keychain};
use crate::grin_util::secp::key::{PublicKey, SecretKey};
use crate::grin_util::secp::{self, pedersen, Secp256k1};
use crate::grin_util::ZeroingString;
use crate::grin_util::{LoggingConfig, ZeroingString};
use crate::slate::ParticipantMessages;
use chrono::prelude::*;
use failure::ResultExt;
@ -52,10 +53,20 @@ where
{
/// Sets the top level system wallet directory
/// default is assumed to be ~/.grin/main/wallet_data (or floonet equivalent)
fn set_wallet_directory(&mut self, dir: &str);
fn set_top_level_directory(&mut self, dir: &str) -> Result<(), Error>;
/// Sets the top level system wallet directory
/// default is assumed to be ~/.grin/main/wallet_data (or floonet equivalent)
fn get_top_level_directory(&self) -> Result<String, Error>;
/// Output a grin-wallet.toml file into the current top-level system wallet directory
fn create_config(&self, chain_type: &global::ChainTypes, file_name: &str) -> Result<(), Error>;
fn create_config(
&self,
chain_type: &global::ChainTypes,
file_name: &str,
wallet_config: Option<WalletConfig>,
logging_config: Option<LoggingConfig>,
) -> Result<(), Error>;
///
fn create_wallet(
@ -64,6 +75,7 @@ where
mnemonic: Option<ZeroingString>,
mnemonic_length: usize,
password: ZeroingString,
test_mode: bool,
) -> Result<(), Error>;
///

View file

@ -23,6 +23,7 @@ use crate::core::global;
use crate::util::init_logger;
use clap::App;
use grin_wallet_config as config;
use grin_wallet_impls::HTTPNodeClient;
use grin_wallet_util::grin_core as core;
use grin_wallet_util::grin_util as util;
use std::env;
@ -123,5 +124,8 @@ fn real_main() -> i32 {
.clone(),
);
cmd::wallet_command(&args, config)
let wallet_config = config.clone().members.unwrap().wallet;
let node_client = HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, None);
cmd::wallet_command(&args, config, node_client)
}

View file

@ -76,6 +76,10 @@ subcommands:
short: l
long: port
takes_value: true
- run_foreign:
help: Also run the Foreign API
long: run_foreign
takes_value: false
- send:
about: Builds a transaction to send coins and sends to the specified listener directly
args:
@ -168,7 +172,7 @@ subcommands:
short: f
long: fluff
- invoice:
about: Initialize an invoice transction.
about: Initialize an invoice transaction.
args:
- amount:
help: Number of coins to invoice with optional fraction, e.g. 12.423
@ -268,7 +272,7 @@ subcommands:
short: f
long: fluff
- cancel:
about: Cancels an previously created transaction, freeing previously locked outputs for use again
about: Cancels a previously created transaction, freeing previously locked outputs for use again
args:
- id:
help: The ID of the transaction to cancel

View file

@ -15,7 +15,6 @@
use crate::cmd::wallet_args;
use crate::config::GlobalWalletConfig;
use clap::ArgMatches;
use grin_wallet_impls::HTTPNodeClient;
use grin_wallet_libwallet::NodeClient;
use semver::Version;
use std::thread;
@ -23,12 +22,19 @@ use std::time::Duration;
const MIN_COMPAT_NODE_VERSION: &str = "2.0.0-beta.1";
pub fn wallet_command(wallet_args: &ArgMatches<'_>, config: GlobalWalletConfig) -> i32 {
pub fn wallet_command<C>(
wallet_args: &ArgMatches<'_>,
config: GlobalWalletConfig,
mut node_client: C,
) -> i32
where
C: NodeClient + 'static,
{
// just get defaults from the global config
let wallet_config = config.members.unwrap().wallet;
// Check the node version info, and exit with report if we're not compatible
let mut node_client = HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, None);
//let mut node_client = HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, None);
let global_wallet_args = wallet_args::parse_global_args(&wallet_config, &wallet_args)
.expect("Can't read configuration file");
node_client.set_node_api_secret(global_wallet_args.node_api_secret.clone());
@ -51,7 +57,7 @@ pub fn wallet_command(wallet_args: &ArgMatches<'_>, config: GlobalWalletConfig)
}
// ... if node isn't available, allow offline functions
let res = wallet_args::wallet_command(wallet_args, wallet_config, node_client, false);
let res = wallet_args::wallet_command(wallet_args, wallet_config, node_client, false, |_| {});
// we need to give log output a chance to catch up before exiting
thread::sleep(Duration::from_millis(100));

View file

@ -228,7 +228,7 @@ where
let mut wallet = Box::new(DefaultWalletImpl::<'static, C>::new(node_client.clone()).unwrap())
as Box<dyn WalletInst<'static, L, C, K>>;
let lc = wallet.lc_provider().unwrap();
lc.set_wallet_directory(&config.data_file_dir);
let _ = lc.set_top_level_directory(&config.data_file_dir);
Ok(Arc::new(Mutex::new(wallet)))
}
@ -403,6 +403,9 @@ pub fn parse_owner_api_args(
if let Some(port) = args.value_of("port") {
config.owner_api_listen_port = Some(port.parse().unwrap());
}
if args.is_present("run_foreign") {
config.owner_api_include_foreign = Some(true);
}
Ok(())
}
@ -761,14 +764,29 @@ pub fn parse_cancel_args(args: &ArgMatches) -> Result<command::CancelArgs, Parse
})
}
pub fn wallet_command<C>(
pub fn wallet_command<C, F>(
wallet_args: &ArgMatches,
mut wallet_config: WalletConfig,
mut node_client: C,
test_mode: bool,
wallet_inst_cb: F,
) -> Result<String, Error>
where
C: NodeClient + 'static + Clone,
F: FnOnce(
Arc<
Mutex<
Box<
dyn WalletInst<
'static,
DefaultLCProvider<'static, C, keychain::ExtKeychain>,
C,
keychain::ExtKeychain,
>,
>,
>,
>,
),
{
if let Some(t) = wallet_config.chain_type.clone() {
core::global::set_mining_mode(t);
@ -814,17 +832,29 @@ where
{
let mut wallet_lock = wallet.lock();
let lc = wallet_lock.lc_provider().unwrap();
lc.set_wallet_directory(&wallet_config.data_file_dir);
let _ = lc.set_top_level_directory(&wallet_config.data_file_dir);
}
// provide wallet instance back to the caller (handy for testing with
// local wallet proxy, etc)
wallet_inst_cb(wallet.clone());
// don't open wallet for certain lifecycle commands
let keychain_mask = match wallet_args.subcommand() {
("init", Some(_)) => None,
("recover", _) => None,
// Owner API can be started without a wallet present
// TODO: Not quite yet, next PR will deal with this
//("owner_api", _) => None,
_ => {
let mut open_wallet = true;
match wallet_args.subcommand() {
("init", Some(_)) => open_wallet = false,
("recover", _) => open_wallet = false,
("owner_api", _) => {
// If wallet exists, open it. Otherwise, that's fine too.
let mut wallet_lock = wallet.lock();
let lc = wallet_lock.lc_provider().unwrap();
open_wallet = lc.wallet_exists(None)?;
}
_ => {}
}
let keychain_mask = match open_wallet {
true => {
let mut wallet_lock = wallet.lock();
let lc = wallet_lock.lc_provider().unwrap();
let mask = lc.open_wallet(
@ -839,6 +869,7 @@ where
}
mask
}
false => None,
};
let km = (&keychain_mask).as_ref();
@ -864,7 +895,13 @@ where
("listen", Some(args)) => {
let mut c = wallet_config.clone();
let a = arg_parse!(parse_listen_args(&mut c, &args));
command::listen(wallet, keychain_mask, &c, &a, &global_wallet_args.clone())
command::listen(
wallet,
Arc::new(Mutex::new(keychain_mask)),
&c,
&a,
&global_wallet_args.clone(),
)
}
("owner_api", Some(args)) => {
let mut c = wallet_config.clone();
@ -950,7 +987,7 @@ where
command::check_repair(wallet, km, a)
}
_ => {
let msg = format!("Unknown wallet command, use 'grin help wallet' for details");
let msg = format!("Unknown wallet command, use 'grin-wallet help' for details");
return Err(ErrorKind::ArgumentError(msg).into());
}
};

View file

@ -27,7 +27,7 @@ use util::{Mutex, ZeroingString};
use grin_wallet_api::{EncryptedRequest, EncryptedResponse};
use grin_wallet_config::{GlobalWalletConfig, WalletConfig, GRIN_WALLET_DIR};
use grin_wallet_impls::{DefaultLCProvider, DefaultWalletImpl};
use grin_wallet_libwallet::{WalletInfo, WalletInst};
use grin_wallet_libwallet::{NodeClient, WalletInfo, WalletInst};
use grin_wallet_util::grin_core::global::{self, ChainTypes};
use grin_wallet_util::grin_keychain::ExtKeychain;
use grin_wallet_util::grin_util::{from_hex, static_secp_instance};
@ -149,6 +149,7 @@ pub fn config_command_wallet(
}
/// Handles setup and detection of paths for wallet
#[allow(dead_code)]
pub fn initial_setup_wallet(dir_name: &str, wallet_name: &str) -> WalletConfig {
let mut current_dir;
current_dir = env::current_dir().unwrap_or_else(|e| {
@ -185,6 +186,7 @@ fn get_wallet_subcommand<'a>(
}
//
// Helper to create an instance of the LMDB wallet
#[allow(dead_code)]
pub fn instantiate_wallet(
mut wallet_config: WalletConfig,
node_client: LocalWalletClient,
@ -226,7 +228,7 @@ pub fn instantiate_wallet(
top_level_wallet_dir.pop();
wallet_config.data_file_dir = top_level_wallet_dir.to_str().unwrap().into();
}
lc.set_wallet_directory(&wallet_config.data_file_dir);
let _ = lc.set_top_level_directory(&wallet_config.data_file_dir);
let keychain_mask = lc
.open_wallet(None, ZeroingString::from(passphrase), true, false)
.unwrap();
@ -235,6 +237,7 @@ pub fn instantiate_wallet(
Ok((Arc::new(Mutex::new(wallet)), keychain_mask))
}
#[allow(dead_code)]
pub fn execute_command(
app: &App,
test_dir: &str,
@ -247,18 +250,36 @@ pub fn execute_command(
let mut config = initial_setup_wallet(test_dir, wallet_name);
//unset chain type so it doesn't get reset
config.chain_type = None;
wallet_args::wallet_command(&args, config.clone(), client.clone(), true)
wallet_args::wallet_command(&args, config.clone(), client.clone(), true, |_| {})
}
// as above, but without necessarily setting up the wallet
#[allow(dead_code)]
pub fn execute_command_no_setup(
pub fn execute_command_no_setup<C, F>(
app: &App,
test_dir: &str,
wallet_name: &str,
client: &LocalWalletClient,
client: &C,
arg_vec: Vec<&str>,
) -> Result<String, grin_wallet_controller::Error> {
f: F,
) -> Result<String, grin_wallet_controller::Error>
where
C: NodeClient + 'static + Clone,
F: FnOnce(
Arc<
Mutex<
Box<
dyn WalletInst<
'static,
DefaultLCProvider<'static, C, ExtKeychain>,
C,
ExtKeychain,
>,
>,
>,
>,
),
{
let args = app.clone().get_matches_from(arg_vec);
let _ = get_wallet_subcommand(test_dir, wallet_name, args.clone());
let config = config::initial_setup_wallet(&ChainTypes::AutomatedTesting, None).unwrap();
@ -266,7 +287,7 @@ pub fn execute_command_no_setup(
wallet_config.chain_type = None;
wallet_config.api_secret_path = None;
wallet_config.node_api_secret_path = None;
wallet_args::wallet_command(&args, wallet_config, client.clone(), true)
wallet_args::wallet_command(&args, wallet_config, client.clone(), true, f)
}
pub fn post<IN>(url: &Url, api_secret: Option<String>, input: &IN) -> Result<String, api::Error>
@ -367,6 +388,7 @@ where
.clone()
.unwrap();
//println!("RES: {}", res);
if res["Err"] != json!(null) {
Ok(Err(WalletAPIReturnError {
message: res["Err"].as_str().unwrap().to_owned(),
@ -374,8 +396,21 @@ where
}))
} else {
// deserialize result into expected type
let value: OUT = serde_json::from_value(res["Ok"].clone()).unwrap();
Ok(Ok(value))
let raw_value = res["Ok"].clone();
let raw_value_str = serde_json::to_string_pretty(&raw_value).unwrap();
//println!("Raw value: {}", raw_value_str);
let ok_val = serde_json::from_str(&raw_value_str);
match ok_val {
Ok(v) => {
let value: OUT = v;
Ok(Ok(value))
}
Err(e) => {
println!("Error deserializing: {:?}", e);
let value: OUT = serde_json::from_value(json!("Null")).unwrap();
Ok(Ok(value))
}
}
}
}

View file

@ -0,0 +1,19 @@
{
"jsonrpc": "2.0",
"method": "init_send_tx",
"params": {
"args": {
"src_acct_name": null,
"amount": "600000000000",
"minimum_confirmations": 2,
"max_outputs": 500,
"num_change_outputs": 1,
"selection_strategy_is_use_all": true,
"message": "my message",
"target_slate_version": null,
"send_args": null
}
},
"id": 1
}

View file

@ -0,0 +1,8 @@
{
"jsonrpc": "2.0",
"method": "close_wallet",
"params": {
"name": null
},
"id": 1
}

View file

@ -0,0 +1,10 @@
{
"jsonrpc": "2.0",
"method": "create_config",
"params": {
"chain_type": "AutomatedTesting",
"wallet_config": null,
"logging_config": null
},
"id": 1
}

View file

@ -0,0 +1,11 @@
{
"jsonrpc": "2.0",
"method": "create_wallet",
"params": {
"name": null,
"mnemonic": null,
"mnemonic_length": 0,
"password": "passwoid"
},
"id": 1
}

View file

@ -0,0 +1,7 @@
{
"jsonrpc": "2.0",
"method": "get_top_level_directory",
"params": {
},
"id": 1
}

View file

@ -0,0 +1,20 @@
{
"jsonrpc": "2.0",
"method": "init_send_tx",
"params": {
"token": null,
"args": {
"src_acct_name": null,
"amount": "600000000000",
"minimum_confirmations": 2,
"max_outputs": 500,
"num_change_outputs": 1,
"selection_strategy_is_use_all": true,
"message": "my message",
"target_slate_version": null,
"send_args": null
}
},
"id": 1
}

View file

@ -2,9 +2,8 @@
"jsonrpc": "2.0",
"method": "open_wallet",
"params": {
"token": null,
"refresh_from_node": true,
"minimum_confirmations": 1
"name": null,
"password": "passwoid"
},
"id": 1
}

View file

@ -197,7 +197,7 @@ fn owner_v3_init_secure() -> Result<(), grin_wallet_controller::Error> {
println!("RES 11: {:?}", res);
assert!(res.is_ok());
// 12) A request which triggers and API error (not an encryption error)
// 12) A request which triggers an API error (not an encryption error)
let req = serde_json::json!({
"jsonrpc": "2.0",
"id": 1,
@ -213,6 +213,14 @@ fn owner_v3_init_secure() -> Result<(), grin_wallet_controller::Error> {
assert!(res.is_err());
assert_eq!(res.unwrap_err().code, -32601);
// 13) A request which triggers an internal API error (not enough funds)
let req = include_str!("data/v3_reqs/init_send_tx.req.json");
let res =
send_request_enc::<String>(13, 1, "http://127.0.0.1:33420/v3/owner", &req, &shared_key)?;
println!("RES 13: {:?}", res);
assert!(res.is_err());
assert_eq!(res.unwrap_err().code, -32099);
clean_output_dir(test_dir);
Ok(())

374
tests/owner_v3_lifecycle.rs Normal file
View file

@ -0,0 +1,374 @@
// Copyright 2019 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.
#[macro_use]
extern crate clap;
#[macro_use]
extern crate log;
extern crate grin_wallet;
use grin_wallet_api::ECDHPubkey;
use grin_wallet_impls::test_framework::{self, LocalWalletClient, WalletProxy};
use clap::App;
use std::thread;
use std::time::Duration;
use grin_wallet_impls::DefaultLCProvider;
use grin_wallet_libwallet::{InitTxArgs, Slate, SlateVersion, VersionedSlate};
use grin_wallet_util::grin_keychain::ExtKeychain;
use serde_json;
use grin_wallet_util::grin_util::Mutex;
use std::path::PathBuf;
use std::sync::Arc;
#[macro_use]
mod common;
use common::{
clean_output_dir, derive_ecdh_key, execute_command, execute_command_no_setup,
initial_setup_wallet, instantiate_wallet, send_request, send_request_enc, setup,
RetrieveSummaryInfoResp,
};
#[test]
fn owner_v3_lifecycle() -> Result<(), grin_wallet_controller::Error> {
let test_dir = "target/test_output/owner_v3_lifecycle";
setup(test_dir);
let yml = load_yaml!("../src/bin/grin-wallet.yml");
let app = App::from_yaml(yml);
// Create a new proxy to simulate server and wallet responses
let wallet_proxy_a: Arc<
Mutex<
WalletProxy<
DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
LocalWalletClient,
ExtKeychain,
>,
>,
> = Arc::new(Mutex::new(WalletProxy::new(test_dir)));
let (chain, wallet2, mask2_i) = {
let mut wallet_proxy = wallet_proxy_a.lock();
let chain = wallet_proxy.chain.clone();
// Create wallet 2 manually, which will mine a bit and insert some
// grins into the equation
let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone());
let arg_vec = vec!["grin-wallet", "-p", "password", "init", "-h"];
execute_command(&app, test_dir, "wallet2", &client2, arg_vec.clone())?;
let config2 = initial_setup_wallet(test_dir, "wallet2");
//config2.api_listen_port = 23415;
let (wallet2, mask2_i) =
instantiate_wallet(config2.clone(), client2.clone(), "password", "default")?;
wallet_proxy.add_wallet(
"wallet2",
client2.get_send_instance(),
wallet2.clone(),
mask2_i.clone(),
);
// start up the owner api with wallet created
let arg_vec = vec!["grin-wallet", "owner_api", "-l", "43420", "--run_foreign"];
// should create new wallet file
let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone());
let p = wallet_proxy_a.clone();
thread::spawn(move || {
let yml = load_yaml!("../src/bin/grin-wallet.yml");
let app = App::from_yaml(yml);
execute_command_no_setup(
&app,
test_dir,
"wallet1",
&client1,
arg_vec.clone(),
|wallet_inst| {
let mut wallet_proxy = p.lock();
wallet_proxy.add_wallet(
"wallet1",
client1.get_send_instance(),
wallet_inst,
None,
);
},
)
.unwrap();
});
(chain, wallet2, mask2_i)
};
// give a bit for wallet to init and populate proxy with wallet via callback in thread above
thread::sleep(Duration::from_millis(500));
let mask2 = (&mask2_i).as_ref();
let wallet_proxy = wallet_proxy_a.clone();
// Set the wallet proxy listener running
thread::spawn(move || {
let mut p = wallet_proxy.lock();
if let Err(e) = p.run() {
error!("Wallet Proxy error: {}", e);
}
});
// mine into wallet 2 a bit
let bh = 10u64;
let _ =
test_framework::award_blocks_to_wallet(&chain, wallet2.clone(), mask2, bh as usize, false);
// We have an owner API with no wallet initialized. Init the secure API
let sec_key_str = "e00dcc4a009e3427c6b1e1a550c538179d46f3827a13ed74c759c860761caf1e";
let req = include_str!("data/v3_reqs/init_secure_api.req.json");
let res = send_request(1, "http://127.0.0.1:43420/v3/owner", req)?;
println!("RES 1: {:?}", res);
assert!(res.is_ok());
let value: ECDHPubkey = res.unwrap();
let shared_key = derive_ecdh_key(sec_key_str, &value.ecdh_pubkey);
// 2) get the top level directory, should default to ~/.grin/auto
let req = include_str!("data/v3_reqs/get_top_level.req.json");
let res =
send_request_enc::<String>(1, 1, "http://127.0.0.1:43420/v3/owner", &req, &shared_key)?;
println!("RES 2: {:?}", res);
assert!(res.is_ok());
assert!(res.unwrap().contains("auto"));
// 3) now set the top level directory to our test wallet dir
let req = serde_json::json!({
"jsonrpc": "2.0",
"id": 1,
"method": "set_top_level_directory",
"params": {
"dir": format!("{}/wallet1", test_dir)
}
});
let res = send_request_enc::<String>(
1,
1,
"http://127.0.0.1:43420/v3/owner",
&req.to_string(),
&shared_key,
)?;
println!("RES 3: {:?}", res);
assert!(res.is_ok());
// 4) create a configuration file in top level directory
let req = include_str!("data/v3_reqs/create_config.req.json");
let res =
send_request_enc::<String>(1, 1, "http://127.0.0.1:43420/v3/owner", &req, &shared_key)?;
println!("RES 4: {:?}", res);
assert!(res.is_ok());
let pb = PathBuf::from(format!("{}/wallet1/grin-wallet.toml", test_dir));
assert!(pb.exists());
// 5) Try and perform an operation without having a wallet open
let req = include_str!("data/v3_reqs/retrieve_info.req.json");
let res =
send_request_enc::<String>(1, 1, "http://127.0.0.1:43420/v3/owner", &req, &shared_key)?;
println!("RES 5: {:?}", res);
assert!(res.is_err());
// 6) Create a wallet
let req = include_str!("data/v3_reqs/create_wallet.req.json");
let res =
send_request_enc::<String>(1, 1, "http://127.0.0.1:43420/v3/owner", &req, &shared_key)?;
println!("RES 6: {:?}", res);
assert!(res.is_ok());
// 7) Try and create a wallet when one exists
let req = include_str!("data/v3_reqs/create_wallet.req.json");
let res =
send_request_enc::<String>(1, 1, "http://127.0.0.1:43420/v3/owner", &req, &shared_key)?;
println!("RES 7: {:?}", res);
assert!(res.is_err());
// 8) Open the wallet
let req = include_str!("data/v3_reqs/open_wallet.req.json");
let res =
send_request_enc::<String>(1, 1, "http://127.0.0.1:43420/v3/owner", &req, &shared_key)?;
println!("RES 8: {:?}", res);
assert!(res.is_ok());
let token = res.unwrap();
// 9) Send a request with our new token
let req = serde_json::json!({
"jsonrpc": "2.0",
"id": 1,
"method": "retrieve_summary_info",
"params": {
"token": token,
"refresh_from_node": true,
"minimum_confirmations": 1
}
});
let res = send_request_enc::<RetrieveSummaryInfoResp>(
1,
1,
"http://127.0.0.1:43420/v3/owner",
&req.to_string(),
&shared_key,
)?;
println!("RES 9: {:?}", res);
assert!(res.is_ok());
// 10) Send same request with no token (even though one is expected)
let req = serde_json::json!({
"jsonrpc": "2.0",
"id": 1,
"method": "retrieve_summary_info",
"params": {
"token": null,
"refresh_from_node": true,
"minimum_confirmations": 1
}
});
let res = send_request_enc::<RetrieveSummaryInfoResp>(
1,
1,
"http://127.0.0.1:43420/v3/owner",
&req.to_string(),
&shared_key,
)?;
println!("RES 10: {:?}", res);
assert!(res.is_err());
// 11) Close the wallet
let req = include_str!("data/v3_reqs/close_wallet.req.json");
let res =
send_request_enc::<String>(1, 1, "http://127.0.0.1:43420/v3/owner", &req, &shared_key)?;
println!("RES 11: {:?}", res);
assert!(res.is_ok());
// 12) Wallet is closed
let req = serde_json::json!({
"jsonrpc": "2.0",
"id": 1,
"method": "retrieve_summary_info",
"params": {
"token": token,
"refresh_from_node": true,
"minimum_confirmations": 1
}
});
let res = send_request_enc::<RetrieveSummaryInfoResp>(
1,
1,
"http://127.0.0.1:43420/v3/owner",
&req.to_string(),
&shared_key,
)?;
println!("RES 12: {:?}", res);
assert!(res.is_err());
// 13) Open the wallet again
let req = include_str!("data/v3_reqs/open_wallet.req.json");
let res =
send_request_enc::<String>(1, 1, "http://127.0.0.1:43420/v3/owner", &req, &shared_key)?;
println!("RES 13: {:?}", res);
assert!(res.is_ok());
let token = res.unwrap();
// 14) Send a request with our new token
let req = serde_json::json!({
"jsonrpc": "2.0",
"id": 1,
"method": "retrieve_summary_info",
"params": {
"token": token,
"refresh_from_node": true,
"minimum_confirmations": 1
}
});
let res = send_request_enc::<RetrieveSummaryInfoResp>(
1,
1,
"http://127.0.0.1:43420/v3/owner",
&req.to_string(),
&shared_key,
)?;
println!("RES 14: {:?}", res);
assert!(res.is_ok());
//15) Ask wallet 2 for some grins
let req = serde_json::json!({
"jsonrpc": "2.0",
"id": 1,
"method": "issue_invoice_tx",
"params": {
"token": token,
"args": {
"amount": "6000000000",
"message": "geez a block of grins",
"dest_acct_name": null,
"target_slate_version": null
}
}
});
let res = send_request_enc::<VersionedSlate>(
1,
1,
"http://127.0.0.1:43420/v3/owner",
&req.to_string(),
&shared_key,
)?;
println!("RES 15: {:?}", res);
assert!(res.is_ok());
let mut slate: Slate = res.unwrap().into();
// give this slate over to wallet 2 manually
grin_wallet_controller::controller::owner_single_use(wallet2.clone(), mask2, |api, m| {
let args = InitTxArgs {
src_acct_name: None,
amount: slate.amount,
minimum_confirmations: 1,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: false,
..Default::default()
};
let res = api.process_invoice_tx(m, &slate, args);
assert!(res.is_ok());
slate = res.unwrap();
api.tx_lock_outputs(m, &slate, 0)?;
Ok(())
})?;
//16) Finalize the invoice tx (to foreign api)
// (Tests that foreign API on same port also has its stored mask updated)
let req = serde_json::json!({
"jsonrpc": "2.0",
"id": 1,
"method": "finalize_invoice_tx",
"params": {
"slate": VersionedSlate::into_version(slate, SlateVersion::V2),
}
});
let res =
send_request::<VersionedSlate>(1, "http://127.0.0.1:43420/v2/foreign", &req.to_string())?;
println!("RES 16: {:?}", res);
assert!(res.is_ok());
thread::sleep(Duration::from_millis(200));
clean_output_dir(test_dir);
Ok(())
}