Normalize line endings

This commit is contained in:
Anynomous
2025-09-18 14:51:29 +02:00
parent aed932726c
commit 86de7c6bcd
165 changed files with 0 additions and 53523 deletions
-88
View File
@@ -1,88 +0,0 @@
[package]
name = "grin_wallet_controller"
version = "5.4.0-alpha.1"
authors = ["Grin Developers <mimblewimble@lists.launchpad.net>"]
description = "Controllers for grin wallet instantiation"
license = "Apache-2.0"
repository = "https://github.com/mimblewimble/grin-wallet"
keywords = [ "crypto", "grin", "mimblewimble" ]
exclude = ["**/*.grin", "**/*.grin2"]
#build = "src/build/build.rs"
edition = "2018"
[dependencies]
futures = "0.3"
hyper = "0.13"
rand = "0.7"
serde = "1"
serde_derive = "1"
serde_json = "1"
log = "0.4"
prettytable-rs = "0.10"
ring = "0.16"
term = "0.6"
tokio = { version = "0.2", features = ["full"] }
uuid = { version = "0.8", features = ["serde", "v4"] }
url = "2.1"
chrono = { version = "0.4.11", features = ["serde"] }
easy-jsonrpc-mw = "0.5.4"
lazy_static = "1"
thiserror = "1"
qr_code = "1.1.0"
grin_wallet_util = { path = "../util", version = "5.4.0-alpha.1" }
grin_wallet_api = { path = "../api", version = "5.4.0-alpha.1" }
grin_wallet_impls = { path = "../impls", version = "5.4.0-alpha.1" }
grin_wallet_libwallet = { path = "../libwallet", version = "5.4.0-alpha.1" }
grin_wallet_config = { path = "../config", version = "5.4.0-alpha.1" }
##### Grin Imports
# For Release
grin_core = "5.3.3"
grin_keychain = "5.3.3"
grin_util = "5.3.3"
grin_api = "5.3.3"
# For beta release
# grin_core = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3"}
# 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" }
# 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" }
# For local testing
# grin_core = { path = "../../grin/core"}
# grin_keychain = { path = "../../grin/keychain"}
# grin_util = { path = "../../grin/util"}
# grin_api = { path = "../../grin/api"}
#####
[dev-dependencies]
ed25519-dalek = "1.0.0-pre.4"
remove_dir_all = "0.7"
##### Grin Imports
# For Release
grin_chain = "5.3.3"
# 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"}
#####
File diff suppressed because it is too large Load Diff
-875
View File
@@ -1,875 +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.
//! Controller for wallet.. instantiates and handles listeners (or single-run
//! invocations) as needed.
use crate::api::{self, ApiServer, BasicAuthMiddleware, ResponseFuture, Router, TLSConfig};
use crate::config::TorConfig;
use crate::keychain::Keychain;
use crate::libwallet::{
address, Error, NodeClient, NodeVersionInfo, Slate, SlatepackAddress, WalletInst,
WalletLCProvider, GRIN_BLOCK_HEADER_VERSION,
};
use crate::util::secp::key::SecretKey;
use crate::util::{from_hex, static_secp_instance, to_base64, Mutex};
use futures::channel::oneshot;
use grin_wallet_api::JsonId;
use grin_wallet_config::types::{TorBridgeConfig, TorProxyConfig};
use grin_wallet_util::OnionV3Address;
use hyper::body;
use hyper::header::HeaderValue;
use hyper::{Body, Request, Response, StatusCode};
use qr_code::QrCode;
use serde::{Deserialize, Serialize};
use serde_json;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::net::SocketAddr;
use std::sync::Arc;
use crate::impls::tor::config as tor_config;
use crate::impls::tor::process as tor_process;
use crate::impls::tor::{bridge as tor_bridge, proxy as tor_proxy};
use crate::apiwallet::{
EncryptedRequest, EncryptedResponse, EncryptionErrorResponse, Foreign,
ForeignCheckMiddlewareFn, ForeignRpc, Owner, OwnerRpc,
};
use easy_jsonrpc_mw;
use easy_jsonrpc_mw::{Handler, MaybeReply};
lazy_static! {
pub static ref GRIN_OWNER_BASIC_REALM: HeaderValue =
HeaderValue::from_str("Basic realm=GrinOwnerAPI").unwrap();
}
fn check_middleware(
name: ForeignCheckMiddlewareFn,
node_version_info: Option<NodeVersionInfo>,
slate: Option<&Slate>,
) -> Result<(), Error> {
match name {
// allow coinbases to be built regardless
ForeignCheckMiddlewareFn::BuildCoinbase => Ok(()),
_ => {
let mut bhv = 3;
if let Some(n) = node_version_info {
bhv = n.block_header_version;
}
if let Some(s) = slate {
if bhv > 4 && s.version_info.block_header_version < GRIN_BLOCK_HEADER_VERSION {
Err(Error::Compatibility(
"Incoming Slate is not compatible with this wallet. \
Please upgrade the node or use a different one."
.into(),
))?;
}
}
Ok(())
}
}
}
/// initiate the tor listener
fn init_tor_listener<L, C, K>(
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K> + 'static>>>,
keychain_mask: Arc<Mutex<Option<SecretKey>>>,
addr: &str,
bridge: TorBridgeConfig,
tor_proxy: TorProxyConfig,
) -> Result<(tor_process::TorProcess, SlatepackAddress), Error>
where
L: WalletLCProvider<'static, C, K> + 'static,
C: NodeClient + 'static,
K: Keychain + 'static,
{
let mut process = tor_process::TorProcess::new();
let mask = keychain_mask.lock();
// eventually want to read a list of service config keys
let mut w_lock = wallet.lock();
let lc = w_lock.lc_provider()?;
let w_inst = lc.wallet_inst()?;
let k = w_inst.keychain((&mask).as_ref())?;
let parent_key_id = w_inst.parent_key_id();
let tor_dir = format!("{}/tor/listener", lc.get_top_level_directory()?);
let sec_key = address::address_from_derivation_path(&k, &parent_key_id, 0)
.map_err(|e| Error::TorConfig(format!("{:?}", e)))?;
let onion_address = OnionV3Address::from_private(&sec_key.0)
.map_err(|e| Error::TorConfig(format!("{:?}", e)))?;
let sp_address = SlatepackAddress::try_from(onion_address.clone())?;
let mut hm_tor_bridge: HashMap<String, String> = HashMap::new();
let mut tor_timeout = 20;
if bridge.bridge_line.is_some() {
tor_timeout = 40;
let bridge_config = tor_bridge::TorBridge::try_from(bridge)
.map_err(|e| Error::TorConfig(format!("{}", e).into()))?;
hm_tor_bridge = bridge_config
.to_hashmap()
.map_err(|e| Error::TorConfig(format!("{}", e).into()))?;
}
let mut hm_tor_poxy: HashMap<String, String> = HashMap::new();
if tor_proxy.transport.is_some() || tor_proxy.allowed_port.is_some() {
let proxy_config = tor_proxy::TorProxy::try_from(tor_proxy)
.map_err(|e| Error::TorConfig(format!("{}", e).into()))?;
hm_tor_poxy = proxy_config
.to_hashmap()
.map_err(|e| Error::TorConfig(format!("{}", e).into()))?;
}
warn!(
"Starting Tor Hidden Service for API listener at address {}, binding to {}",
onion_address, addr
);
tor_config::output_tor_listener_config(
&tor_dir,
addr,
&vec![sec_key],
hm_tor_bridge,
hm_tor_poxy,
)
.map_err(|e| Error::TorConfig(format!("{:?}", e).into()))?;
// Start TOR process
process
.torrc_path(&format!("{}/torrc", tor_dir))
.working_dir(&tor_dir)
.timeout(tor_timeout)
.completion_percent(100)
.launch()
.map_err(|e| Error::TorProcess(format!("{:?}", e)))?;
Ok((process, sp_address))
}
/// Instantiate wallet Owner API for a single-use (command line) call
/// Return a function containing a loaded API context to call
pub fn owner_single_use<L, F, C, K>(
wallet: Option<Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>>,
keychain_mask: Option<&SecretKey>,
api_context: Option<&mut Owner<L, C, K>>,
f: F,
) -> Result<(), Error>
where
L: WalletLCProvider<'static, C, K> + 'static,
F: FnOnce(&mut Owner<L, C, K>, Option<&SecretKey>) -> Result<(), Error>,
C: NodeClient + 'static,
K: Keychain + 'static,
{
match api_context {
Some(c) => f(c, keychain_mask)?,
None => {
let wallet = match wallet {
Some(w) => w,
None => {
return Err(Error::GenericError(format!(
"Instantiated wallet or Owner API context must be provided"
)))
}
};
f(&mut Owner::new(wallet, None), keychain_mask)?
}
}
Ok(())
}
/// Instantiate wallet Foreign API for a single-use (command line) call
/// Return a function containing a loaded API context to call
pub fn foreign_single_use<'a, L, F, C, K>(
wallet: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
keychain_mask: Option<SecretKey>,
f: F,
) -> Result<(), Error>
where
L: WalletLCProvider<'a, C, K>,
F: FnOnce(&mut Foreign<'a, L, C, K>) -> Result<(), Error>,
C: NodeClient + 'a,
K: Keychain + 'a,
{
f(&mut Foreign::new(
wallet,
keychain_mask,
Some(check_middleware),
false,
))?;
Ok(())
}
/// Listener version, providing same API but listening for requests on a
/// port and wrapping the calls
/// Note keychain mask is only provided here in case the foreign listener is also being used
/// 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: Arc<Mutex<Option<SecretKey>>>,
addr: &str,
api_secret: Option<String>,
tls_config: Option<TLSConfig>,
owner_api_include_foreign: Option<bool>,
tor_config: Option<TorConfig>,
test_mode: bool,
) -> Result<(), Error>
where
L: WalletLCProvider<'static, C, K> + 'static,
C: NodeClient + 'static,
K: Keychain + 'static,
{
let mut router = Router::new();
if api_secret.is_some() {
let api_basic_auth =
"Basic ".to_string() + &to_base64(&("grin:".to_string() + &api_secret.unwrap()));
let basic_auth_middleware = Arc::new(BasicAuthMiddleware::new(
api_basic_auth,
&GRIN_OWNER_BASIC_REALM,
Some("/v2/foreign".into()),
));
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_v3 = OwnerAPIHandlerV3::new(
wallet.clone(),
keychain_mask.clone(),
tor_config.clone(),
running_foreign,
);
router
.add_route("/v3/owner", Arc::new(api_handler_v3))
.map_err(|_| Error::GenericError("Router failed to add route".to_string()))?;
// If so configured, add the foreign API to the same port
if running_foreign {
warn!("Starting HTTP Foreign API on Owner server at {}.", addr);
let foreign_api_handler_v2 =
ForeignAPIHandlerV2::new(wallet, keychain_mask, test_mode, Mutex::new(tor_config));
router
.add_route("/v2/foreign", Arc::new(foreign_api_handler_v2))
.map_err(|_| Error::GenericError("Router failed to add route".to_string()))?;
}
let api_chan: &'static mut (oneshot::Sender<()>, oneshot::Receiver<()>) =
Box::leak(Box::new(oneshot::channel::<()>()));
let mut apis = ApiServer::new();
warn!("Starting HTTP Owner API server at {}.", addr);
let socket_addr: SocketAddr = addr.parse().expect("unable to parse socket address");
let api_thread = apis
.start(socket_addr, router, tls_config, api_chan)
.map_err(|_| Error::GenericError("API thread failed to start".to_string()))?;
warn!("HTTP Owner listener started.");
api_thread
.join()
.map_err(|e| Error::GenericError(format!("API thread panicked :{:?}", e)))
}
/// Listener version, providing same API but listening for requests on a
/// 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: Arc<Mutex<Option<SecretKey>>>,
addr: &str,
tls_config: Option<TLSConfig>,
use_tor: bool,
test_mode: bool,
tor_config: Option<TorConfig>,
) -> Result<(), Error>
where
L: WalletLCProvider<'static, C, K> + 'static,
C: NodeClient + 'static,
K: Keychain + 'static,
{
// Check if wallet has been opened first
{
let mut w_lock = wallet.lock();
let lc = w_lock.lc_provider()?;
let _ = lc.wallet_inst()?;
}
let (tor_bridge, tor_proxy) = match tor_config.clone() {
Some(s) => (s.bridge, s.proxy),
None => (TorBridgeConfig::default(), TorProxyConfig::default()),
};
// need to keep in scope while the main listener is running
let (_tor_process, address) = match use_tor {
true => {
match init_tor_listener(
wallet.clone(),
keychain_mask.clone(),
addr,
tor_bridge,
tor_proxy,
) {
Ok((tp, addr)) => (Some(tp), Some(addr)),
Err(e) => {
warn!("Unable to start TOR listener; Check that TOR executable is installed and on your path");
error!("Tor Error: {}", e);
warn!("Listener will be available via HTTP only");
(None, None)
}
}
}
false => (None, None),
};
let api_handler_v2 =
ForeignAPIHandlerV2::new(wallet, keychain_mask, test_mode, Mutex::new(tor_config));
let mut router = Router::new();
router
.add_route("/v2/foreign", Arc::new(api_handler_v2))
.map_err(|_| Error::GenericError("Router failed to add route".to_string()))?;
let api_chan: &'static mut (oneshot::Sender<()>, oneshot::Receiver<()>) =
Box::leak(Box::new(oneshot::channel::<()>()));
let mut apis = ApiServer::new();
warn!("Starting HTTP Foreign listener API server at {}.", addr);
let socket_addr: SocketAddr = addr.parse().expect("unable to parse socket address");
let api_thread = apis
.start(socket_addr, router, tls_config, api_chan)
.map_err(|_| Error::GenericError("API thread failed to start".to_string()))?;
warn!("HTTP Foreign listener started.");
if let Some(a) = address {
let qr_string = match QrCode::new(a.to_string()) {
Ok(qr) => qr.to_string(false, 3),
Err(_) => "Failed to generate QR code!".to_string(),
};
warn!("Slatepack Address is: {}\n{}", a, qr_string);
}
api_thread
.join()
.map_err(|e| Error::GenericError(format!("API thread panicked :{:?}", e)))
}
/// V3 API Handler/Wrapper for owner functions, which include a secure
/// mode + lifecycle functions
pub struct OwnerAPIHandlerV3<L, C, K>
where
L: WalletLCProvider<'static, C, K> + 'static,
C: NodeClient + 'static,
K: Keychain + 'static,
{
/// Wallet instance
pub wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K> + 'static>>>,
/// Handle to Owner API
owner_api: Arc<Owner<L, C, K>>,
/// 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;
impl OwnerV3Helpers {
/// Checks whether a request is to init the secure API
pub fn is_init_secure_api(val: &serde_json::Value) -> bool {
matches!(val["method"].as_str(), Some("init_secure_api"))
}
/// Checks whether a request is to open the wallet
pub fn is_open_wallet(val: &serde_json::Value) -> bool {
matches!(val["method"].as_str(), Some("open_wallet"))
}
/// Checks whether a request is an encrypted request
pub fn is_encrypted_request(val: &serde_json::Value) -> bool {
matches!(val["method"].as_str(), Some("encrypted_request_v3"))
}
/// whether encryption is enabled
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(
key: Arc<Mutex<Option<SecretKey>>>,
) -> Result<(), serde_json::Value> {
match OwnerV3Helpers::encryption_enabled(key) {
true => Ok(()),
false => Err(EncryptionErrorResponse::new(
1,
-32001,
"Encryption must be enabled. Please call 'init_secure_api` first",
)
.as_json_value()),
}
}
/// Update the statically held owner API shared key
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 = 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) {
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<(JsonId, serde_json::Value), serde_json::Value> {
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(
1,
-32002,
&format!("Encrypted request format error: {}", e),
)
.as_json_value()
})?;
let id = enc_req.id.clone();
let res = enc_req.decrypt(&shared_key).map_err(|e| {
EncryptionErrorResponse::new(1, -32002, &format!("Decryption error: {}", e))
.as_json_value()
})?;
Ok((id, res))
}
/// Encrypt a response
pub fn encrypt_response(
key: Arc<Mutex<Option<SecretKey>>>,
id: &JsonId,
res: &serde_json::Value,
) -> Result<serde_json::Value, serde_json::Value> {
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))
.as_json_value()
})?;
let res = enc_res.as_json_value().map_err(|e| {
EncryptionErrorResponse::new(
1,
-32002,
&format!("Encrypted response format error: {}", e),
)
.as_json_value()
})?;
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>
where
L: WalletLCProvider<'static, C, K>,
C: NodeClient + 'static,
K: Keychain + 'static,
{
/// 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>>>,
tor_config: Option<TorConfig>,
running_foreign: bool,
) -> OwnerAPIHandlerV3<L, C, K> {
let owner_api = Owner::new(wallet.clone(), None);
owner_api.set_tor_config(tor_config);
let owner_api = Arc::new(owner_api);
OwnerAPIHandlerV3 {
wallet,
owner_api,
shared_key: Arc::new(Mutex::new(None)),
keychain_mask: keychain_mask,
running_foreign,
}
}
async fn call_api(
req: Request<Body>,
key: Arc<Mutex<Option<SecretKey>>>,
mask: Arc<Mutex<Option<SecretKey>>>,
running_foreign: bool,
api: Arc<Owner<L, C, K>>,
) -> Result<serde_json::Value, Error> {
let mut val: serde_json::Value = parse_body(req).await?;
let mut is_init_secure_api = OwnerV3Helpers::is_init_secure_api(&val);
let mut was_encrypted = false;
let mut encrypted_req_id = JsonId::StrId(String::from(""));
if !is_init_secure_api {
if let Err(v) = OwnerV3Helpers::check_encryption_started(key.clone()) {
return Ok(v);
}
let res = OwnerV3Helpers::decrypt_request(key.clone(), &val);
match res {
Err(e) => return Ok(e),
Ok(v) => {
encrypted_req_id = v.0.clone();
val = v.1;
}
}
was_encrypted = true;
}
// 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 <dyn OwnerRpc>::handle_request(&*api, val) {
MaybeReply::Reply(mut r) => {
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(
key.clone(),
&encrypted_req_id,
&unencrypted_intercept,
);
r = match res {
Ok(v) => v,
Err(v) => return Ok(v),
}
}
// intercept init_secure_api response (after encryption,
// 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(),
);
}
Ok(r)
}
MaybeReply::DontReply => {
// Since it's http, we need to return something. We return [] because jsonrpc
// clients will parse it as an empty batch response.
Ok(serde_json::json!([]))
}
}
}
async fn handle_post_request(
req: Request<Body>,
key: Arc<Mutex<Option<SecretKey>>>,
mask: Arc<Mutex<Option<SecretKey>>>,
running_foreign: bool,
api: Arc<Owner<L, C, K>>,
) -> Result<Response<Body>, Error> {
let res = Self::call_api(req, key, mask, running_foreign, api).await?;
Ok(json_response_pretty(&res))
}
}
impl<L, C, K> api::Handler for OwnerAPIHandlerV3<L, C, K>
where
L: WalletLCProvider<'static, C, K> + 'static,
C: NodeClient + 'static,
K: Keychain + 'static,
{
fn post(&self, req: Request<Body>) -> ResponseFuture {
let key = self.shared_key.clone();
let mask = self.keychain_mask.clone();
let running_foreign = self.running_foreign;
let api = self.owner_api.clone();
Box::pin(async move {
match Self::handle_post_request(req, key, mask, running_foreign, api).await {
Ok(r) => Ok(r),
Err(e) => {
error!("Request Error: {:?}", e);
Ok(create_error_response(e))
}
}
})
}
fn options(&self, _req: Request<Body>) -> ResponseFuture {
Box::pin(async { Ok(create_ok_response("{}")) })
}
}
/// V2 API Handler/Wrapper for foreign functions
pub struct ForeignAPIHandlerV2<L, C, K>
where
L: WalletLCProvider<'static, C, K> + 'static,
C: NodeClient + 'static,
K: Keychain + 'static,
{
/// Wallet instance
pub wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K> + 'static>>>,
/// Keychain mask
pub keychain_mask: Arc<Mutex<Option<SecretKey>>>,
/// run in doctest mode
pub test_mode: bool,
/// tor config
pub tor_config: Mutex<Option<TorConfig>>,
}
impl<L, C, K> ForeignAPIHandlerV2<L, C, K>
where
L: WalletLCProvider<'static, C, K> + 'static,
C: NodeClient + 'static,
K: Keychain + 'static,
{
/// 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: Arc<Mutex<Option<SecretKey>>>,
test_mode: bool,
tor_config: Mutex<Option<TorConfig>>,
) -> ForeignAPIHandlerV2<L, C, K> {
ForeignAPIHandlerV2 {
wallet,
keychain_mask,
test_mode,
tor_config,
}
}
async fn call_api(
req: Request<Body>,
api: Foreign<'static, L, C, K>,
) -> Result<serde_json::Value, Error> {
let val: serde_json::Value = parse_body(req).await?;
match <dyn ForeignRpc>::handle_request(&api, val) {
MaybeReply::Reply(r) => Ok(r),
MaybeReply::DontReply => {
// Since it's http, we need to return something. We return [] because jsonrpc
// clients will parse it as an empty batch response.
Ok(serde_json::json!([]))
}
}
}
async fn handle_post_request(
req: Request<Body>,
mask: Option<SecretKey>,
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K> + 'static>>>,
test_mode: bool,
tor_config: Option<TorConfig>,
) -> Result<Response<Body>, Error> {
let api = Foreign::new(wallet, mask, Some(check_middleware), test_mode);
api.set_tor_config(tor_config);
let res = Self::call_api(req, api).await?;
Ok(json_response_pretty(&res))
}
}
impl<L, C, K> api::Handler for ForeignAPIHandlerV2<L, C, K>
where
L: WalletLCProvider<'static, C, K> + 'static,
C: NodeClient + 'static,
K: Keychain + 'static,
{
fn post(&self, req: Request<Body>) -> ResponseFuture {
let mask = self.keychain_mask.lock().clone();
let wallet = self.wallet.clone();
let test_mode = self.test_mode;
let tor_config = self.tor_config.lock().clone();
Box::pin(async move {
match Self::handle_post_request(req, mask, wallet, test_mode, tor_config).await {
Ok(v) => Ok(v),
Err(e) => {
error!("Request Error: {:?}", e);
Ok(create_error_response(e))
}
}
})
}
fn options(&self, _req: Request<Body>) -> ResponseFuture {
Box::pin(async { Ok(create_ok_response("{}")) })
}
}
// Utility to serialize a struct into JSON and produce a sensible Response
// out of it.
fn _json_response<T>(s: &T) -> Response<Body>
where
T: Serialize,
{
match serde_json::to_string(s) {
Ok(json) => response(StatusCode::OK, json),
Err(_) => response(StatusCode::INTERNAL_SERVER_ERROR, ""),
}
}
// pretty-printed version of above
fn json_response_pretty<T>(s: &T) -> Response<Body>
where
T: Serialize,
{
match serde_json::to_string_pretty(s) {
Ok(json) => response(StatusCode::OK, json),
Err(_) => response(StatusCode::INTERNAL_SERVER_ERROR, ""),
}
}
fn create_error_response(e: Error) -> Response<Body> {
Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.header("access-control-allow-origin", "*")
.header(
"access-control-allow-headers",
"Content-Type, Authorization",
)
.body(format!("{}", e).into())
.unwrap()
}
fn create_ok_response(json: &str) -> Response<Body> {
Response::builder()
.status(StatusCode::OK)
.header("access-control-allow-origin", "*")
.header(
"access-control-allow-headers",
"Content-Type, Authorization",
)
.header(hyper::header::CONTENT_TYPE, "application/json")
.body(json.to_string().into())
.unwrap()
}
/// Build a new hyper Response with the status code and body provided.
///
/// Whenever the status code is `StatusCode::OK` the text parameter should be
/// valid JSON as the content type header will be set to `application/json'
fn response<T: Into<Body>>(status: StatusCode, text: T) -> Response<Body> {
let mut builder = Response::builder()
.status(status)
.header("access-control-allow-origin", "*")
.header(
"access-control-allow-headers",
"Content-Type, Authorization",
);
if status == StatusCode::OK {
builder = builder.header(hyper::header::CONTENT_TYPE, "application/json");
}
builder.body(text.into()).unwrap()
}
async fn parse_body<T>(req: Request<Body>) -> Result<T, Error>
where
for<'de> T: Deserialize<'de> + Send + 'static,
{
let body = body::to_bytes(req.into_body())
.await
.map_err(|_| Error::GenericError("Failed to read request".to_string()))?;
serde_json::from_reader(&body[..])
.map_err(|e| Error::GenericError(format!("Invalid request body: {}", e)))
}
-630
View File
@@ -1,630 +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 crate::core::core::FeeFields;
use crate::core::core::{self, amount_to_hr_string};
use crate::core::global;
use crate::libwallet::{
AcctPathMapping, Error, OutputCommitMapping, OutputStatus, TxLogEntry, ViewWallet, WalletInfo,
};
use crate::util::ToHex;
use grin_wallet_util::OnionV3Address;
use prettytable;
use std::io::prelude::Write;
use term;
/// Display outputs in a pretty way
pub fn outputs(
account: &str,
cur_height: u64,
validated: bool,
outputs: Vec<OutputCommitMapping>,
dark_background_color_scheme: bool,
) -> Result<(), Error> {
let title = format!(
"Wallet Outputs - Account '{}' - Block Height: {}",
account, cur_height
);
println!();
if term::stdout().is_none() {
println!("Could not open terminal");
return Ok(());
}
let mut t = term::stdout().unwrap();
t.fg(term::color::MAGENTA).unwrap();
writeln!(t, "{}", title).unwrap();
t.reset().unwrap();
let mut table = table!();
table.set_titles(row![
bMG->"Output Commitment",
bMG->"MMR Index",
bMG->"Block Height",
bMG->"Locked Until",
bMG->"Status",
bMG->"Coinbase?",
bMG->"# Confirms",
bMG->"Value",
bMG->"Tx"
]);
for m in outputs {
let commit = format!("{}", m.commit.as_ref().to_hex());
let index = match m.output.mmr_index {
None => "None".to_owned(),
Some(t) => t.to_string(),
};
let height = format!("{}", m.output.height);
let lock_height = format!("{}", m.output.lock_height);
let is_coinbase = format!("{}", m.output.is_coinbase);
// Mark unconfirmed coinbase outputs as "Mining" instead of "Unconfirmed"
let status = match m.output.status {
OutputStatus::Unconfirmed if m.output.is_coinbase => "Mining".to_string(),
_ => format!("{}", m.output.status),
};
let num_confirmations = format!("{}", m.output.num_confirmations(cur_height));
let value = format!("{}", core::amount_to_hr_string(m.output.value, false));
let tx = match m.output.tx_log_entry {
None => "".to_owned(),
Some(t) => t.to_string(),
};
if dark_background_color_scheme {
table.add_row(row![
bFC->commit,
bFB->index,
bFB->height,
bFB->lock_height,
bFR->status,
bFY->is_coinbase,
bFB->num_confirmations,
bFG->value,
bFC->tx,
]);
} else {
table.add_row(row![
bFD->commit,
bFB->index,
bFB->height,
bFB->lock_height,
bFR->status,
bFD->is_coinbase,
bFB->num_confirmations,
bFG->value,
bFD->tx,
]);
}
}
table.set_format(*prettytable::format::consts::FORMAT_NO_COLSEP);
table.printstd();
println!();
if !validated {
println!(
"\nWARNING: Wallet failed to verify data. \
The above is from local cache and possibly invalid! \
(is your `grin server` offline or broken?)"
);
}
Ok(())
}
/// Display transaction log in a pretty way
pub fn txs(
account: &str,
cur_height: u64,
validated: bool,
txs: &[TxLogEntry],
include_status: bool,
dark_background_color_scheme: bool,
) -> Result<(), Error> {
let title = format!(
"Transaction Log - Account '{}' - Block Height: {}",
account, cur_height
);
println!();
if term::stdout().is_none() {
println!("Could not open terminal");
return Ok(());
}
let mut t = term::stdout().unwrap();
t.fg(term::color::MAGENTA).unwrap();
writeln!(t, "{}", title).unwrap();
t.reset().unwrap();
let mut table = table!();
table.set_titles(row![
bMG->"Id",
bMG->"Type",
bMG->"Shared Transaction Id",
bMG->"Creation Time",
bMG->"TTL Cutoff Height",
bMG->"Confirmed?",
bMG->"Confirmation Time",
bMG->"Num. \nInputs",
bMG->"Num. \nOutputs",
bMG->"Amount \nCredited",
bMG->"Amount \nDebited",
bMG->"Fee",
bMG->"Net \nDifference",
bMG->"Payment \nProof",
bMG->"Kernel",
bMG->"Tx \nData",
]);
for t in txs {
let id = format!("{}", t.id);
let slate_id = match t.tx_slate_id {
Some(m) => format!("{}", m),
None => "None".to_owned(),
};
let entry_type = format!("{}", t.tx_type);
let creation_ts = format!("{}", t.creation_ts.format("%Y-%m-%d %H:%M:%S"));
let ttl_cutoff_height = match t.ttl_cutoff_height {
Some(b) => format!("{}", b),
None => "None".to_owned(),
};
let confirmation_ts = match t.confirmation_ts {
Some(m) => format!("{}", m.format("%Y-%m-%d %H:%M:%S")),
None => "None".to_owned(),
};
let confirmed = format!("{}", t.confirmed);
let num_inputs = format!("{}", t.num_inputs);
let num_outputs = format!("{}", t.num_outputs);
let amount_debited_str = core::amount_to_hr_string(t.amount_debited, true);
let amount_credited_str = core::amount_to_hr_string(t.amount_credited, true);
let fee = match t.fee {
Some(f) => format!("{}", core::amount_to_hr_string(f.fee(), true)),
None => "None".to_owned(),
};
let net_diff = if t.amount_credited >= t.amount_debited {
core::amount_to_hr_string(t.amount_credited - t.amount_debited, true)
} else {
format!(
"-{}",
core::amount_to_hr_string(t.amount_debited - t.amount_credited, true)
)
};
let tx_data = match t.stored_tx {
Some(_) => "Yes".to_owned(),
None => "None".to_owned(),
};
let kernel_excess = match t.kernel_excess {
Some(e) => {
let excess: &[u8] = e.0.as_ref();
excess.to_hex()
}
None => "None".to_owned(),
};
let payment_proof = match t.payment_proof {
Some(_) => "Yes".to_owned(),
None => "None".to_owned(),
};
if dark_background_color_scheme {
table.add_row(row![
bFC->id,
bFC->entry_type,
bFC->slate_id,
bFB->creation_ts,
bFB->ttl_cutoff_height,
bFC->confirmed,
bFB->confirmation_ts,
bFC->num_inputs,
bFC->num_outputs,
bFG->amount_credited_str,
bFR->amount_debited_str,
bFR->fee,
bFY->net_diff,
bfG->payment_proof,
bFB->kernel_excess,
bFb->tx_data,
]);
} else {
if t.confirmed {
table.add_row(row![
bFD->id,
bFb->entry_type,
bFD->slate_id,
bFB->creation_ts,
bFg->confirmed,
bFB->confirmation_ts,
bFD->num_inputs,
bFD->num_outputs,
bFG->amount_credited_str,
bFD->amount_debited_str,
bFD->fee,
bFG->net_diff,
bfG->payment_proof,
bFB->kernel_excess,
bFB->tx_data,
]);
} else {
table.add_row(row![
bFD->id,
bFb->entry_type,
bFD->slate_id,
bFB->creation_ts,
bFR->confirmed,
bFB->confirmation_ts,
bFD->num_inputs,
bFD->num_outputs,
bFG->amount_credited_str,
bFD->amount_debited_str,
bFD->fee,
bFG->net_diff,
bfG->payment_proof,
bFB->kernel_excess,
bFB->tx_data,
]);
}
}
}
table.set_format(*prettytable::format::consts::FORMAT_NO_COLSEP);
table.printstd();
println!();
if !validated && include_status {
println!(
"\nWARNING: Wallet failed to verify data. \
The above is from local cache and possibly invalid! \
(is your `grin server` offline or broken?)"
);
}
Ok(())
}
pub fn view_wallet_balance(w: ViewWallet, cur_height: u64, dark_background_color_scheme: bool) {
println!(
"\n____ View Wallet Summary Info - Block Height: {} ____\n Rewind Hash - {}\n",
cur_height, w.rewind_hash
);
let mut table = table!();
if dark_background_color_scheme {
table.add_row(row![
bFG->"Total Balance",
FG->amount_to_hr_string(w.total_balance, false)
]);
} else {
table.add_row(row![
bFG->"Total Balance",
FG->amount_to_hr_string(w.total_balance, false)
]);
};
table.set_format(*prettytable::format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
table.printstd();
println!();
}
pub fn view_wallet_output(
view_wallet: ViewWallet,
cur_height: u64,
dark_background_color_scheme: bool,
) -> Result<(), Error> {
println!();
let title = format!("View Wallet Outputs - Block Height: {}", cur_height);
if term::stdout().is_none() {
println!("Could not open terminal");
return Ok(());
}
let mut t = term::stdout().unwrap();
t.fg(term::color::MAGENTA).unwrap();
writeln!(t, "{}", title).unwrap();
t.reset().unwrap();
let mut table = table!();
table.set_titles(row![
bMG->"Output Commitment",
bMG->"MMR Index",
bMG->"Block Height",
bMG->"Locked Until",
bMG->"Coinbase?",
bMG->"# Confirms",
bMG->"Value",
]);
for m in view_wallet.output_result {
let commit = format!("{}", m.commit);
let index = m.mmr_index;
let height = format!("{}", m.height);
let lock_height = format!("{}", m.lock_height);
let is_coinbase = format!("{}", m.is_coinbase);
let num_confirmations = format!("{}", m.num_confirmations(cur_height));
let value = format!("{}", core::amount_to_hr_string(m.value, false));
if dark_background_color_scheme {
table.add_row(row![
bFC->commit,
bFB->index,
bFB->height,
bFB->lock_height,
bFY->is_coinbase,
bFB->num_confirmations,
bFG->value,
]);
} else {
table.add_row(row![
bFD->commit,
bFB->index,
bFB->height,
bFB->lock_height,
bFD->is_coinbase,
bFB->num_confirmations,
bFG->value,
]);
}
}
table.set_format(*prettytable::format::consts::FORMAT_NO_COLSEP);
table.printstd();
println!();
Ok(())
}
/// Display summary info in a pretty way
pub fn info(
account: &str,
wallet_info: &WalletInfo,
validated: bool,
dark_background_color_scheme: bool,
) {
println!(
"\n____ Wallet Summary Info - Account '{}' as of height {} ____\n",
account, wallet_info.last_confirmed_height,
);
let mut table = table!();
if dark_background_color_scheme {
table.add_row(row![
bFG->"Confirmed Total",
FG->amount_to_hr_string(wallet_info.total, false)
]);
if wallet_info.amount_reverted > 0 {
table.add_row(row![
Fr->format!("Reverted"),
Fr->amount_to_hr_string(wallet_info.amount_reverted, false)
]);
}
// Only dispay "Immature Coinbase" if we have related outputs in the wallet.
// This row just introduces confusion if the wallet does not receive coinbase rewards.
if wallet_info.amount_immature > 0 {
table.add_row(row![
bFY->format!("Immature Coinbase (< {})", global::coinbase_maturity()),
FY->amount_to_hr_string(wallet_info.amount_immature, false)
]);
}
table.add_row(row![
bFY->format!("Awaiting Confirmation (< {})", wallet_info.minimum_confirmations),
FY->amount_to_hr_string(wallet_info.amount_awaiting_confirmation, false)
]);
table.add_row(row![
bFB->format!("Awaiting Finalization"),
FB->amount_to_hr_string(wallet_info.amount_awaiting_finalization, false)
]);
table.add_row(row![
Fr->"Locked by previous transaction",
Fr->amount_to_hr_string(wallet_info.amount_locked, false)
]);
table.add_row(row![
Fw->"--------------------------------",
Fw->"-------------"
]);
table.add_row(row![
bFG->"Currently Spendable",
FG->amount_to_hr_string(wallet_info.amount_currently_spendable, false)
]);
} else {
table.add_row(row![
bFG->"Total",
FG->amount_to_hr_string(wallet_info.total, false)
]);
if wallet_info.amount_reverted > 0 {
table.add_row(row![
Fr->format!("Reverted"),
Fr->amount_to_hr_string(wallet_info.amount_reverted, false)
]);
}
// Only dispay "Immature Coinbase" if we have related outputs in the wallet.
// This row just introduces confusion if the wallet does not receive coinbase rewards.
if wallet_info.amount_immature > 0 {
table.add_row(row![
bFB->format!("Immature Coinbase (< {})", global::coinbase_maturity()),
FB->amount_to_hr_string(wallet_info.amount_immature, false)
]);
}
table.add_row(row![
bFB->format!("Awaiting Confirmation (< {})", wallet_info.minimum_confirmations),
FB->amount_to_hr_string(wallet_info.amount_awaiting_confirmation, false)
]);
table.add_row(row![
Fr->"Locked by previous transaction",
Fr->amount_to_hr_string(wallet_info.amount_locked, false)
]);
table.add_row(row![
Fw->"--------------------------------",
Fw->"-------------"
]);
table.add_row(row![
bFG->"Currently Spendable",
FG->amount_to_hr_string(wallet_info.amount_currently_spendable, false)
]);
};
table.set_format(*prettytable::format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
table.printstd();
println!();
if !validated {
println!(
"\nWARNING: Wallet failed to verify data against a live chain. \
The above is from local cache and only valid up to the given height! \
(is your `grin server` offline or broken?)"
);
}
}
/// Display summary info in a pretty way
pub fn estimate(
amount: u64,
strategies: Vec<(
&str, // strategy
u64, // total amount to be locked
FeeFields, // fee
)>,
dark_background_color_scheme: bool,
) {
println!(
"\nEstimation for sending {}:\n",
amount_to_hr_string(amount, false)
);
let mut table = table!();
table.set_titles(row![
bMG->"Selection strategy",
bMG->"Fee",
bMG->"Will be locked",
]);
for (strategy, total, fee_fields) in strategies {
if dark_background_color_scheme {
table.add_row(row![
bFC->strategy,
FR->amount_to_hr_string(fee_fields.fee(), false), // apply fee mask past HF4
FY->amount_to_hr_string(total, false),
]);
} else {
table.add_row(row![
bFD->strategy,
FR->amount_to_hr_string(fee_fields.fee(), false), // apply fee mask past HF4
FY->amount_to_hr_string(total, false),
]);
}
}
table.printstd();
println!();
}
/// Display list of wallet accounts in a pretty way
pub fn accounts(acct_mappings: Vec<AcctPathMapping>) {
println!("\n____ Wallet Accounts ____\n",);
let mut table = table!();
table.set_titles(row![
mMG->"Name",
bMG->"Parent BIP-32 Derivation Path",
]);
for m in acct_mappings {
table.add_row(row![
bFC->m.label,
bGC->m.path.to_bip_32_string(),
]);
}
table.set_format(*prettytable::format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
table.printstd();
println!();
}
/// Display individual Payment Proof
pub fn payment_proof(tx: &TxLogEntry) -> Result<(), Error> {
let title = format!("Payment Proof - Transaction '{}'", tx.id,);
println!();
if term::stdout().is_none() {
println!("Could not open terminal");
return Ok(());
}
let mut t = term::stdout().unwrap();
t.fg(term::color::MAGENTA).unwrap();
writeln!(t, "{}", title).unwrap();
t.reset().unwrap();
let pp = match &tx.payment_proof {
None => {
writeln!(t, "None").unwrap();
t.reset().unwrap();
return Ok(());
}
Some(p) => p.clone(),
};
t.fg(term::color::WHITE).unwrap();
writeln!(t).unwrap();
let receiver_signature = match pp.receiver_signature {
Some(s) => {
let sig_bytes = s.to_bytes();
let sig_ref: &[u8] = sig_bytes.as_ref();
sig_ref.to_hex()
}
None => "None".to_owned(),
};
let fee = match tx.fee {
Some(f) => f.fee(), // apply fee mask past HF4
None => 0,
};
let amount = if tx.amount_credited >= tx.amount_debited {
core::amount_to_hr_string(tx.amount_credited - tx.amount_debited, true)
} else {
format!(
"{}",
core::amount_to_hr_string(tx.amount_debited - tx.amount_credited - fee, true)
)
};
let sender_signature = match pp.sender_signature {
Some(s) => {
let sig_bytes = s.to_bytes();
let sig_ref: &[u8] = sig_bytes.as_ref();
sig_ref.to_hex()
}
None => "None".to_owned(),
};
let kernel_excess = match tx.kernel_excess {
Some(e) => {
let excess: &[u8] = e.0.as_ref();
excess.to_hex()
}
None => "None".to_owned(),
};
writeln!(
t,
"Receiver Address: {}",
OnionV3Address::from_bytes(pp.receiver_address.to_bytes())
)
.unwrap();
writeln!(t, "Receiver Signature: {}", receiver_signature).unwrap();
writeln!(t, "Amount: {}", amount).unwrap();
writeln!(t, "Kernel Excess: {}", kernel_excess).unwrap();
writeln!(
t,
"Sender Address: {}",
OnionV3Address::from_bytes(pp.sender_address.to_bytes())
)
.unwrap();
writeln!(t, "Sender Signature: {}", sender_signature).unwrap();
t.reset().unwrap();
println!();
Ok(())
}
-105
View File
@@ -1,105 +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.
//! Implementation specific error types
use crate::api;
use crate::core::core::transaction;
use crate::core::libtx;
use crate::impls;
use crate::keychain;
use crate::libwallet;
/// Wallet errors, mostly wrappers around underlying crypto or I/O errors.
#[derive(Clone, Eq, PartialEq, Debug, thiserror::Error)]
pub enum Error {
/// LibTX Error
#[error("LibTx Error")]
LibTX(#[from] libtx::Error),
/// Impls error
#[error("Impls Error")]
Impls(#[from] impls::Error),
/// LibWallet Error
#[error("LibWallet Error: {0}")]
LibWallet(#[from] libwallet::Error),
/// Keychain error
#[error("Keychain error")]
Keychain(#[from] keychain::Error),
/// Transaction Error
#[error("Transaction error")]
Transaction(#[from] transaction::Error),
/// Secp Error
#[error("Secp error")]
Secp,
/// Filewallet error
#[error("Wallet data error: {0}")]
FileWallet(&'static str),
/// Error when formatting json
#[error("IO error")]
IO,
/// Error when formatting json
#[error("Serde JSON error")]
Format,
/// Error when contacting a node through its API
#[error("Node API error")]
Node(#[from] api::Error),
/// Error originating from hyper.
#[error("Hyper error")]
Hyper,
/// Error originating from hyper uri parsing.
#[error("Uri parsing error")]
Uri,
/// Attempt to use duplicate transaction id in separate transactions
#[error("Duplicate transaction ID error")]
DuplicateTransactionId,
/// Wallet seed already exists
#[error("Wallet seed file exists: {0}")]
WalletSeedExists(String),
/// Wallet seed doesn't exist
#[error("Wallet seed doesn't exist error")]
WalletSeedDoesntExist,
/// Enc/Decryption Error
#[error("Enc/Decryption error (check password?)")]
Encryption,
/// BIP 39 word list
#[error("BIP39 Mnemonic (word list) Error")]
Mnemonic,
/// Command line argument error
#[error("{0}")]
ArgumentError(String),
/// Other
#[error("Listener Startup Error")]
ListenerError,
/// Other
#[error("Generic error: {0}")]
GenericError(String),
}
-38
View File
@@ -1,38 +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.
//! Library module for the main wallet functionalities provided by Grin.
#[macro_use]
extern crate prettytable;
#[macro_use]
extern crate log;
#[macro_use]
extern crate lazy_static;
use grin_api as api;
use grin_core as core;
use grin_keychain as keychain;
use grin_util as util;
use grin_wallet_api as apiwallet;
use grin_wallet_config as config;
use grin_wallet_impls as impls;
use grin_wallet_libwallet as libwallet;
pub mod command;
pub mod controller;
pub mod display;
mod error;
pub use crate::error::Error;
-276
View File
@@ -1,276 +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.
//! tests differing accounts in the same wallet
#[macro_use]
extern crate log;
extern crate grin_wallet_controller as wallet;
extern crate grin_wallet_impls as impls;
use grin_core as core;
use grin_keychain as keychain;
use self::core::global;
use self::keychain::{ExtKeychain, Keychain};
use grin_wallet_libwallet as libwallet;
use impls::test_framework::{self, LocalWalletClient};
use libwallet::InitTxArgs;
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
#[macro_use]
mod common;
use common::{clean_output_dir, create_wallet_proxy, setup};
/// Various tests on accounts within the same wallet
fn accounts_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// Create a new proxy to simulate server and wallet responses
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();
create_wallet_and_add!(
client2,
wallet2,
mask2_i,
test_dir,
"wallet2",
None,
&mut wallet_proxy,
false
);
let mask2 = (&mask2_i).as_ref();
// Set the wallet proxy listener running
thread::spawn(move || {
if let Err(e) = wallet_proxy.run() {
error!("Wallet Proxy error: {}", e);
}
});
// few values to keep things shorter
let reward = core::consensus::REWARD;
let cm = global::coinbase_maturity(); // assume all testing precedes soft fork height
// test default accounts exist
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let accounts = api.accounts(m)?;
assert_eq!(accounts[0].label, "default");
assert_eq!(accounts[0].path, ExtKeychain::derive_key_id(2, 0, 0, 0, 0));
Ok(())
})?;
// add some accounts
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let new_path = api.create_account_path(m, "account1").unwrap();
assert_eq!(new_path, ExtKeychain::derive_key_id(2, 1, 0, 0, 0));
let new_path = api.create_account_path(m, "account2").unwrap();
assert_eq!(new_path, ExtKeychain::derive_key_id(2, 2, 0, 0, 0));
let new_path = api.create_account_path(m, "account3").unwrap();
assert_eq!(new_path, ExtKeychain::derive_key_id(2, 3, 0, 0, 0));
// trying to add same label again should fail
let res = api.create_account_path(m, "account1");
assert!(res.is_err());
Ok(())
})?;
// add account to wallet 2
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
let new_path = api.create_account_path(m, "listener_account").unwrap();
assert_eq!(new_path, ExtKeychain::derive_key_id(2, 1, 0, 0, 0));
Ok(())
})?;
// Default wallet 2 to listen on that account
{
wallet_inst!(wallet2, w);
w.set_parent_key_id_by_name("listener_account")?;
}
// Mine into two different accounts in the same wallet
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("account1")?;
assert_eq!(w.parent_key_id(), ExtKeychain::derive_key_id(2, 1, 0, 0, 0));
}
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 7, false);
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("account2")?;
assert_eq!(w.parent_key_id(), ExtKeychain::derive_key_id(2, 2, 0, 0, 0));
}
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 5, false);
// Should have 5 in account1 (5 spendable), 5 in account (2 spendable)
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
assert!(wallet1_refreshed);
assert_eq!(wallet1_info.last_confirmed_height, 12);
assert_eq!(wallet1_info.total, 5 * reward);
assert_eq!(wallet1_info.amount_currently_spendable, (5 - cm) * reward);
// check tx log as well
let (_, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert_eq!(txs.len(), 5);
Ok(())
})?;
// now check second account
{
// let mut w_lock = wallet1.lock();
// let lc = w_lock.lc_provider()?;
// let w = lc.wallet_inst()?;
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("account1")?;
}
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
// check last confirmed height on this account is different from above (should be 0)
let (_, wallet1_info) = api.retrieve_summary_info(m, false, 1)?;
assert_eq!(wallet1_info.last_confirmed_height, 0);
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
assert!(wallet1_refreshed);
assert_eq!(wallet1_info.last_confirmed_height, 12);
assert_eq!(wallet1_info.total, 7 * reward);
assert_eq!(wallet1_info.amount_currently_spendable, 7 * reward);
// check tx log as well
let (_, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert_eq!(txs.len(), 7);
Ok(())
})?;
// should be nothing in default account
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("default")?;
}
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (_, wallet1_info) = api.retrieve_summary_info(m, false, 1)?;
assert_eq!(wallet1_info.last_confirmed_height, 0);
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
assert!(wallet1_refreshed);
assert_eq!(wallet1_info.last_confirmed_height, 12);
assert_eq!(wallet1_info.total, 0,);
assert_eq!(wallet1_info.amount_currently_spendable, 0,);
// check tx log as well
let (_, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert_eq!(txs.len(), 0);
Ok(())
})?;
// Send a tx to another wallet
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("account1")?;
}
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let args = InitTxArgs {
src_acct_name: None,
amount: reward,
minimum_confirmations: 2,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: true,
..Default::default()
};
let mut slate = api.init_send_tx(m, args)?;
slate = client1.send_tx_slate_direct("wallet2", &slate)?;
api.tx_lock_outputs(m, &slate)?;
slate = api.finalize_tx(m, &slate)?;
api.post_tx(m, &slate, false)?;
Ok(())
})?;
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
assert!(wallet1_refreshed);
assert_eq!(wallet1_info.last_confirmed_height, 13);
let (_, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert_eq!(txs.len(), 9);
Ok(())
})?;
// other account should be untouched
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("account2")?;
}
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (_, wallet1_info) = api.retrieve_summary_info(m, false, 1)?;
assert_eq!(wallet1_info.last_confirmed_height, 12);
let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
assert_eq!(wallet1_info.last_confirmed_height, 13);
let (_, txs) = api.retrieve_txs(m, true, None, None, None)?;
println!("{:?}", txs);
assert_eq!(txs.len(), 5);
Ok(())
})?;
// wallet 2 should only have this tx on the listener account
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?;
assert!(wallet2_refreshed);
assert_eq!(wallet2_info.last_confirmed_height, 13);
let (_, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert_eq!(txs.len(), 1);
Ok(())
})?;
// Default account on wallet 2 should be untouched
{
wallet_inst!(wallet2, w);
w.set_parent_key_id_by_name("default")?;
}
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
let (_, wallet2_info) = api.retrieve_summary_info(m, false, 1)?;
assert_eq!(wallet2_info.last_confirmed_height, 0);
let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?;
assert!(wallet2_refreshed);
assert_eq!(wallet2_info.last_confirmed_height, 13);
assert_eq!(wallet2_info.total, 0,);
assert_eq!(wallet2_info.amount_currently_spendable, 0,);
// check tx log as well
let (_, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert_eq!(txs.len(), 0);
Ok(())
})?;
// let logging finish
stopper.store(false, Ordering::Relaxed);
thread::sleep(Duration::from_millis(200));
Ok(())
}
#[test]
fn accounts() {
let test_dir = "test_output/accounts";
setup(test_dir);
if let Err(e) = accounts_test_impl(test_dir) {
panic!("Libwallet Error: {}", e);
}
clean_output_dir(test_dir);
}
-171
View File
@@ -1,171 +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.
//! tests whose only purpose is to build up a 'real' looking chain with
//! actual transactions for testing purposes
#[macro_use]
extern crate log;
extern crate grin_wallet_controller as wallet;
extern crate grin_wallet_impls as impls;
extern crate grin_wallet_libwallet as libwallet;
use grin_core as core;
use self::libwallet::{InitTxArgs, Slate};
use impls::test_framework::{self, LocalWalletClient};
use rand::Rng;
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
mod common;
use common::{clean_output_dir, create_wallet_proxy, setup};
/// Builds a chain with real transactions up to the given height
fn build_chain(test_dir: &'static str, block_height: usize) -> Result<(), libwallet::Error> {
// Create a new proxy to simulate server and wallet responses
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,
true
);
let mask1 = (&mask1_i).as_ref();
debug!("Mask1: {:?}", mask1);
create_wallet_and_add!(
client2,
wallet2,
mask2_i,
test_dir,
"wallet2",
None,
&mut wallet_proxy,
false
);
let mask2 = (&mask2_i).as_ref();
debug!("Mask2: {:?}", mask2);
// Set the wallet proxy listener running
thread::spawn(move || {
if let Err(e) = wallet_proxy.run() {
error!("Wallet Proxy error: {}", e);
}
});
// Stop the scanning updater threads because it extends the time needed to build the chain
// exponentially
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, _m| {
api.stop_updater()?;
Ok(())
})?;
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, _m| {
api.stop_updater()?;
Ok(())
})?;
// few values to keep things shorter
let reward = core::consensus::REWARD;
let mut rng = rand::thread_rng();
// Start off with a few blocks
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false);
for _ in 0..block_height {
let mut wallet_1_has_funds = false;
// Check wallet 1 contents
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
debug!(
"Wallet 1 spendable - {}",
wallet1_info.amount_currently_spendable
);
if wallet1_info.amount_currently_spendable > reward {
wallet_1_has_funds = true;
}
Ok(())
})?;
// let's say 1 in every 3 blocks has a transaction (i.e. random 0 here and wallet1 has funds)
let transact = rng.gen_range(0, 2) == 0;
if !transact || !wallet_1_has_funds {
let _ =
test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 1, false);
continue;
}
// send a random tx or three
let num_txs = rng.gen_range(0, 3);
for _ in 0..num_txs {
let amount: u64 = rng.gen_range(1, 10_000_000_001);
let mut slate = Slate::blank(1, false);
debug!("Creating TX for {}", amount);
wallet::controller::owner_single_use(
Some(wallet1.clone()),
mask1,
None,
|sender_api, m| {
// note this will increment the block count as part of the transaction "Posting"
let args = InitTxArgs {
src_acct_name: None,
amount: amount,
minimum_confirmations: 1,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: false,
..Default::default()
};
let slate_i = sender_api.init_send_tx(m, args)?;
slate = client1.send_tx_slate_direct("wallet2", &slate_i)?;
sender_api.tx_lock_outputs(m, &slate)?;
slate = sender_api.finalize_tx(m, &slate)?;
Ok(())
},
)?;
}
}
// let logging finish
stopper.store(false, Ordering::Relaxed);
thread::sleep(Duration::from_millis(200));
Ok(())
}
#[test]
#[ignore]
fn build_chain_to_height() {
// ******************
// If letting this run for a while to build a chain, recommend also tweaking scan threshold around 1112 of owner.rs:
// ***
// let start_index = last_scanned_block.height.saturating_sub(1);
// ***
// TODO: Make this parameter somehow
// ******************
let test_dir = "test_output/build_chain";
clean_output_dir(test_dir);
setup(test_dir);
if let Err(e) = build_chain(test_dir, 2048) {
panic!("Libwallet Error: {}", e);
}
// don't clean to get the result for testing
}
-101
View File
@@ -1,101 +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.
#[macro_use]
extern crate log;
extern crate grin_wallet_controller as wallet;
extern crate grin_wallet_impls as impls;
extern crate grin_wallet_util;
use grin_core::core::OutputFeatures;
use grin_keychain::{
mnemonic, BlindingFactor, ExtKeychain, ExtKeychainPath, Keychain, SwitchCommitmentType,
};
use grin_util::{secp, ZeroingString};
use grin_wallet_libwallet as libwallet;
use impls::test_framework::LocalWalletClient;
use rand::{thread_rng, Rng};
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
#[macro_use]
mod common;
use common::{clean_output_dir, create_wallet_proxy, setup};
fn build_output_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// Generate seed so we can verify the blinding factor is derived correctly
let seed: [u8; 32] = thread_rng().gen();
let keychain = ExtKeychain::from_seed(&seed, false).unwrap();
let mnemonic = mnemonic::from_entropy(&seed).unwrap();
// Create a new proxy to simulate server and wallet responses
let mut wallet_proxy = create_wallet_proxy(test_dir);
let stopper = wallet_proxy.running.clone();
create_wallet_and_add!(
client1,
wallet1,
mask1_i,
test_dir,
"wallet1",
Some(ZeroingString::from(mnemonic)),
&mut wallet_proxy,
false
);
let mask1 = (&mask1_i).as_ref();
// Set the wallet proxy listener running
thread::spawn(move || {
if let Err(e) = wallet_proxy.run() {
error!("Wallet Proxy error: {}", e);
}
});
let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit);
let features = OutputFeatures::Plain;
let amount = 60_000_000_000;
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| {
let built_output = sender_api.build_output(m, features, amount)?;
let key_id = built_output.key_id;
assert_eq!(key_id.to_path(), ExtKeychainPath::new(3, 0, 0, 0, 0));
let blind = built_output.blind;
let key = keychain.derive_key(amount, &key_id, SwitchCommitmentType::Regular)?;
assert_eq!(blind, BlindingFactor::from_secret_key(key.clone()));
let output = built_output.output;
assert_eq!(output.features(), features);
assert_eq!(output.commitment(), secp.commit(amount, key)?);
output.verify_proof()?;
Ok(())
})?;
// let logging finish
stopper.store(false, Ordering::Relaxed);
thread::sleep(Duration::from_millis(200));
Ok(())
}
#[test]
fn build_output() {
let test_dir = "test_output/build_output";
setup(test_dir);
if let Err(e) = build_output_test_impl(test_dir) {
panic!("Libwallet Error: {}", e);
}
clean_output_dir(test_dir);
}
-878
View File
@@ -1,878 +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.
//! tests differing accounts in the same wallet
#[macro_use]
extern crate log;
extern crate grin_wallet_controller as wallet;
extern crate grin_wallet_impls as impls;
use grin_core as core;
use grin_util as util;
use self::core::consensus;
use self::core::global;
use grin_wallet_libwallet as libwallet;
use impls::test_framework::{self, LocalWalletClient};
use impls::{PathToSlate, SlatePutter as _};
use libwallet::{InitTxArgs, NodeClient};
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
use util::ZeroingString;
#[macro_use]
mod common;
use common::{clean_output_dir, create_wallet_proxy, setup};
macro_rules! send_to_dest {
($a:expr, $m: expr, $b:expr, $c:expr, $d:expr) => {
test_framework::send_to_dest($a, $m, $b, $c, $d, false)
};
}
macro_rules! wallet_info {
($a:expr, $m:expr) => {
test_framework::wallet_info($a, $m)
};
}
/// Various tests on checking functionality
fn scan_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// Create a new proxy to simulate server and wallet responses
let mut wallet_proxy = create_wallet_proxy(test_dir);
let chain = wallet_proxy.chain.clone();
let stopper = wallet_proxy.running.clone();
// Create a new wallet test client, and set its queues to communicate with the
// proxy
create_wallet_and_add!(
client1,
wallet1,
mask1_i,
test_dir,
"wallet1",
None,
&mut wallet_proxy,
false
);
let mask1 = (&mask1_i).as_ref();
create_wallet_and_add!(
client2,
wallet2,
mask2_i,
test_dir,
"wallet2",
None,
&mut wallet_proxy,
false
);
let mask2 = (&mask2_i).as_ref();
// Set the wallet proxy listener running
thread::spawn(move || {
if let Err(e) = wallet_proxy.run() {
error!("Wallet Proxy error: {}", e);
}
});
// few values to keep things shorter
let reward = core::consensus::REWARD;
let cm = global::coinbase_maturity() as u64; // assume all testing precedes soft fork height
// add some accounts
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
api.create_account_path(m, "named_account_1")?;
api.create_account_path(m, "account_2")?;
api.create_account_path(m, "account_3")?;
api.set_active_account(m, "named_account_1")?;
Ok(())
})?;
// add account to wallet 2
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
api.create_account_path(m, "account_1")?;
api.set_active_account(m, "account_1")?;
Ok(())
})?;
// Do some mining
let bh = 20u64;
let _ =
test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false);
// Sanity check contents
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
assert!(wallet1_refreshed);
assert_eq!(wallet1_info.last_confirmed_height, bh);
assert_eq!(wallet1_info.total, bh * reward);
assert_eq!(wallet1_info.amount_currently_spendable, (bh - cm) * reward);
// check tx log as well
let (_, txs) = api.retrieve_txs(m, true, None, None, None)?;
let (c, _) = libwallet::TxLogEntry::sum_confirmed(&txs);
assert_eq!(wallet1_info.total, c);
assert_eq!(txs.len(), bh as usize);
Ok(())
})?;
// Accidentally delete some outputs
let mut w1_outputs_commits = vec![];
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
w1_outputs_commits = api.retrieve_outputs(m, false, true, None)?.1;
Ok(())
})?;
let w1_outputs: Vec<libwallet::OutputData> =
w1_outputs_commits.into_iter().map(|m| m.output).collect();
{
wallet_inst!(wallet1, w);
{
let mut batch = w.batch(mask1)?;
batch.delete(&w1_outputs[4].key_id, &None)?;
batch.delete(&w1_outputs[10].key_id, &None)?;
let mut accidental_spent = w1_outputs[13].clone();
accidental_spent.status = libwallet::OutputStatus::Spent;
batch.save(accidental_spent)?;
batch.commit()?;
}
}
// check we have a problem now
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
let (_, txs) = api.retrieve_txs(m, true, None, None, None)?;
let (c, _) = libwallet::TxLogEntry::sum_confirmed(&txs);
assert!(wallet1_info.total != c);
Ok(())
})?;
// this should restore our missing outputs
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
api.scan(m, None, true)?;
Ok(())
})?;
// check our outputs match again
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
assert!(wallet1_refreshed);
assert_eq!(wallet1_info.total, bh * reward);
// And check account names haven't been splatted
let accounts = api.accounts(m)?;
assert_eq!(accounts.len(), 4);
assert!(api.set_active_account(m, "account_1").is_err());
assert!(api.set_active_account(m, "named_account_1").is_ok());
Ok(())
})?;
// perform a transaction, but don't let it finish
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
// send to send
let args = InitTxArgs {
src_acct_name: None,
amount: reward * 2,
minimum_confirmations: cm,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: true,
..Default::default()
};
let slate = api.init_send_tx(m, args)?;
// output tx file
let send_file = format!("{}/part_tx_1.tx", test_dir);
PathToSlate(send_file.into()).put_tx(&slate, false)?;
api.tx_lock_outputs(m, &slate)?;
Ok(())
})?;
// check we're all locked
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
assert!(wallet1_refreshed);
assert!(wallet1_info.amount_currently_spendable == 0);
Ok(())
})?;
// unlock/restore
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
api.scan(m, None, true)?;
Ok(())
})?;
// check spendable amount again
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
assert_eq!(wallet1_info.amount_currently_spendable, (bh - cm) * reward);
Ok(())
})?;
// let logging finish
stopper.store(false, Ordering::Relaxed);
thread::sleep(Duration::from_millis(200));
Ok(())
}
fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
let seed_phrase = "affair pistol cancel crush garment candy ancient flag work \
market crush dry stand focus mutual weapon offer ceiling rival turn team spring \
where swift";
let seed_phrase = Some(ZeroingString::from(seed_phrase));
// Create a new proxy to simulate server and wallet responses
let mut wallet_proxy = create_wallet_proxy(test_dir);
let chain = wallet_proxy.chain.clone();
let stopper = wallet_proxy.running.clone();
// Create a new wallet test client, and set its queues to communicate with the
// proxy
create_wallet_and_add!(
m_client,
miner,
miner_mask_i,
test_dir,
"miner",
None,
&mut wallet_proxy,
false
);
let miner_mask = (&miner_mask_i).as_ref();
// non-mining recipient wallets
create_wallet_and_add!(
client1,
wallet1,
mask1_i,
test_dir,
"wallet1",
seed_phrase,
&mut wallet_proxy,
false
);
let mask1 = (&mask1_i).as_ref();
create_wallet_and_add!(
client2,
wallet2,
mask2_i,
test_dir,
"wallet2",
seed_phrase,
&mut wallet_proxy,
false
);
let mask2 = (&mask2_i).as_ref();
// we'll restore into here
create_wallet_and_add!(
client3,
wallet3,
mask3_i,
test_dir,
"wallet3",
seed_phrase,
&mut wallet_proxy,
false
);
let mask3 = (&mask3_i).as_ref();
// also restore into here
create_wallet_and_add!(
client4,
wallet4,
mask4_i,
test_dir,
"wallet4",
seed_phrase,
&mut wallet_proxy,
false
);
let mask4 = (&mask4_i).as_ref();
// Simulate a recover from seed without restore into here
create_wallet_and_add!(
client5,
wallet5,
mask5_i,
test_dir,
"wallet5",
seed_phrase,
&mut wallet_proxy,
false
);
//simulate a recover from seed without restore into here
let mask5 = (&mask5_i).as_ref();
create_wallet_and_add!(
client6,
wallet6,
mask6_i,
test_dir,
"wallet6",
seed_phrase,
&mut wallet_proxy,
false
);
let mask6 = (&mask6_i).as_ref();
create_wallet_and_add!(
client7,
wallet7,
mask7_i,
test_dir,
"wallet7",
seed_phrase,
&mut wallet_proxy,
false
);
let mask7 = (&mask7_i).as_ref();
create_wallet_and_add!(
client8,
wallet8,
mask8_i,
test_dir,
"wallet8",
seed_phrase,
&mut wallet_proxy,
false
);
let mask8 = (&mask8_i).as_ref();
create_wallet_and_add!(
client9,
wallet9,
mask9_i,
test_dir,
"wallet9",
seed_phrase,
&mut wallet_proxy,
false
);
let mask9 = (&mask9_i).as_ref();
create_wallet_and_add!(
client10,
wallet10,
mask10_i,
test_dir,
"wallet10",
seed_phrase,
&mut wallet_proxy,
false
);
let mask10 = (&mask10_i).as_ref();
// Set the wallet proxy listener running
thread::spawn(move || {
if let Err(e) = wallet_proxy.run() {
error!("Wallet Proxy error: {}", e);
}
});
// few values to keep things shorter
let _reward = core::consensus::REWARD;
let cm = global::coinbase_maturity() as usize; // assume all testing precedes soft fork height
// Do some mining
let mut bh = 20u64;
let base_amount = consensus::GRIN_BASE;
let _ = test_framework::award_blocks_to_wallet(
&chain,
miner.clone(),
miner_mask,
bh as usize,
false,
);
// send some funds to wallets 1
send_to_dest!(
miner.clone(),
miner_mask,
m_client.clone(),
"wallet1",
base_amount * 1
)?;
send_to_dest!(
miner.clone(),
miner_mask,
m_client.clone(),
"wallet1",
base_amount * 2
)?;
send_to_dest!(
miner.clone(),
miner_mask,
m_client.clone(),
"wallet1",
base_amount * 3
)?;
bh += 3;
// 0) Check repair when all is okay should leave wallet contents alone
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
api.scan(m, None, true)?;
let info = wallet_info!(wallet1.clone(), m)?;
assert_eq!(info.amount_currently_spendable, base_amount * 6);
assert_eq!(info.total, base_amount * 6);
Ok(())
})?;
// send some funds to wallet 2
send_to_dest!(
miner.clone(),
miner_mask,
m_client.clone(),
"wallet2",
base_amount * 4
)?;
send_to_dest!(
miner.clone(),
miner_mask,
m_client.clone(),
"wallet2",
base_amount * 5
)?;
send_to_dest!(
miner.clone(),
miner_mask,
m_client.clone(),
"wallet2",
base_amount * 6
)?;
bh += 3;
let _ = test_framework::award_blocks_to_wallet(&chain, miner.clone(), miner_mask, cm, false);
bh += cm as u64;
// confirm balances
// since info is now performing a partial scan, these should confirm
// as containing all outputs
let info = wallet_info!(wallet1.clone(), mask1)?;
assert_eq!(info.amount_currently_spendable, base_amount * 21);
assert_eq!(info.total, base_amount * 21);
let info = wallet_info!(wallet2.clone(), mask2)?;
assert_eq!(info.amount_currently_spendable, base_amount * 21);
assert_eq!(info.total, base_amount * 21);
// Now there should be outputs on the chain using the same
// seed + BIP32 path.
// 1) a full restore should recover all of them:
wallet::controller::owner_single_use(Some(wallet3.clone()), mask3, None, |api, m| {
api.scan(m, None, false)?;
Ok(())
})?;
wallet::controller::owner_single_use(Some(wallet3.clone()), mask3, None, |api, m| {
let info = wallet_info!(wallet3.clone(), m)?;
let outputs = api.retrieve_outputs(m, true, false, None)?.1;
assert_eq!(outputs.len(), 6);
assert_eq!(info.amount_currently_spendable, base_amount * 21);
assert_eq!(info.total, base_amount * 21);
Ok(())
})?;
// 2) scan should recover them into a single wallet
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
api.scan(m, None, true)?;
Ok(())
})?;
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let info = wallet_info!(wallet1.clone(), m)?;
let outputs = api.retrieve_outputs(m, true, false, None)?.1;
assert_eq!(outputs.len(), 6);
assert_eq!(info.amount_currently_spendable, base_amount * 21);
Ok(())
})?;
// 3) If I recover from seed and start using the wallet without restoring,
// scan should restore the older outputs
// update, again, since scan is run automatically, balances on both
// wallets should turn out the same
send_to_dest!(
miner.clone(),
miner_mask,
m_client.clone(),
"wallet4",
base_amount * 7
)?;
send_to_dest!(
miner.clone(),
miner_mask,
m_client.clone(),
"wallet4",
base_amount * 8
)?;
send_to_dest!(
miner.clone(),
miner_mask,
m_client.clone(),
"wallet4",
base_amount * 9
)?;
bh += 3;
let _ = test_framework::award_blocks_to_wallet(&chain, miner.clone(), miner_mask, cm, false);
bh += cm as u64;
wallet::controller::owner_single_use(Some(wallet4.clone()), mask4, None, |api, m| {
let info = wallet_info!(wallet4.clone(), m)?;
let outputs = api.retrieve_outputs(m, true, false, None)?.1;
assert_eq!(outputs.len(), 9);
assert_eq!(info.amount_currently_spendable, base_amount * 45);
Ok(())
})?;
wallet::controller::owner_single_use(Some(wallet5.clone()), mask5, None, |api, m| {
api.scan(m, None, false)?;
Ok(())
})?;
wallet::controller::owner_single_use(Some(wallet5.clone()), mask5, None, |api, m| {
let info = wallet_info!(wallet5.clone(), m)?;
let outputs = api.retrieve_outputs(m, true, false, None)?.1;
assert_eq!(outputs.len(), 9);
assert_eq!(info.amount_currently_spendable, base_amount * (45));
Ok(())
})?;
// 4) If I recover from seed and start using the wallet without restoring,
// scan should restore the older outputs
send_to_dest!(
miner.clone(),
miner_mask,
m_client.clone(),
"wallet6",
base_amount * 10
)?;
send_to_dest!(
miner.clone(),
miner_mask,
m_client.clone(),
"wallet6",
base_amount * 11
)?;
send_to_dest!(
miner.clone(),
miner_mask,
m_client.clone(),
"wallet6",
base_amount * 12
)?;
bh += 3;
let _ = test_framework::award_blocks_to_wallet(
&chain,
miner.clone(),
miner_mask,
cm as usize,
false,
);
bh += cm as u64;
wallet::controller::owner_single_use(Some(wallet6.clone()), mask6, None, |api, m| {
let info = wallet_info!(wallet6.clone(), m)?;
let outputs = api.retrieve_outputs(m, true, false, None)?.1;
assert_eq!(outputs.len(), 12);
assert_eq!(info.amount_currently_spendable, base_amount * 78);
Ok(())
})?;
wallet::controller::owner_single_use(Some(wallet6.clone()), mask6, None, |api, m| {
api.scan(m, None, true)?;
Ok(())
})?;
wallet::controller::owner_single_use(Some(wallet6.clone()), mask6, None, |api, m| {
let info = wallet_info!(wallet6.clone(), m)?;
let outputs = api.retrieve_outputs(m, true, false, None)?.1;
assert_eq!(outputs.len(), 12);
assert_eq!(info.amount_currently_spendable, base_amount * (78));
Ok(())
})?;
// 5) Start using same seed with a different account, amounts should
// be distinct and restore should return funds from other account
send_to_dest!(
miner.clone(),
miner_mask,
m_client.clone(),
"wallet7",
base_amount * 13
)?;
send_to_dest!(
miner.clone(),
miner_mask,
m_client.clone(),
"wallet7",
base_amount * 14
)?;
send_to_dest!(
miner.clone(),
miner_mask,
m_client.clone(),
"wallet7",
base_amount * 15
)?;
bh += 3;
// mix it up a bit
wallet::controller::owner_single_use(Some(wallet7.clone()), mask7, None, |api, m| {
api.create_account_path(m, "account_1")?;
api.set_active_account(m, "account_1")?;
Ok(())
})?;
send_to_dest!(
miner.clone(),
miner_mask,
m_client.clone(),
"wallet7",
base_amount * 1
)?;
send_to_dest!(
miner.clone(),
miner_mask,
m_client.clone(),
"wallet7",
base_amount * 2
)?;
send_to_dest!(
miner.clone(),
miner_mask,
m_client.clone(),
"wallet7",
base_amount * 3
)?;
bh += 3;
// check balances
let _ = test_framework::award_blocks_to_wallet(&chain, miner.clone(), miner_mask, cm, false);
bh += cm as u64;
wallet::controller::owner_single_use(Some(wallet7.clone()), mask7, None, |api, m| {
let info = wallet_info!(wallet7.clone(), m)?;
let outputs = api.retrieve_outputs(m, true, false, None)?.1;
assert_eq!(outputs.len(), 3);
assert_eq!(info.amount_currently_spendable, base_amount * 6);
api.set_active_account(m, "default")?;
let info = wallet_info!(wallet7.clone(), m)?;
let outputs = api.retrieve_outputs(m, true, false, None)?.1;
assert_eq!(outputs.len(), 15);
assert_eq!(info.amount_currently_spendable, base_amount * 120);
Ok(())
})?;
wallet::controller::owner_single_use(Some(wallet8.clone()), mask8, None, |api, m| {
api.scan(m, None, false)?;
let info = wallet_info!(wallet8.clone(), m)?;
let outputs = api.retrieve_outputs(m, true, false, None)?.1;
assert_eq!(outputs.len(), 15);
assert_eq!(info.amount_currently_spendable, base_amount * 120);
api.set_active_account(m, "account_1")?;
let info = wallet_info!(wallet8.clone(), m)?;
let outputs = api.retrieve_outputs(m, true, false, None)?.1;
assert_eq!(outputs.len(), 3);
assert_eq!(info.amount_currently_spendable, base_amount * 6);
Ok(())
})?;
// 6) Start using same seed with a different account, now overwriting
// ids on account 2 as well, scan should get all outputs created
// to now into 2 accounts
wallet::controller::owner_single_use(Some(wallet9.clone()), mask9, None, |api, m| {
api.create_account_path(m, "account_1")?;
api.set_active_account(m, "account_1")?;
Ok(())
})?;
send_to_dest!(
miner.clone(),
miner_mask,
m_client.clone(),
"wallet9",
base_amount * 4
)?;
send_to_dest!(
miner.clone(),
miner_mask,
m_client.clone(),
"wallet9",
base_amount * 5
)?;
send_to_dest!(
miner.clone(),
miner_mask,
m_client.clone(),
"wallet9",
base_amount * 6
)?;
bh += 3;
let _bh = bh;
wallet::controller::owner_single_use(Some(wallet9.clone()), mask9, None, |api, m| {
let info = wallet_info!(wallet9.clone(), m)?;
let outputs = api.retrieve_outputs(m, true, false, None)?.1;
assert_eq!(outputs.len(), 6);
assert_eq!(info.amount_currently_spendable, base_amount * 21);
api.scan(m, None, true)?;
let info = wallet_info!(wallet9.clone(), m)?;
let outputs = api.retrieve_outputs(m, true, false, None)?.1;
assert_eq!(outputs.len(), 6);
assert_eq!(info.amount_currently_spendable, base_amount * 21);
api.set_active_account(m, "default")?;
let info = wallet_info!(wallet9.clone(), m)?;
let outputs = api.retrieve_outputs(m, true, false, None)?.1;
assert_eq!(outputs.len(), 15);
assert_eq!(info.amount_currently_spendable, base_amount * 120);
Ok(())
})?;
let _ = test_framework::award_blocks_to_wallet(&chain, miner.clone(), miner_mask, cm, false);
// 7) Ensure scan creates missing accounts
wallet::controller::owner_single_use(Some(wallet10.clone()), mask10, None, |api, m| {
api.scan(m, None, true)?;
api.set_active_account(m, "account_1")?;
let info = wallet_info!(wallet10.clone(), m)?;
let outputs = api.retrieve_outputs(m, true, false, None)?.1;
assert_eq!(outputs.len(), 6);
assert_eq!(info.amount_currently_spendable, base_amount * 21);
api.set_active_account(m, "default")?;
let info = wallet_info!(wallet10.clone(), m)?;
let outputs = api.retrieve_outputs(m, true, false, None)?.1;
assert_eq!(outputs.len(), 15);
assert_eq!(info.amount_currently_spendable, base_amount * 120);
Ok(())
})?;
// let logging finish
stopper.store(false, Ordering::Relaxed);
thread::sleep(Duration::from_millis(200));
Ok(())
}
// Testing output scanning functionality, easier here as the testing framework
// is all here
fn output_scanning_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 a new wallet test client, and set its queues to communicate with the
// proxy
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);
}
});
// Do some mining
let bh = 20u64;
let _ =
test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false);
// Now some chain scanning
{
// Entire range should be correct
let ranges = client1.height_range_to_pmmr_indices(1, None)?;
assert_eq!(ranges, (1, 38));
let outputs = client1.get_outputs_by_pmmr_index(ranges.0, Some(ranges.1), 1000)?;
assert_eq!(outputs.2.len(), 20);
// Basic range should be correct
let ranges = client1.height_range_to_pmmr_indices(1, Some(14))?;
assert_eq!(ranges, (1, 25));
let outputs = client1.get_outputs_by_pmmr_index(ranges.0, Some(ranges.1), 1000)?;
println!(
"Last Index: {}, Max: {}, Outputs.len: {}",
outputs.0,
outputs.1,
outputs.2.len()
);
assert_eq!(outputs.2.len(), 14);
// mid range
let ranges = client1.height_range_to_pmmr_indices(5, Some(14))?;
assert_eq!(ranges, (8, 25));
let outputs = client1.get_outputs_by_pmmr_index(ranges.0, Some(ranges.1), 1000)?;
println!(
"Last Index: {}, Max: {}, Outputs.len: {}",
outputs.0,
outputs.1,
outputs.2.len()
);
for o in outputs.2.clone() {
println!("height: {}, mmr_index: {}", o.3, o.4);
}
assert_eq!(outputs.2.len(), 10);
// end
let ranges = client1.height_range_to_pmmr_indices(5, None)?;
assert_eq!(ranges, (8, 38));
let outputs = client1.get_outputs_by_pmmr_index(ranges.0, Some(ranges.1), 1000)?;
println!(
"Last Index: {}, Max: {}, Outputs.len: {}",
outputs.0,
outputs.1,
outputs.2.len()
);
for o in outputs.2.clone() {
println!("height: {}, mmr_index: {}", o.3, o.4);
}
assert_eq!(outputs.2.len(), 16);
}
// let logging finish
stopper.store(false, Ordering::Relaxed);
thread::sleep(Duration::from_millis(200));
Ok(())
}
#[test]
fn scan() {
let test_dir = "test_output/scan";
setup(test_dir);
if let Err(e) = scan_impl(test_dir) {
panic!("Libwallet Error: {}", e);
}
clean_output_dir(test_dir);
}
#[test]
fn two_wallets_one_seed() {
let test_dir = "test_output/two_wallets_one_seed";
setup(test_dir);
if let Err(e) = two_wallets_one_seed_impl(test_dir) {
panic!("Libwallet Error: {}", e);
}
clean_output_dir(test_dir);
}
#[test]
fn output_scanning() {
let test_dir = "test_output/output_scanning";
setup(test_dir);
if let Err(e) = output_scanning_impl(test_dir) {
panic!("Libwallet Error: {}", e);
}
clean_output_dir(test_dir);
}
-178
View File
@@ -1,178 +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.
//! common functions for tests (instantiating wallet and proxy, mostly)
extern crate grin_wallet_controller as wallet;
extern crate grin_wallet_impls as impls;
extern crate grin_wallet_libwallet as libwallet;
use grin_core as core;
use grin_keychain as keychain;
use grin_util as util;
use self::core::global;
use self::core::global::ChainTypes;
use self::keychain::ExtKeychain;
use self::libwallet::WalletInst;
use impls::test_framework::{LocalWalletClient, WalletProxy};
use impls::{DefaultLCProvider, DefaultWalletImpl};
use std::sync::Arc;
use util::secp::key::SecretKey;
use util::{Mutex, ZeroingString};
#[macro_export]
macro_rules! wallet_inst {
($wallet:ident, $w: ident) => {
let mut w_lock = $wallet.lock();
let lc = w_lock.lc_provider()?;
let $w = lc.wallet_inst()?;
};
}
#[macro_export]
macro_rules! create_wallet_and_add {
($client:ident, $wallet: ident, $mask: ident, $test_dir: expr, $name: expr, $seed_phrase: expr, $proxy: expr, $create_mask: expr) => {
let $client = LocalWalletClient::new($name, $proxy.tx.clone());
let ($wallet, $mask) = common::create_local_wallet(
$test_dir,
$name,
$seed_phrase.clone(),
$client.clone(),
$create_mask,
);
$proxy.add_wallet(
$name,
$client.get_send_instance(),
$wallet.clone(),
$mask.clone(),
);
};
}
#[macro_export]
macro_rules! open_wallet_and_add {
($client:ident, $wallet: ident, $mask: ident, $test_dir: expr, $name: expr, $proxy: expr, $create_mask: expr) => {
let $client = LocalWalletClient::new($name, $proxy.tx.clone());
let ($wallet, $mask) =
common::open_local_wallet($test_dir, $name, $client.clone(), $create_mask);
$proxy.add_wallet(
$name,
$client.get_send_instance(),
$wallet.clone(),
$mask.clone(),
);
};
}
pub fn clean_output_dir(test_dir: &str) {
let path = std::path::Path::new(test_dir);
if path.is_dir() {
remove_dir_all::remove_dir_all(test_dir).unwrap();
}
}
pub fn setup(test_dir: &str) {
util::init_test_logger();
clean_output_dir(test_dir);
global::set_local_chain_type(ChainTypes::AutomatedTesting);
}
/// Some tests require the global chain_type to be configured due to threads being spawned internally.
/// It is recommended to avoid relying on this if at all possible as global chain_type
/// leaks across multiple tests and will likely have unintended consequences.
#[allow(dead_code)]
pub fn setup_global_chain_type() {
global::init_global_chain_type(global::ChainTypes::AutomatedTesting);
}
pub fn create_wallet_proxy(
test_dir: &str,
) -> WalletProxy<DefaultLCProvider<LocalWalletClient, ExtKeychain>, LocalWalletClient, ExtKeychain>
{
WalletProxy::new(test_dir)
}
pub fn create_local_wallet(
test_dir: &str,
name: &str,
mnemonic: Option<ZeroingString>,
client: LocalWalletClient,
create_mask: bool,
) -> (
Arc<
Mutex<
Box<
dyn WalletInst<
'static,
DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
LocalWalletClient,
ExtKeychain,
>,
>,
>,
>,
Option<SecretKey>,
) {
let mut wallet = Box::new(DefaultWalletImpl::<LocalWalletClient>::new(client).unwrap())
as Box<
dyn WalletInst<
DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
LocalWalletClient,
ExtKeychain,
>,
>;
let lc = wallet.lc_provider().unwrap();
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)
.unwrap();
(Arc::new(Mutex::new(wallet)), mask)
}
#[allow(dead_code)]
pub fn open_local_wallet(
test_dir: &str,
name: &str,
client: LocalWalletClient,
create_mask: bool,
) -> (
Arc<
Mutex<
Box<
dyn WalletInst<
'static,
DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
LocalWalletClient,
ExtKeychain,
>,
>,
>,
>,
Option<SecretKey>,
) {
let mut wallet = Box::new(DefaultWalletImpl::<LocalWalletClient>::new(client).unwrap())
as Box<
dyn WalletInst<
DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
LocalWalletClient,
ExtKeychain,
>,
>;
let lc = wallet.lc_provider().unwrap();
let _ = lc.set_top_level_directory(&format!("{}/{}", test_dir, name));
let mask = lc
.open_wallet(None, ZeroingString::from(""), create_mask, false)
.unwrap();
(Arc::new(Mutex::new(wallet)), mask)
}
-320
View File
@@ -1,320 +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.
//! Test a wallet file send/recieve
#[macro_use]
extern crate log;
extern crate grin_wallet_controller as wallet;
extern crate grin_wallet_impls as impls;
use grin_core as core;
use grin_wallet_libwallet as libwallet;
use impls::test_framework::{self, LocalWalletClient};
use impls::{PathToSlate, SlateGetter as _, SlatePutter as _};
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
use grin_wallet_libwallet::{InitTxArgs, IssueInvoiceTxArgs, Slate};
#[macro_use]
mod common;
use common::{clean_output_dir, create_wallet_proxy, setup};
/// self send impl
fn file_exchange_test_impl(test_dir: &'static str, use_bin: bool) -> Result<(), libwallet::Error> {
// Create a new proxy to simulate server and wallet responses
let mut wallet_proxy = create_wallet_proxy(test_dir);
let chain = wallet_proxy.chain.clone();
let stopper = wallet_proxy.running.clone();
// Create a new wallet test client, and set its queues to communicate with the
// proxy
create_wallet_and_add!(
client1,
wallet1,
mask1_i,
test_dir,
"wallet1",
None,
&mut wallet_proxy,
false
);
let mask1 = (&mask1_i).as_ref();
create_wallet_and_add!(
client2,
wallet2,
mask2_i,
test_dir,
"wallet2",
None,
&mut wallet_proxy,
false
);
let mask2 = (&mask2_i).as_ref();
// Set the wallet proxy listener running
thread::spawn(move || {
if let Err(e) = wallet_proxy.run() {
error!("Wallet Proxy error: {}", e);
}
});
// few values to keep things shorter
let reward = core::consensus::REWARD;
// add some accounts
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
api.create_account_path(m, "mining")?;
api.create_account_path(m, "listener")?;
Ok(())
})?;
// add some accounts
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
api.create_account_path(m, "account1")?;
api.create_account_path(m, "account2")?;
Ok(())
})?;
// Get some mining done
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("mining")?;
}
let mut bh = 10u64;
let _ =
test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false);
let (send_file, receive_file, final_file) = match use_bin {
false => (
format!("{}/standard_S1.tx", test_dir),
format!("{}/standard_S2.tx", test_dir),
format!("{}/standard_S3.tx", test_dir),
),
true => (
format!("{}/standard_S1.txbin", test_dir),
format!("{}/standard_S2.txbin", test_dir),
format!("{}/standard_S3.txbin", test_dir),
),
};
// Should have 5 in account1 (5 spendable), 5 in account (2 spendable)
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
assert!(wallet1_refreshed);
assert_eq!(wallet1_info.last_confirmed_height, bh);
assert_eq!(wallet1_info.total, bh * reward);
// send to send
let args = InitTxArgs {
src_acct_name: Some("mining".to_owned()),
amount: reward * 2,
minimum_confirmations: 2,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: true,
..Default::default()
};
let slate = api.init_send_tx(m, args)?;
// output tx file
PathToSlate((&send_file).into()).put_tx(&slate, use_bin)?;
api.tx_lock_outputs(m, &slate)?;
Ok(())
})?;
// Get some mining done
{
wallet_inst!(wallet2, w);
w.set_parent_key_id_by_name("account1")?;
}
let mut slate = PathToSlate((&send_file).into()).get_tx()?.0;
// wallet 2 receives file, completes, sends file back
wallet::controller::foreign_single_use(wallet2.clone(), mask2_i.clone(), |api| {
slate = api.receive_tx(&slate, None, None)?;
PathToSlate((&receive_file).into()).put_tx(&slate, use_bin)?;
Ok(())
})?;
// wallet 1 finalises and posts
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let mut slate = PathToSlate(receive_file.into()).get_tx()?.0;
slate = api.finalize_tx(m, &slate)?;
// Output final file for reference
PathToSlate((&final_file).into()).put_tx(&slate, use_bin)?;
api.post_tx(m, &slate, false)?;
bh += 1;
Ok(())
})?;
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false);
bh += 3;
// Check total in mining account
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
assert!(wallet1_refreshed);
assert_eq!(wallet1_info.last_confirmed_height, bh);
assert_eq!(wallet1_info.total, bh * reward - reward * 2);
Ok(())
})?;
// Check total in 'wallet 2' account
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?;
assert!(wallet2_refreshed);
assert_eq!(wallet2_info.last_confirmed_height, bh);
assert_eq!(wallet2_info.total, 2 * reward);
Ok(())
})?;
// Now other types of exchange, for reference
// Invoice transaction
let (send_file, receive_file, final_file) = match use_bin {
false => (
format!("{}/invoice_I1.tx", test_dir),
format!("{}/invoice_I2.tx", test_dir),
format!("{}/invoice_I3.tx", test_dir),
),
true => (
format!("{}/invoice_I1.txbin", test_dir),
format!("{}/invoice_I2.txbin", test_dir),
format!("{}/invoice_I3.txbin", test_dir),
),
};
let mut slate = Slate::blank(2, true);
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
let args = IssueInvoiceTxArgs {
amount: 1000000000,
..Default::default()
};
slate = api.issue_invoice_tx(m, args)?;
PathToSlate((&send_file).into()).put_tx(&slate, use_bin)?;
Ok(())
})?;
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let args = InitTxArgs {
src_acct_name: None,
amount: slate.amount,
minimum_confirmations: 2,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: true,
..Default::default()
};
slate = PathToSlate((&send_file).into()).get_tx()?.0;
slate = api.process_invoice_tx(m, &slate, args)?;
api.tx_lock_outputs(m, &slate)?;
PathToSlate((&receive_file).into()).put_tx(&slate, use_bin)?;
Ok(())
})?;
wallet::controller::foreign_single_use(wallet2.clone(), mask2_i.clone(), |api| {
// Wallet 2 receives the invoice transaction
slate = PathToSlate((&receive_file).into()).get_tx()?.0;
slate = api.finalize_tx(&slate, false)?;
PathToSlate((&final_file).into()).put_tx(&slate, use_bin)?;
Ok(())
})?;
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
api.post_tx(m, &slate, false)?;
Ok(())
})?;
// Standard, with payment proof
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false);
let (send_file, receive_file, final_file) = match use_bin {
false => (
format!("{}/standard_pp_S1.tx", test_dir),
format!("{}/standard_pp_S2.tx", test_dir),
format!("{}/standard_pp_S3.tx", test_dir),
),
true => (
format!("{}/standard_pp_S1.txbin", test_dir),
format!("{}/standard_pp_S2.txbin", test_dir),
format!("{}/standard_pp_S3.txbin", test_dir),
),
};
let mut slate = Slate::blank(2, true);
let mut address = None;
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
address = Some(api.get_slatepack_address(m, 0)?);
Ok(())
})?;
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
// send to send
let args = InitTxArgs {
src_acct_name: Some("mining".to_owned()),
amount: reward,
minimum_confirmations: 2,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: true,
payment_proof_recipient_address: address.clone(),
..Default::default()
};
let slate = api.init_send_tx(m, args)?;
PathToSlate((&send_file).into()).put_tx(&slate, use_bin)?;
api.tx_lock_outputs(m, &slate)?;
Ok(())
})?;
wallet::controller::foreign_single_use(wallet2.clone(), mask2_i.clone(), |api| {
slate = PathToSlate((&send_file).into()).get_tx()?.0;
slate = api.receive_tx(&slate, None, None)?;
PathToSlate((&receive_file).into()).put_tx(&slate, use_bin)?;
Ok(())
})?;
// wallet 1 finalises and posts
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
slate = PathToSlate(receive_file.into()).get_tx()?.0;
slate = api.finalize_tx(m, &slate)?;
// Output final file for reference
PathToSlate((&final_file).into()).put_tx(&slate, use_bin)?;
api.post_tx(m, &slate, false)?;
bh += 1;
Ok(())
})?;
// let logging finish
stopper.store(false, Ordering::Relaxed);
thread::sleep(Duration::from_millis(200));
Ok(())
}
#[test]
fn wallet_file_exchange_json() {
let test_dir = "test_output/file_exchange_json";
setup(test_dir);
// Json output
if let Err(e) = file_exchange_test_impl(test_dir, false) {
panic!("Libwallet Error: {}", e);
}
clean_output_dir(test_dir);
}
#[test]
fn wallet_file_exchange_bin() {
let test_dir = "test_output/file_exchange_bin";
setup(test_dir);
if let Err(e) = file_exchange_test_impl(test_dir, true) {
panic!("Libwallet Error: {}", e);
}
clean_output_dir(test_dir);
}
-305
View File
@@ -1,305 +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.
//! Test a wallet sending to self
#[macro_use]
extern crate log;
extern crate grin_wallet_controller as wallet;
extern crate grin_wallet_impls as impls;
use grin_core as core;
use grin_wallet_libwallet as libwallet;
use impls::test_framework::{self, LocalWalletClient};
use libwallet::{InitTxArgs, IssueInvoiceTxArgs, Slate, SlateState};
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
#[macro_use]
mod common;
use common::{clean_output_dir, create_wallet_proxy, setup};
/// self send impl
fn invoice_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// Create a new proxy to simulate server and wallet responses
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,
true
);
let mask1 = (&mask1_i).as_ref();
create_wallet_and_add!(
client2,
wallet2,
mask2_i,
test_dir,
"wallet2",
None,
&mut wallet_proxy,
true
);
let mask2 = (&mask2_i).as_ref();
// Set the wallet proxy listener running
thread::spawn(move || {
if let Err(e) = wallet_proxy.run() {
error!("Wallet Proxy error: {}", e);
}
});
// few values to keep things shorter
let reward = core::consensus::REWARD;
// add some accounts
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
api.create_account_path(m, "mining")?;
api.create_account_path(m, "listener")?;
Ok(())
})?;
// Get some mining done
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("mining")?;
}
let mut _bh = 10u64;
let _ =
test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, _bh as usize, false);
// Sanity check wallet 1 contents
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
assert!(wallet1_refreshed);
assert_eq!(wallet1_info.last_confirmed_height, _bh);
assert_eq!(wallet1_info.total, _bh * reward);
Ok(())
})?;
let mut slate = Slate::blank(2, true);
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
// Wallet 2 inititates an invoice transaction, requesting payment
let args = IssueInvoiceTxArgs {
amount: reward * 2,
..Default::default()
};
slate = api.issue_invoice_tx(m, args)?;
Ok(())
})?;
assert_eq!(slate.state, SlateState::Invoice1);
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
// Wallet 1 receives the invoice transaction
let args = InitTxArgs {
src_acct_name: None,
amount: slate.amount,
minimum_confirmations: 2,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: true,
..Default::default()
};
slate = api.process_invoice_tx(m, &slate, args)?;
api.tx_lock_outputs(m, &slate)?;
Ok(())
})?;
assert_eq!(slate.state, SlateState::Invoice2);
// wallet 2 finalizes and posts
wallet::controller::foreign_single_use(wallet2.clone(), mask2_i.clone(), |api| {
// Wallet 2 receives the invoice transaction
slate = api.finalize_tx(&slate, false)?;
Ok(())
})?;
assert_eq!(slate.state, SlateState::Invoice3);
// wallet 1 posts so wallet 2 doesn't get the mined amount
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
api.post_tx(m, &slate, false)?;
Ok(())
})?;
_bh += 1;
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false);
_bh += 3;
// Check transaction log for wallet 2
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
let (_, wallet2_info) = api.retrieve_summary_info(m, true, 1)?;
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert!(refreshed);
assert!(txs.len() == 1);
println!(
"last confirmed height: {}, bh: {}",
wallet2_info.last_confirmed_height, _bh
);
assert!(refreshed);
Ok(())
})?;
// Check transaction log for wallet 1, ensure only 1 entry
// exists
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert!(refreshed);
assert_eq!(txs.len() as u64, _bh + 1);
println!(
"Wallet 1: last confirmed height: {}, bh: {}",
wallet1_info.last_confirmed_height, _bh
);
Ok(())
})?;
// Test self-sending
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
// Wallet 1 inititates an invoice transaction, requesting payment
let args = IssueInvoiceTxArgs {
amount: reward * 2,
..Default::default()
};
slate = api.issue_invoice_tx(m, args)?;
// Wallet 1 receives the invoice transaction
let args = InitTxArgs {
src_acct_name: None,
amount: slate.amount,
minimum_confirmations: 2,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: true,
..Default::default()
};
println!("Self invoice slate init: {}", slate);
slate = api.process_invoice_tx(m, &slate, args)?;
api.tx_lock_outputs(m, &slate)?;
Ok(())
})?;
println!("Self invoice slate after process: {}", slate);
// wallet 1 finalizes and posts
wallet::controller::foreign_single_use(wallet1.clone(), mask1_i.clone(), |api| {
// Wallet 2 receives the invoice transaction
slate = api.finalize_tx(&slate, false)?;
Ok(())
})?;
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false);
//bh += 3;
// As above, but use owner API to finalize
let mut slate = Slate::blank(2, true);
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
// Wallet 2 inititates an invoice transaction, requesting payment
let args = IssueInvoiceTxArgs {
amount: reward * 2,
..Default::default()
};
slate = api.issue_invoice_tx(m, args)?;
Ok(())
})?;
assert_eq!(slate.state, SlateState::Invoice1);
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
// Wallet 1 receives the invoice transaction
let args = InitTxArgs {
src_acct_name: None,
amount: slate.amount,
minimum_confirmations: 2,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: true,
..Default::default()
};
slate = api.process_invoice_tx(m, &slate, args)?;
api.tx_lock_outputs(m, &slate)?;
Ok(())
})?;
assert_eq!(slate.state, SlateState::Invoice2);
// wallet 2 finalizes via owner API
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
// Wallet 2 receives the invoice transaction
slate = api.finalize_tx(m, &slate)?;
Ok(())
})?;
assert_eq!(slate.state, SlateState::Invoice3);
// test that payee can only cancel once
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false);
_bh += 3;
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
// Wallet 2 inititates an invoice transaction, requesting payment
let args = IssueInvoiceTxArgs {
amount: reward * 2,
..Default::default()
};
slate = api.issue_invoice_tx(m, args)?;
Ok(())
})?;
assert_eq!(slate.state, SlateState::Invoice1);
let orig_slate = slate.clone();
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
// Wallet 1 receives the invoice transaction
let args = InitTxArgs {
src_acct_name: None,
amount: slate.amount,
minimum_confirmations: 2,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: true,
..Default::default()
};
slate = api.process_invoice_tx(m, &slate, args.clone())?;
api.tx_lock_outputs(m, &slate)?;
// Wallet 1 cancels the invoice transaction
api.cancel_tx(m, None, Some(slate.id))?;
// Wallet 1 attempts to repay again
let res = api.process_invoice_tx(m, &orig_slate, args);
assert!(res.is_err());
Ok(())
})?;
assert_eq!(slate.state, SlateState::Invoice2);
// let logging finish
stopper.store(false, Ordering::Relaxed);
thread::sleep(Duration::from_millis(200));
Ok(())
}
#[test]
fn wallet_invoice_tx() -> Result<(), libwallet::Error> {
let test_dir = "test_output/invoice_tx";
setup(test_dir);
invoice_tx_impl(test_dir)?;
clean_output_dir(test_dir);
Ok(())
}
-158
View File
@@ -1,158 +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.
//! Tests and experimentations with late locking
#[macro_use]
extern crate log;
extern crate grin_wallet_controller as wallet;
extern crate grin_wallet_impls as impls;
extern crate grin_wallet_libwallet as libwallet;
use self::libwallet::{InitTxArgs, Slate};
use impls::test_framework::{self, LocalWalletClient};
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
#[macro_use]
mod common;
use common::{clean_output_dir, create_wallet_proxy, setup};
/// self send impl
fn late_lock_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// Create a new proxy to simulate server and wallet responses
let mut wallet_proxy = create_wallet_proxy(test_dir);
let chain = wallet_proxy.chain.clone();
let stopper = wallet_proxy.running.clone();
// Create a new wallet test client, and set its queues to communicate with the
// proxy
create_wallet_and_add!(
client1,
wallet1,
mask1_i,
test_dir,
"wallet1",
None,
&mut wallet_proxy,
false
);
let mask1 = (&mask1_i).as_ref();
create_wallet_and_add!(
client2,
wallet2,
mask2_i,
test_dir,
"wallet2",
None,
&mut wallet_proxy,
false
);
let mask2 = (&mask2_i).as_ref();
// Set the wallet proxy listener running
thread::spawn(move || {
if let Err(e) = wallet_proxy.run() {
error!("Wallet Proxy error: {}", e);
}
});
// add some accounts
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
api.create_account_path(m, "mining")?;
Ok(())
})?;
// add some accounts
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
api.create_account_path(m, "account1")?;
Ok(())
})?;
// Get some mining done
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("mining")?;
}
{
wallet_inst!(wallet2, w);
w.set_parent_key_id_by_name("account1")?;
}
test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 10, false)?;
let mut slate = Slate::blank(2, false);
let amount = 100_000_000_000;
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| {
let args = InitTxArgs {
src_acct_name: Some("mining".to_owned()),
amount,
minimum_confirmations: 2,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: false,
late_lock: Some(true),
..Default::default()
};
let slate_i = sender_api.init_send_tx(m, args)?;
println!("S1 SLATE: {}", slate_i);
slate = client1.send_tx_slate_direct("wallet2", &slate_i)?;
println!("S2 SLATE: {}", slate);
// Note we don't call `tx_lock_outputs` on the sender side here,
// as the outputs will only be locked during finalization
slate = sender_api.finalize_tx(m, &slate)?;
println!("S3 SLATE: {}", slate);
// Now post tx to our node for inclusion in the next block.
sender_api.post_tx(m, &slate, true)?;
Ok(())
})?;
test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false)?;
// update/test contents of both accounts
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (wallet1_refreshed, wallet_info) = api.retrieve_summary_info(m, true, 1)?;
assert!(wallet1_refreshed);
// Reward from mining 11 blocks, minus the amount sent.
// Note: We mined the block containing the tx, so fees are effectively refunded.
assert_eq!(560_000_000_000, wallet_info.amount_currently_spendable);
Ok(())
})?;
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
let (wallet2_refreshed, wallet_info) = api.retrieve_summary_info(m, true, 1)?;
assert!(wallet2_refreshed);
assert_eq!(amount, wallet_info.amount_currently_spendable);
Ok(())
})?;
// let logging finish
stopper.store(false, Ordering::Relaxed);
thread::sleep(Duration::from_millis(200));
Ok(())
}
#[test]
fn late_lock() {
let test_dir = "test_output/late_lock";
setup(test_dir);
if let Err(e) = late_lock_test_impl(test_dir) {
panic!("Libwallet Error: {}", e);
}
clean_output_dir(test_dir);
}
-190
View File
@@ -1,190 +0,0 @@
// Copyright 2024 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.
//! Test a wallet sending to self, then creation of comsig request
#[macro_use]
extern crate log;
extern crate grin_wallet_controller as wallet;
extern crate grin_wallet_impls as impls;
use grin_core as core;
use grin_util as util;
use grin_util::secp::key::SecretKey;
use grin_wallet_libwallet as libwallet;
use impls::test_framework::{self, LocalWalletClient};
use libwallet::{mwixnet::MixnetReqCreationParams, InitTxArgs};
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
#[macro_use]
mod common;
use common::{clean_output_dir, create_wallet_proxy, setup};
/// self send impl
fn mwixnet_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// Create a new proxy to simulate server and wallet responses
let mut wallet_proxy = create_wallet_proxy(test_dir);
let chain = wallet_proxy.chain.clone();
let stopper = wallet_proxy.running.clone();
// Create a new wallet test client, and set its queues to communicate with the
// proxy
create_wallet_and_add!(
client1,
wallet1,
mask1_i,
test_dir,
"wallet1",
None,
&mut wallet_proxy,
true
);
let mask1 = (&mask1_i).as_ref();
// Set the wallet proxy listener running
thread::spawn(move || {
if let Err(e) = wallet_proxy.run() {
error!("Wallet Proxy error: {}", e);
}
});
// few values to keep things shorter
let reward = core::consensus::REWARD;
// add some accounts
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
api.create_account_path(m, "mining")?;
api.create_account_path(m, "listener")?;
Ok(())
})?;
// Get some mining done
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("mining")?;
}
let mut bh = 10u64;
let _ =
test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false);
// Should have 5 in account1 (5 spendable), 5 in account (2 spendable)
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
assert!(wallet1_refreshed);
assert_eq!(wallet1_info.last_confirmed_height, bh);
assert_eq!(wallet1_info.total, bh * reward);
// send to send
let args = InitTxArgs {
src_acct_name: Some("mining".to_owned()),
amount: reward * 2,
minimum_confirmations: 2,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: true,
..Default::default()
};
let mut slate = api.init_send_tx(m, args)?;
api.tx_lock_outputs(m, &slate)?;
// Send directly to self
wallet::controller::foreign_single_use(wallet1.clone(), mask1_i.clone(), |api| {
slate = api.receive_tx(&slate, Some("listener"), None)?;
Ok(())
})?;
slate = api.finalize_tx(m, &slate)?;
api.post_tx(m, &slate, false)?; // mines a block
bh += 1;
Ok(())
})?;
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false);
bh += 3;
// Check total in mining account
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
assert!(wallet1_refreshed);
assert_eq!(wallet1_info.last_confirmed_height, bh);
assert_eq!(wallet1_info.total, bh * reward - reward * 2);
Ok(())
})?;
// Check total in 'listener' account
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("listener")?;
}
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
assert!(wallet1_refreshed);
assert_eq!(wallet1_info.last_confirmed_height, bh);
assert_eq!(wallet1_info.total, 2 * reward);
Ok(())
})?;
// Recipient wallet creates a mwixnet request from the last output
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let secp_locked = util::static_secp_instance();
let secp = secp_locked.lock();
let server_pubkey_str_1 =
"97444ae673bb92c713c1a2f7b8882ffbfc1c67401a280a775dce1a8651584332";
let server_pubkey_str_2 =
"0c9414341f2140ed34a5a12a6479bf5a6404820d001ab81d9d3e8cc38f049b4e";
let server_pubkey_str_3 =
"b58ece97d60e71bb7e53218400b0d67bfe6a3cb7d3b4a67a44f8fb7c525cbca5";
let server_key_1 =
SecretKey::from_slice(&secp, &grin_util::from_hex(&server_pubkey_str_1).unwrap())
.unwrap();
let server_key_2 =
SecretKey::from_slice(&secp, &grin_util::from_hex(&server_pubkey_str_2).unwrap())
.unwrap();
let server_key_3 =
SecretKey::from_slice(&secp, &grin_util::from_hex(&server_pubkey_str_3).unwrap())
.unwrap();
let params = MixnetReqCreationParams {
server_keys: vec![server_key_1, server_key_2, server_key_3],
fee_per_hop: 50_000_000,
};
let outputs = api.retrieve_outputs(mask1, false, false, None)?;
// get last output
let last_output = outputs.1[outputs.1.len() - 1].clone();
let mwixnet_req = api.create_mwixnet_req(m, &params, &last_output.commit, true)?;
println!("MWIXNET REQ: {:?}", mwixnet_req);
// check output we created comsig for is indeed locked
let outputs = api.retrieve_outputs(mask1, false, false, None)?;
// get last output
let last_output = outputs.1[outputs.1.len() - 1].clone();
assert!(last_output.output.status == libwallet::OutputStatus::Locked);
Ok(())
})?;
// let logging finish
stopper.store(false, Ordering::Relaxed);
thread::sleep(Duration::from_millis(1000));
Ok(())
}
#[test]
fn mwixnet_comsig_test() {
let test_dir = "test_output/mwixnet";
setup(test_dir);
if let Err(e) = mwixnet_test_impl(test_dir) {
panic!("Libwallet Error: {}", e);
}
clean_output_dir(test_dir);
}
-212
View File
@@ -1,212 +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.
//! Test sender transaction with no change output
#[macro_use]
extern crate log;
extern crate grin_wallet_controller as wallet;
extern crate grin_wallet_impls as impls;
use grin_core as core;
use grin_wallet_libwallet as libwallet;
use impls::test_framework::{self, LocalWalletClient};
use libwallet::{InitTxArgs, IssueInvoiceTxArgs, Slate};
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
#[macro_use]
mod common;
use common::{clean_output_dir, create_wallet_proxy, setup};
fn no_change_test_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();
create_wallet_and_add!(
client2,
wallet2,
mask2_i,
test_dir,
"wallet2",
None,
&mut wallet_proxy,
false
);
let mask2 = (&mask2_i).as_ref();
// Set the wallet proxy listener running
thread::spawn(move || {
if let Err(e) = wallet_proxy.run() {
error!("Wallet Proxy error: {}", e);
}
});
// few values to keep things shorter
let reward = core::consensus::REWARD;
// Mine into wallet 1
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 4, false);
let fee = core::libtx::tx_fee(1, 1, 1);
// send a single block's worth of transactions with minimal strategy
let mut slate = Slate::blank(2, false);
let mut stored_excess = None;
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let args = InitTxArgs {
src_acct_name: None,
amount: reward - fee,
minimum_confirmations: 2,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: false,
..Default::default()
};
slate = api.init_send_tx(m, args)?;
slate = client1.send_tx_slate_direct("wallet2", &slate)?;
api.tx_lock_outputs(m, &slate)?;
slate = api.finalize_tx(m, &slate)?;
println!("Posted Slate: {:?}", slate);
stored_excess = Some(slate.tx.as_ref().unwrap().body.kernels[0].excess);
api.post_tx(m, &slate, false)?;
Ok(())
})?;
// ensure stored excess is correct in both wallets
// Wallet 1 calculated the excess with the full slate // Wallet 2 only had the excess provided by
// wallet 1
// Refresh and check transaction log for wallet 1
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (refreshed, txs) = api.retrieve_txs(m, true, None, Some(slate.id), None)?;
assert!(refreshed);
let tx = txs[0].clone();
println!("SIMPLE SEND - SENDING WALLET");
println!("{:?}", tx);
println!();
assert!(tx.confirmed);
assert_eq!(stored_excess, tx.kernel_excess);
Ok(())
})?;
// Refresh and check transaction log for wallet 2
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
let (refreshed, txs) = api.retrieve_txs(m, true, None, Some(slate.id), None)?;
assert!(refreshed);
let tx = txs[0].clone();
println!("SIMPLE SEND - RECEIVING WALLET");
println!("{:?}", tx);
println!();
assert!(tx.confirmed);
assert_eq!(stored_excess, tx.kernel_excess);
Ok(())
})?;
// ensure invoice TX works as well with no change
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
// Wallet 2 inititates an invoice transaction, requesting payment
let args = IssueInvoiceTxArgs {
amount: reward - fee,
..Default::default()
};
slate = api.issue_invoice_tx(m, args)?;
Ok(())
})?;
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
// Wallet 1 receives the invoice transaction
let args = InitTxArgs {
src_acct_name: None,
amount: slate.amount,
minimum_confirmations: 2,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: false,
..Default::default()
};
slate = api.process_invoice_tx(m, &slate, args)?;
api.tx_lock_outputs(m, &slate)?;
Ok(())
})?;
// wallet 2 finalizes and posts
wallet::controller::foreign_single_use(wallet2.clone(), mask2_i.clone(), |api| {
// Wallet 2 receives the invoice transaction
slate = api.finalize_tx(&slate, false)?;
Ok(())
})?;
wallet::controller::owner_single_use(Some(wallet2.clone()), mask1, None, |api, m| {
println!("Invoice Posted TX: {}", slate);
stored_excess = Some(slate.tx.as_ref().unwrap().body.kernels[0].excess);
api.post_tx(m, &slate, false)?;
Ok(())
})?;
// check wallet 2's version
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
let (refreshed, txs) = api.retrieve_txs(m, true, None, Some(slate.id), None)?;
assert!(refreshed);
for tx in txs {
stored_excess = tx.kernel_excess;
println!("Wallet 2: {:?}", tx);
println!();
assert!(tx.confirmed);
assert_eq!(stored_excess, tx.kernel_excess);
}
Ok(())
})?;
// Refresh and check transaction log for wallet 1
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (refreshed, txs) = api.retrieve_txs(m, true, None, Some(slate.id), None)?;
assert!(refreshed);
for tx in txs {
println!("Wallet 1: {:?}", tx);
println!();
assert_eq!(stored_excess, tx.kernel_excess);
assert!(tx.confirmed);
}
Ok(())
})?;
// let logging finish
stopper.store(false, Ordering::Relaxed);
thread::sleep(Duration::from_millis(200));
Ok(())
}
#[test]
fn no_change() {
let test_dir = "test_output/no_change";
setup(test_dir);
if let Err(e) = no_change_test_impl(test_dir) {
panic!("Libwallet Error: {}", e);
}
clean_output_dir(test_dir);
}
-172
View File
@@ -1,172 +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.
//! tests differing accounts in the same wallet
#[macro_use]
extern crate log;
extern crate grin_wallet_controller as wallet;
extern crate grin_wallet_impls as impls;
extern crate grin_wallet_util;
use grin_wallet_libwallet as libwallet;
use impls::test_framework::{self, LocalWalletClient};
use libwallet::{InitTxArgs, Slate};
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
#[macro_use]
mod common;
use common::{clean_output_dir, create_wallet_proxy, setup};
/// Various tests on accounts within the same wallet
fn payment_proofs_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// Create a new proxy to simulate server and wallet responses
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();
create_wallet_and_add!(
client2,
wallet2,
mask2_i,
test_dir,
"wallet2",
None,
&mut wallet_proxy,
false
);
let mask2 = (&mask2_i).as_ref();
// Set the wallet proxy listener running
thread::spawn(move || {
if let Err(e) = wallet_proxy.run() {
error!("Wallet Proxy error: {}", e);
}
});
// few values to keep things shorter
// Do some mining
let bh = 10u64;
let _ =
test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false);
let mut address = None;
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
address = Some(api.get_slatepack_address(m, 0)?);
Ok(())
})?;
println!("Public address is: {:?}", address);
let amount = 60_000_000_000;
let mut slate = Slate::blank(1, false);
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| {
// note this will increment the block count as part of the transaction "Posting"
let args = InitTxArgs {
src_acct_name: None,
amount: amount,
minimum_confirmations: 2,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: true,
payment_proof_recipient_address: address.clone(),
..Default::default()
};
let slate_i = sender_api.init_send_tx(m, args)?;
assert_eq!(
slate_i.payment_proof.as_ref().unwrap().receiver_address,
address.as_ref().unwrap().pub_key,
);
println!(
"Sender addr: {:?}",
slate_i.payment_proof.as_ref().unwrap().sender_address
);
// Check we are creating a tx with kernel features 0
// We will check this produces a Plain kernel later.
assert_eq!(0, slate.kernel_features);
slate = client1.send_tx_slate_direct("wallet2", &slate_i)?;
sender_api.tx_lock_outputs(m, &slate)?;
// Ensure what's stored in TX log for payment proof is correct
let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id), None)?;
assert!(txs[0].payment_proof.is_some());
let pp = txs[0].clone().payment_proof.unwrap();
assert_eq!(
pp.receiver_address,
slate_i.payment_proof.as_ref().unwrap().receiver_address
);
assert!(pp.receiver_signature.is_some());
assert_eq!(pp.sender_address_path, 0);
assert_eq!(pp.sender_signature, None);
// check we should get an error at this point since proof is not complete
let pp = sender_api.retrieve_payment_proof(m, true, None, Some(slate.id));
assert!(pp.is_err());
slate = sender_api.finalize_tx(m, &slate)?;
sender_api.post_tx(m, &slate, true)?;
Ok(())
})?;
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 2, false);
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| {
// Check payment proof here
let mut pp = sender_api.retrieve_payment_proof(m, true, None, Some(slate.id))?;
println!("Payment proof: {:?}", pp);
// verify, should be good
let res = sender_api.verify_payment_proof(m, &pp)?;
assert_eq!(res, (true, false));
// Modify values, should not be good
pp.amount = 20;
let res = sender_api.verify_payment_proof(m, &pp);
assert!(res.is_err());
Ok(())
})?;
// let logging finish
stopper.store(false, Ordering::Relaxed);
thread::sleep(Duration::from_millis(200));
Ok(())
}
#[test]
fn payment_proofs() {
let test_dir = "test_output/payment_proofs";
setup(test_dir);
if let Err(e) = payment_proofs_test_impl(test_dir) {
panic!("Libwallet Error: {}", e);
}
clean_output_dir(test_dir);
}
-268
View File
@@ -1,268 +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.
//! Test a wallet repost command
#[macro_use]
extern crate log;
extern crate grin_wallet_controller as wallet;
extern crate grin_wallet_impls as impls;
extern crate grin_wallet_libwallet as libwallet;
use grin_core as core;
use self::libwallet::{InitTxArgs, Slate};
use impls::test_framework::{self, LocalWalletClient};
use impls::{PathToSlate, SlateGetter as _, SlatePutter as _};
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
#[macro_use]
mod common;
use common::{clean_output_dir, create_wallet_proxy, setup};
/// self send impl
fn file_repost_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// Create a new proxy to simulate server and wallet responses
let mut wallet_proxy = create_wallet_proxy(test_dir);
let chain = wallet_proxy.chain.clone();
let stopper = wallet_proxy.running.clone();
// Create a new wallet test client, and set its queues to communicate with the
// proxy
create_wallet_and_add!(
client1,
wallet1,
mask1_i,
test_dir,
"wallet1",
None,
&mut wallet_proxy,
false
);
let mask1 = (&mask1_i).as_ref();
create_wallet_and_add!(
client2,
wallet2,
mask2_i,
test_dir,
"wallet2",
None,
&mut wallet_proxy,
false
);
let mask2 = (&mask2_i).as_ref();
// Set the wallet proxy listener running
thread::spawn(move || {
if let Err(e) = wallet_proxy.run() {
error!("Wallet Proxy error: {}", e);
}
});
// few values to keep things shorter
let reward = core::consensus::REWARD;
// add some accounts
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
api.create_account_path(m, "mining")?;
api.create_account_path(m, "listener")?;
Ok(())
})?;
// add some accounts
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
api.create_account_path(m, "account1")?;
api.create_account_path(m, "account2")?;
Ok(())
})?;
// Get some mining done
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("mining")?;
}
let mut bh = 10u64;
let _ =
test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false);
let send_file = format!("{}/part_tx_1.tx", test_dir);
let receive_file = format!("{}/part_tx_2.tx", test_dir);
let mut slate = Slate::blank(2, false);
// Should have 5 in account1 (5 spendable), 5 in account (2 spendable)
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
assert!(wallet1_refreshed);
assert_eq!(wallet1_info.last_confirmed_height, bh);
assert_eq!(wallet1_info.total, bh * reward);
// send to send
let args = InitTxArgs {
src_acct_name: Some("mining".to_owned()),
amount: reward * 2,
minimum_confirmations: 2,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: true,
..Default::default()
};
let slate = api.init_send_tx(m, args)?;
PathToSlate((&send_file).into()).put_tx(&slate, false)?;
api.tx_lock_outputs(m, &slate)?;
Ok(())
})?;
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false);
bh += 3;
// wallet 1 receives file to different account, completes
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("listener")?;
}
wallet::controller::foreign_single_use(wallet1.clone(), mask1_i.clone(), |api| {
slate = PathToSlate((&send_file).into()).get_tx()?.0;
slate = api.receive_tx(&slate, None, None)?;
PathToSlate((&receive_file).into()).put_tx(&slate, false)?;
Ok(())
})?;
// wallet 1 receives file to different account, completes
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("mining")?;
}
// wallet 1 finalize
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
slate = PathToSlate((&receive_file).into()).get_tx()?.0;
slate = api.finalize_tx(m, &slate)?;
Ok(())
})?;
// Now repost from cached
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (_, txs) = api.retrieve_txs(m, true, None, Some(slate.id), None)?;
println!("TXS[0]: {:?}", txs[0]);
let stored_tx = api.get_stored_tx(m, None, Some(&txs[0].tx_slate_id.unwrap()))?;
println!("Stored tx: {:?}", stored_tx);
api.post_tx(m, &slate, false)?;
bh += 1;
Ok(())
})?;
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false);
bh += 3;
// update/test contents of both accounts
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
assert!(wallet1_refreshed);
assert_eq!(wallet1_info.last_confirmed_height, bh);
assert_eq!(wallet1_info.total, bh * reward - reward * 2);
Ok(())
})?;
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("listener")?;
}
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?;
assert!(wallet2_refreshed);
assert_eq!(wallet2_info.last_confirmed_height, bh);
assert_eq!(wallet2_info.total, 2 * reward);
Ok(())
})?;
// as above, but syncronously
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("mining")?;
}
{
wallet_inst!(wallet2, w);
w.set_parent_key_id_by_name("account1")?;
}
let mut slate = Slate::blank(2, false);
let amount = 60_000_000_000;
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| {
// note this will increment the block count as part of the transaction "Posting"
let args = InitTxArgs {
src_acct_name: None,
amount: reward * 2,
minimum_confirmations: 2,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: true,
..Default::default()
};
let slate_i = sender_api.init_send_tx(m, args)?;
slate = client1.send_tx_slate_direct("wallet2", &slate_i)?;
sender_api.tx_lock_outputs(m, &slate)?;
slate = sender_api.finalize_tx(m, &slate)?;
Ok(())
})?;
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false);
bh += 3;
// Now repost from cached
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (_, txs) = api.retrieve_txs(m, true, None, Some(slate.id), None)?;
let stored_tx_slate = api.get_stored_tx(m, Some(txs[0].id), None)?.unwrap();
api.post_tx(m, &stored_tx_slate, false)?;
bh += 1;
Ok(())
})?;
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false);
bh += 3;
//
// update/test contents of both accounts
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
assert!(wallet1_refreshed);
assert_eq!(wallet1_info.last_confirmed_height, bh);
assert_eq!(wallet1_info.total, bh * reward - reward * 4);
Ok(())
})?;
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?;
assert!(wallet2_refreshed);
assert_eq!(wallet2_info.last_confirmed_height, bh);
assert_eq!(wallet2_info.total, 2 * amount);
Ok(())
})?;
// let logging finish
stopper.store(false, Ordering::Relaxed);
thread::sleep(Duration::from_millis(200));
Ok(())
}
#[test]
fn wallet_file_repost() {
let test_dir = "test_output/file_repost";
setup(test_dir);
if let Err(e) = file_repost_test_impl(test_dir) {
panic!("Libwallet Error: {}", e);
}
clean_output_dir(test_dir);
}
-379
View File
@@ -1,379 +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.
#[macro_use]
mod common;
use common::{clean_output_dir, create_wallet_proxy, setup};
use grin_chain as chain;
use grin_core as core;
use grin_core::core::hash::Hashed;
use grin_core::core::Transaction;
use grin_core::global;
use grin_keychain::ExtKeychain;
use grin_util::secp::key::SecretKey;
use grin_util::Mutex;
use grin_wallet_controller::controller::owner_single_use as owner;
use grin_wallet_impls::test_framework::*;
use grin_wallet_impls::{DefaultLCProvider, PathToSlate, SlatePutter};
use grin_wallet_libwallet as libwallet;
use grin_wallet_libwallet::api_impl::types::InitTxArgs;
use grin_wallet_libwallet::WalletInst;
use log::error;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::Duration;
type Wallet = Arc<
Mutex<
Box<
dyn WalletInst<
'static,
DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
LocalWalletClient,
ExtKeychain,
>,
>,
>,
>;
fn revert(
test_dir: &'static str,
) -> Result<
(
Arc<chain::Chain>,
Arc<AtomicBool>,
u64,
u64,
Transaction,
Wallet,
Option<SecretKey>,
Wallet,
Option<SecretKey>,
),
libwallet::Error,
> {
let mut wallet_proxy = create_wallet_proxy(test_dir);
let stopper = wallet_proxy.running.clone();
let chain = wallet_proxy.chain.clone();
let test_dir2 = format!("{}/chain2", test_dir);
let wallet_proxy2 = create_wallet_proxy(&test_dir2);
let chain2 = wallet_proxy2.chain.clone();
let stopper2 = wallet_proxy2.running.clone();
create_wallet_and_add!(
client1,
wallet1,
mask1_i,
test_dir,
"wallet1",
None,
&mut wallet_proxy,
false
);
let mask1 = mask1_i.as_ref();
create_wallet_and_add!(
client2,
wallet2,
mask2_i,
test_dir,
"wallet2",
None,
&mut wallet_proxy,
false
);
let mask2 = mask2_i.as_ref();
// Set the wallet proxy listener running
std::thread::spawn(move || {
if let Err(e) = wallet_proxy.run() {
error!("Wallet Proxy error: {}", e);
}
});
owner(Some(wallet1.clone()), mask1, None, |api, m| {
api.create_account_path(m, "a")?;
api.set_active_account(m, "a")?;
Ok(())
})?;
owner(Some(wallet2.clone()), mask2, None, |api, m| {
api.create_account_path(m, "b")?;
api.set_active_account(m, "b")?;
Ok(())
})?;
let reward = core::consensus::REWARD;
let cm = global::coinbase_maturity() as u64;
let sent = reward * 2;
// Mine some blocks
let bh = 10u64;
award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false)?;
// Sanity check contents
owner(Some(wallet1.clone()), mask1, None, |api, m| {
let (refreshed, info) = api.retrieve_summary_info(m, true, 1)?;
assert!(refreshed);
assert_eq!(info.last_confirmed_height, bh);
assert_eq!(info.total, bh * reward);
assert_eq!(info.amount_currently_spendable, (bh - cm) * reward);
assert_eq!(info.amount_reverted, 0);
// check tx log as well
let (_, txs) = api.retrieve_txs(m, true, None, None, None)?;
let (c, _) = libwallet::TxLogEntry::sum_confirmed(&txs);
assert_eq!(info.total, c);
assert_eq!(txs.len(), bh as usize);
Ok(())
})?;
owner(Some(wallet2.clone()), mask2, None, |api, m| {
let (refreshed, info) = api.retrieve_summary_info(m, true, 1)?;
assert!(refreshed);
assert_eq!(info.last_confirmed_height, bh);
assert_eq!(info.total, 0);
assert_eq!(info.amount_currently_spendable, 0);
assert_eq!(info.amount_reverted, 0);
// check tx log as well
let (_, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert_eq!(txs.len(), 0);
Ok(())
})?;
// Send some funds
let mut tx = None;
owner(Some(wallet1.clone()), mask1, None, |api, m| {
// send to send
let args = InitTxArgs {
src_acct_name: None,
amount: sent,
minimum_confirmations: cm,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: false,
..Default::default()
};
let slate = api.init_send_tx(m, args)?;
// output tx file
let send_file = format!("{}/part_tx_1.tx", test_dir);
PathToSlate(send_file.into()).put_tx(&slate, false)?;
api.tx_lock_outputs(m, &slate)?;
let slate = client1.send_tx_slate_direct("wallet2", &slate)?;
let slate = api.finalize_tx(m, &slate)?;
tx = slate.tx;
Ok(())
})?;
let tx = tx.expect("tx from slate");
// Check funds have been received
owner(Some(wallet2.clone()), mask2, None, |api, m| {
let (refreshed, info) = api.retrieve_summary_info(m, true, 1)?;
assert!(refreshed);
assert_eq!(info.last_confirmed_height, bh);
assert_eq!(info.total, 0);
assert_eq!(info.amount_currently_spendable, 0);
assert_eq!(info.amount_reverted, 0);
// check tx log as well
let (_, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert_eq!(txs.len(), 1);
let tx = &txs[0];
assert_eq!(tx.tx_type, libwallet::TxLogEntryType::TxReceived);
assert!(!tx.confirmed);
Ok(())
})?;
// Update parallel chain
assert_eq!(chain2.head_header().unwrap().height, 0);
for i in 0..bh {
let hash = chain.get_header_by_height(i + 1).unwrap().hash();
let block = chain.get_block(&hash).unwrap();
process_block(&chain2, block);
}
assert_eq!(chain2.head_header().unwrap().height, bh);
// Build 2 blocks at same height: 1 with the tx, 1 without
let head = chain.head_header().unwrap();
let block_with =
create_block_for_wallet(&chain, head.clone(), &[tx.clone()], wallet1.clone(), mask1)?;
let block_without = create_block_for_wallet(&chain, head, &[], wallet1.clone(), mask1)?;
// Add block with tx to the chain
process_block(&chain, block_with.clone());
assert_eq!(chain.head_header().unwrap(), block_with.header);
// Add block without tx to the parallel chain
process_block(&chain2, block_without.clone());
assert_eq!(chain2.head_header().unwrap(), block_without.header);
let bh = bh + 1;
// Check funds have been confirmed
owner(Some(wallet2.clone()), mask2, None, |api, m| {
let (refreshed, info) = api.retrieve_summary_info(m, true, 1)?;
assert!(refreshed);
assert_eq!(info.last_confirmed_height, bh);
assert_eq!(info.total, sent);
assert_eq!(info.amount_currently_spendable, sent);
assert_eq!(info.amount_reverted, 0);
// check tx log as well
let (_, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert_eq!(txs.len(), 1);
let tx = &txs[0];
assert_eq!(tx.tx_type, libwallet::TxLogEntryType::TxReceived);
assert!(tx.confirmed);
assert!(tx.kernel_excess.is_some());
assert!(tx.reverted_after.is_none());
Ok(())
})?;
// Attach more blocks to the parallel chain, making it the longest one
award_block_to_wallet(&chain2, &[], wallet1.clone(), mask1)?;
assert_eq!(chain2.head_header().unwrap().height, bh + 1);
let new_head = chain2
.get_block(&chain2.head_header().unwrap().hash())
.unwrap();
// Input blocks from parallel chain to original chain, updating it as well
// and effectively reverting the transaction
process_block(&chain, block_without.clone()); // This shouldn't update the head
assert_eq!(chain.head_header().unwrap(), block_with.header);
process_block(&chain, new_head.clone()); // But this should!
assert_eq!(chain.head_header().unwrap(), new_head.header);
let bh = bh + 1;
// Check funds have been reverted
owner(Some(wallet2.clone()), mask2, None, |api, m| {
api.scan(m, None, false)?;
let (refreshed, info) = api.retrieve_summary_info(m, true, 1)?;
assert!(refreshed);
assert_eq!(info.last_confirmed_height, bh);
assert_eq!(info.total, 0);
assert_eq!(info.amount_currently_spendable, 0);
assert_eq!(info.amount_reverted, sent);
// check tx log as well
let (_, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert_eq!(txs.len(), 1);
let tx = &txs[0];
assert_eq!(tx.tx_type, libwallet::TxLogEntryType::TxReverted);
assert!(!tx.confirmed);
assert!(tx.reverted_after.is_some());
Ok(())
})?;
stopper2.store(false, Ordering::Relaxed);
Ok((
chain, stopper, sent, bh, tx, wallet1, mask1_i, wallet2, mask2_i,
))
}
fn revert_reconfirm_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
let (chain, stopper, sent, bh, tx, wallet1, mask1_i, wallet2, mask2_i) = revert(test_dir)?;
let mask1 = mask1_i.as_ref();
let mask2 = mask2_i.as_ref();
// Include the tx into the chain again, the tx should no longer be reverted
award_block_to_wallet(&chain, &[tx], wallet1.clone(), mask1)?;
let bh = bh + 1;
// Check funds have been confirmed again
owner(Some(wallet2.clone()), mask2, None, |api, m| {
let (refreshed, info) = api.retrieve_summary_info(m, true, 1)?;
assert!(refreshed);
assert_eq!(info.last_confirmed_height, bh);
assert_eq!(info.total, sent);
assert_eq!(info.amount_currently_spendable, sent);
assert_eq!(info.amount_reverted, 0);
// check tx log as well
let (_, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert_eq!(txs.len(), 1);
let tx = &txs[0];
assert_eq!(tx.tx_type, libwallet::TxLogEntryType::TxReceived);
assert!(tx.confirmed);
assert!(tx.reverted_after.is_none());
Ok(())
})?;
// let logging finish
stopper.store(false, Ordering::Relaxed);
thread::sleep(Duration::from_millis(1000));
Ok(())
}
fn revert_cancel_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
let (_, stopper, sent, bh, _, _, _, wallet2, mask2_i) = revert(test_dir)?;
let mask2 = mask2_i.as_ref();
// Cancelling tx
owner(Some(wallet2.clone()), mask2, None, |api, m| {
// Sanity check
let (refreshed, info) = api.retrieve_summary_info(m, true, 1)?;
assert!(refreshed);
assert_eq!(info.last_confirmed_height, bh);
assert_eq!(info.total, 0);
assert_eq!(info.amount_currently_spendable, 0);
assert_eq!(info.amount_reverted, sent);
let (_, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert_eq!(txs.len(), 1);
let tx = &txs[0];
// Cancel
api.cancel_tx(m, Some(tx.id), None)?;
// Check updated summary info
let (refreshed, info) = api.retrieve_summary_info(m, true, 1)?;
assert!(refreshed);
assert_eq!(info.last_confirmed_height, bh);
assert_eq!(info.total, 0);
assert_eq!(info.amount_currently_spendable, 0);
assert_eq!(info.amount_reverted, 0);
// Check updated tx log
let (_, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert_eq!(txs.len(), 1);
let tx = &txs[0];
assert_eq!(tx.tx_type, libwallet::TxLogEntryType::TxReceivedCancelled);
Ok(())
})?;
// let logging finish
stopper.store(false, Ordering::Relaxed);
thread::sleep(Duration::from_millis(1000));
Ok(())
}
#[test]
fn tx_revert_reconfirm() {
let test_dir = "test_output/revert_tx";
setup(test_dir);
if let Err(e) = revert_reconfirm_impl(test_dir) {
panic!("Libwallet Error: {}", e);
}
clean_output_dir(test_dir);
}
#[test]
fn tx_revert_cancel() {
let test_dir = "test_output/revert_tx_cancel";
setup(test_dir);
if let Err(e) = revert_cancel_impl(test_dir) {
panic!("Libwallet Error: {}", e);
}
clean_output_dir(test_dir);
}
-148
View File
@@ -1,148 +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.
//! Test a wallet sending to self
#[macro_use]
extern crate log;
extern crate grin_wallet_controller as wallet;
extern crate grin_wallet_impls as impls;
use grin_core as core;
use grin_wallet_libwallet as libwallet;
use impls::test_framework::{self, LocalWalletClient};
use libwallet::InitTxArgs;
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
#[macro_use]
mod common;
use common::{clean_output_dir, create_wallet_proxy, setup};
/// self send impl
fn self_send_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// Create a new proxy to simulate server and wallet responses
let mut wallet_proxy = create_wallet_proxy(test_dir);
let chain = wallet_proxy.chain.clone();
let stopper = wallet_proxy.running.clone();
// Create a new wallet test client, and set its queues to communicate with the
// proxy
create_wallet_and_add!(
client1,
wallet1,
mask1_i,
test_dir,
"wallet1",
None,
&mut wallet_proxy,
true
);
let mask1 = (&mask1_i).as_ref();
// Set the wallet proxy listener running
thread::spawn(move || {
if let Err(e) = wallet_proxy.run() {
error!("Wallet Proxy error: {}", e);
}
});
// few values to keep things shorter
let reward = core::consensus::REWARD;
// add some accounts
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
api.create_account_path(m, "mining")?;
api.create_account_path(m, "listener")?;
Ok(())
})?;
// Get some mining done
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("mining")?;
}
let mut bh = 10u64;
let _ =
test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false);
// Should have 5 in account1 (5 spendable), 5 in account (2 spendable)
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
assert!(wallet1_refreshed);
assert_eq!(wallet1_info.last_confirmed_height, bh);
assert_eq!(wallet1_info.total, bh * reward);
// send to send
let args = InitTxArgs {
src_acct_name: Some("mining".to_owned()),
amount: reward * 2,
minimum_confirmations: 2,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: true,
..Default::default()
};
let mut slate = api.init_send_tx(m, args)?;
api.tx_lock_outputs(m, &slate)?;
// Send directly to self
wallet::controller::foreign_single_use(wallet1.clone(), mask1_i.clone(), |api| {
slate = api.receive_tx(&slate, Some("listener"), None)?;
Ok(())
})?;
slate = api.finalize_tx(m, &slate)?;
api.post_tx(m, &slate, false)?; // mines a block
bh += 1;
Ok(())
})?;
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false);
bh += 3;
// Check total in mining account
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
assert!(wallet1_refreshed);
assert_eq!(wallet1_info.last_confirmed_height, bh);
assert_eq!(wallet1_info.total, bh * reward - reward * 2);
Ok(())
})?;
// Check total in 'listener' account
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("listener")?;
}
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
assert!(wallet1_refreshed);
assert_eq!(wallet1_info.last_confirmed_height, bh);
assert_eq!(wallet1_info.total, 2 * reward);
Ok(())
})?;
// let logging finish
stopper.store(false, Ordering::Relaxed);
thread::sleep(Duration::from_millis(1000));
Ok(())
}
#[test]
fn wallet_self_send() {
let test_dir = "test_output/self_send";
setup(test_dir);
if let Err(e) = self_send_test_impl(test_dir) {
panic!("Libwallet Error: {}", e);
}
clean_output_dir(test_dir);
}
-587
View File
@@ -1,587 +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.
//! Test a wallet file send/recieve
#[macro_use]
extern crate log;
extern crate grin_wallet_controller as wallet;
extern crate grin_wallet_impls as impls;
use grin_core as core;
use grin_wallet_libwallet as libwallet;
use impls::test_framework::{self, LocalWalletClient};
use impls::{PathToSlatepack, SlatePutter as _};
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
use grin_wallet_libwallet::{
InitTxArgs, IssueInvoiceTxArgs, Slate, Slatepack, SlatepackAddress, Slatepacker,
SlatepackerArgs,
};
use ed25519_dalek::PublicKey as edDalekPublicKey;
use ed25519_dalek::SecretKey as edDalekSecretKey;
#[macro_use]
mod common;
use common::{clean_output_dir, create_wallet_proxy, setup};
fn output_slatepack(
slate: &Slate,
file: &str,
armored: bool,
use_bin: bool,
sender: Option<SlatepackAddress>,
recipients: Vec<SlatepackAddress>,
) -> Result<(), libwallet::Error> {
let packer = Slatepacker::new(SlatepackerArgs {
sender,
recipients,
dec_key: None,
});
let mut file = file.into();
if armored {
file = format!("{}.armored", file);
}
PathToSlatepack::new(file.into(), &packer, armored).put_tx(&slate, use_bin)
}
fn slate_from_packed(
file: &str,
armored: bool,
dec_key: Option<&edDalekSecretKey>,
) -> Result<(Slatepack, Slate), libwallet::Error> {
let packer = Slatepacker::new(SlatepackerArgs {
sender: None,
recipients: vec![],
dec_key,
});
let mut file = file.into();
if armored {
file = format!("{}.armored", file);
}
let slatepack = PathToSlatepack::new(file.into(), &packer, armored).get_slatepack(true)?;
Ok((slatepack.clone(), packer.get_slate(&slatepack)?))
}
/// self send impl
fn slatepack_exchange_test_impl(
test_dir: &'static str,
use_bin: bool,
use_armored: bool,
use_encryption: bool,
) -> Result<(), libwallet::Error> {
// Create a new proxy to simulate server and wallet responses
let mut wallet_proxy = create_wallet_proxy(test_dir);
let chain = wallet_proxy.chain.clone();
let stopper = wallet_proxy.running.clone();
// Create a new wallet test client, and set its queues to communicate with the
// proxy
create_wallet_and_add!(
client1,
wallet1,
mask1_i,
test_dir,
"wallet1",
None,
&mut wallet_proxy,
false
);
let mask1 = (&mask1_i).as_ref();
create_wallet_and_add!(
client2,
wallet2,
mask2_i,
test_dir,
"wallet2",
None,
&mut wallet_proxy,
false
);
let mask2 = (&mask2_i).as_ref();
// Set the wallet proxy listener running
thread::spawn(move || {
if let Err(e) = wallet_proxy.run() {
error!("Wallet Proxy error: {}", e);
}
});
// few values to keep things shorter
let reward = core::consensus::REWARD;
// add some accounts
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
api.create_account_path(m, "mining")?;
api.create_account_path(m, "listener")?;
Ok(())
})?;
// add some accounts
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
api.create_account_path(m, "account1")?;
api.create_account_path(m, "account2")?;
Ok(())
})?;
// Get some mining done
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("mining")?;
}
let mut bh = 10u64;
let _ =
test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false);
let (recipients_1, dec_key_1, sender_1) = match use_encryption {
true => {
let mut rec_address = SlatepackAddress::random();
let mut sec_key = edDalekSecretKey::from_bytes(&[0u8; 32]).unwrap();
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
sec_key = api.get_slatepack_secret_key(m, 0)?;
let pub_key = edDalekPublicKey::from(&sec_key);
rec_address = SlatepackAddress::new(&pub_key);
Ok(())
})?;
(
vec![rec_address.clone()],
Some(sec_key),
Some(rec_address.clone()),
)
}
false => (vec![], None, None),
};
let (recipients_2, dec_key_2, sender_2) = match use_encryption {
true => {
let mut rec_address = SlatepackAddress::random();
let mut sec_key = edDalekSecretKey::from_bytes(&[0u8; 32]).unwrap();
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
sec_key = api.get_slatepack_secret_key(m, 0)?;
let pub_key = edDalekPublicKey::from(&sec_key);
rec_address = SlatepackAddress::new(&pub_key);
Ok(())
})?;
(
vec![rec_address.clone()],
Some(sec_key),
Some(rec_address.clone()),
)
}
false => (vec![], None, None),
};
let (send_file, receive_file, final_file) = match use_bin {
false => (
format!("{}/standard_S1.slatepack", test_dir),
format!("{}/standard_S2.slatepack", test_dir),
format!("{}/standard_S3.slatepack", test_dir),
),
true => (
format!("{}/standard_S1.slatepackbin", test_dir),
format!("{}/standard_S2.slatepackbin", test_dir),
format!("{}/standard_S3.slatepackbin", test_dir),
),
};
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
assert!(wallet1_refreshed);
assert_eq!(wallet1_info.last_confirmed_height, bh);
assert_eq!(wallet1_info.total, bh * reward);
// send to send
let args = InitTxArgs {
src_acct_name: Some("mining".to_owned()),
amount: reward * 2,
minimum_confirmations: 2,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: true,
..Default::default()
};
let slate = api.init_send_tx(m, args)?;
// output tx file
output_slatepack(
&slate,
&send_file,
use_armored,
use_bin,
sender_1.clone(),
recipients_2.clone(),
)?;
api.tx_lock_outputs(m, &slate)?;
Ok(())
})?;
// Get some mining done
{
wallet_inst!(wallet2, w);
w.set_parent_key_id_by_name("account1")?;
}
let (mut slatepack, mut slate) =
slate_from_packed(&send_file, use_armored, (&dec_key_2).as_ref())?;
// wallet 2 receives file, completes, sends file back
wallet::controller::foreign_single_use(wallet2.clone(), mask2_i.clone(), |api| {
slate = api.receive_tx(&slate, None, None)?;
output_slatepack(
&slate,
&receive_file,
use_armored,
use_bin,
// re-encrypt for sender!
sender_2.clone(),
match slatepack.sender.clone() {
Some(s) => vec![s.clone()],
None => vec![],
},
)?;
Ok(())
})?;
// wallet 1 finalises and posts
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (_, mut slate) = slate_from_packed(&receive_file, use_armored, (&dec_key_1).as_ref())?;
slate = api.finalize_tx(m, &slate)?;
// Output final file for reference
output_slatepack(&slate, &final_file, use_armored, use_bin, None, vec![])?;
api.post_tx(m, &slate, false)?;
bh += 1;
Ok(())
})?;
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false);
bh += 3;
// Check total in mining account
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
assert!(wallet1_refreshed);
assert_eq!(wallet1_info.last_confirmed_height, bh);
assert_eq!(wallet1_info.total, bh * reward - reward * 2);
Ok(())
})?;
// Check total in 'wallet 2' account
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?;
assert!(wallet2_refreshed);
assert_eq!(wallet2_info.last_confirmed_height, bh);
assert_eq!(wallet2_info.total, 2 * reward);
Ok(())
})?;
// Now other types of exchange, for reference
// Invoice transaction
let (send_file, receive_file, final_file) = match use_bin {
false => (
format!("{}/invoice_I1.slatepack", test_dir),
format!("{}/invoice_I2.slatepack", test_dir),
format!("{}/invoice_I3.slatepack", test_dir),
),
true => (
format!("{}/invoice_I1.slatepackbin", test_dir),
format!("{}/invoice_I2.slatepackbin", test_dir),
format!("{}/invoice_I3.slatepackbin", test_dir),
),
};
let mut slate = Slate::blank(2, true);
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
let args = IssueInvoiceTxArgs {
amount: 1000000000,
..Default::default()
};
slate = api.issue_invoice_tx(m, args)?;
output_slatepack(
&slate,
&send_file,
use_armored,
use_bin,
sender_2.clone(),
recipients_1.clone(),
)?;
Ok(())
})?;
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let args = InitTxArgs {
src_acct_name: None,
amount: slate.amount,
minimum_confirmations: 2,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: true,
..Default::default()
};
let res = slate_from_packed(&send_file, use_armored, (&dec_key_1).as_ref())?;
slatepack = res.0;
slate = res.1;
slate = api.process_invoice_tx(m, &slate, args)?;
api.tx_lock_outputs(m, &slate)?;
output_slatepack(
&slate,
&receive_file,
use_armored,
use_bin,
sender_1.clone(),
match slatepack.sender.clone() {
Some(s) => vec![s.clone()],
None => vec![],
},
)?;
Ok(())
})?;
wallet::controller::foreign_single_use(wallet2.clone(), mask2_i.clone(), |api| {
// Wallet 2 receives the invoice transaction
let res = slate_from_packed(&receive_file, use_armored, (&dec_key_2).as_ref())?;
slate = res.1;
slate = api.finalize_tx(&slate, false)?;
output_slatepack(&slate, &final_file, use_armored, use_bin, None, vec![])?;
Ok(())
})?;
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
api.post_tx(m, &slate, false)?;
Ok(())
})?;
// Standard, with payment proof
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false);
let (send_file, receive_file, final_file) = match use_bin {
false => (
format!("{}/standard_pp_S1.slatepack", test_dir),
format!("{}/standard_pp_S2.slatepack", test_dir),
format!("{}/standard_pp_S3.slatepack", test_dir),
),
true => (
format!("{}/standard_pp_S1.slatepackbin", test_dir),
format!("{}/standard_pp_S2.slatepackbin", test_dir),
format!("{}/standard_pp_S3.slatepackbin", test_dir),
),
};
let mut slate = Slate::blank(2, true);
let mut address = None;
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
address = Some(api.get_slatepack_address(m, 0)?);
Ok(())
})?;
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
// send to send
let args = InitTxArgs {
src_acct_name: Some("mining".to_owned()),
amount: reward,
minimum_confirmations: 2,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: true,
payment_proof_recipient_address: address.clone(),
..Default::default()
};
let slate = api.init_send_tx(m, args)?;
output_slatepack(
&slate,
&send_file,
use_armored,
use_bin,
sender_1,
recipients_2.clone(),
)?;
api.tx_lock_outputs(m, &slate)?;
Ok(())
})?;
wallet::controller::foreign_single_use(wallet2.clone(), mask2_i.clone(), |api| {
let res = slate_from_packed(&send_file, use_armored, (&dec_key_2).as_ref())?;
let slatepack = res.0;
slate = res.1;
slate = api.receive_tx(&slate, None, None)?;
output_slatepack(
&slate,
&receive_file,
use_armored,
use_bin,
sender_2,
match slatepack.sender {
Some(s) => vec![s.clone()],
None => vec![],
},
)?;
Ok(())
})?;
// wallet 1 finalises and posts
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let res = slate_from_packed(&receive_file, use_armored, (&dec_key_1).as_ref())?;
slate = res.1;
slate = api.finalize_tx(m, &slate)?;
// Output final file for reference
output_slatepack(&slate, &final_file, use_armored, use_bin, None, vec![])?;
api.post_tx(m, &slate, false)?;
bh += 1;
Ok(())
})?;
// let logging finish
stopper.store(false, Ordering::Relaxed);
thread::sleep(Duration::from_millis(200));
Ok(())
}
/// Exercise slate encryption/decryption via the API,
/// Since doctests don't cover encryption
fn slatepack_api_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// Create a new proxy to simulate server and wallet responses
let mut wallet_proxy = create_wallet_proxy(test_dir);
let chain = wallet_proxy.chain.clone();
let stopper = wallet_proxy.running.clone();
// Create a new wallet test client, and set its queues to communicate with the
// proxy
create_wallet_and_add!(
client1,
wallet1,
mask1_i,
test_dir,
"wallet1",
None,
&mut wallet_proxy,
false
);
let mask1 = (&mask1_i).as_ref();
// Set the wallet proxy listener running
thread::spawn(move || {
if let Err(e) = wallet_proxy.run() {
error!("Wallet Proxy error: {}", e);
}
});
// few values to keep things shorter
let reward = core::consensus::REWARD;
// Get some mining done
let bh = 6u64;
let _ =
test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false);
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let args = InitTxArgs {
src_acct_name: Some("mining".to_owned()),
amount: reward * 2,
minimum_confirmations: 2,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: true,
..Default::default()
};
let slate = api.init_send_tx(m, args)?;
// create an encrypted slatepack (just encrypted for self)
let enc_addr = api.get_slatepack_address(m, 0)?;
let slatepack = api.create_slatepack_message(m, &slate, Some(0), vec![enc_addr])?;
println!("{}", slatepack);
let slatepack_raw = api.decode_slatepack_message(m, slatepack.clone(), vec![0])?;
println!("{}", slatepack_raw);
let decoded_slate = api.slate_from_slatepack_message(m, slatepack, vec![0])?;
println!("{}", decoded_slate);
Ok(())
})?;
// let logging finish
stopper.store(false, Ordering::Relaxed);
thread::sleep(Duration::from_millis(200));
Ok(())
}
#[test]
fn slatepack_exchange_json() {
let test_dir = "test_output/slatepack_exchange_json";
setup(test_dir);
// Json output
if let Err(e) = slatepack_exchange_test_impl(test_dir, false, false, false) {
panic!("Libwallet Error: {}", e);
}
clean_output_dir(test_dir);
}
#[test]
fn slatepack_exchange_bin() {
let test_dir = "test_output/slatepack_exchange_bin";
setup(test_dir);
// Bin output
if let Err(e) = slatepack_exchange_test_impl(test_dir, true, false, false) {
panic!("Libwallet Error: {}", e);
}
clean_output_dir(test_dir);
}
#[test]
fn slatepack_exchange_armored() {
let test_dir = "test_output/slatepack_exchange_armored";
setup(test_dir);
// Bin output
if let Err(e) = slatepack_exchange_test_impl(test_dir, true, true, true) {
panic!("Libwallet Error: {}", e);
}
clean_output_dir(test_dir);
}
#[test]
fn slatepack_exchange_json_enc() {
let test_dir = "test_output/slatepack_exchange_json_enc";
setup(test_dir);
// Json output
if let Err(e) = slatepack_exchange_test_impl(test_dir, false, false, true) {
panic!("Libwallet Error: {}", e);
}
clean_output_dir(test_dir);
}
#[test]
fn slatepack_exchange_bin_enc() {
let test_dir = "test_output/slatepack_exchange_bin_enc";
setup(test_dir);
// Bin output
if let Err(e) = slatepack_exchange_test_impl(test_dir, true, false, true) {
panic!("Libwallet Error: {}", e);
}
clean_output_dir(test_dir);
}
#[test]
fn slatepack_exchange_armored_enc() {
let test_dir = "test_output/slatepack_exchange_armored_enc";
setup(test_dir);
// Bin output
if let Err(e) = slatepack_exchange_test_impl(test_dir, true, true, true) {
panic!("Libwallet Error: {}", e);
}
clean_output_dir(test_dir);
}
#[test]
fn slatepack_api() {
let test_dir = "test_output/slatepack_api";
setup(test_dir);
// Json output
if let Err(e) = slatepack_api_impl(test_dir) {
panic!("Libwallet Error: {}", e);
}
clean_output_dir(test_dir);
}
-601
View File
@@ -1,601 +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.
//! tests for transactions building within core::libtx
#[macro_use]
extern crate log;
extern crate grin_wallet_controller as wallet;
extern crate grin_wallet_impls as impls;
extern crate grin_wallet_libwallet as libwallet;
use grin_core as core;
use self::core::core::transaction;
use self::core::global;
use self::libwallet::{InitTxArgs, OutputStatus, Slate, SlateState};
use impls::test_framework::{self, LocalWalletClient};
use std::convert::TryInto;
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
mod common;
use common::{clean_output_dir, create_wallet_proxy, setup};
/// Exercises the Transaction API fully with a test NodeClient operating
/// directly on a chain instance
/// Callable with any type of wallet
fn basic_transaction_api(test_dir: &'static str) -> Result<(), libwallet::Error> {
// Create a new proxy to simulate server and wallet responses
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,
true
);
let mask1 = (&mask1_i).as_ref();
println!("Mask1: {:?}", mask1);
create_wallet_and_add!(
client2,
wallet2,
mask2_i,
test_dir,
"wallet2",
None,
&mut wallet_proxy,
false
);
let mask2 = (&mask2_i).as_ref();
println!("Mask2: {:?}", mask2);
// Set the wallet proxy listener running
thread::spawn(move || {
if let Err(e) = wallet_proxy.run() {
error!("Wallet Proxy error: {}", e);
}
});
// few values to keep things shorter
let reward = core::consensus::REWARD;
let cm = global::coinbase_maturity();
// mine a few blocks
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 10, false);
// Check wallet 1 contents are as expected
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
debug!(
"Wallet 1 Info Pre-Transaction, after {} blocks: {:?}",
wallet1_info.last_confirmed_height, wallet1_info
);
assert!(wallet1_refreshed);
assert_eq!(
wallet1_info.amount_currently_spendable,
(wallet1_info.last_confirmed_height - cm) * reward
);
assert_eq!(wallet1_info.amount_immature, cm * reward);
Ok(())
})?;
// assert wallet contents
// and a single use api for a send command
let amount = 60_000_000_000;
let mut slate = Slate::blank(1, false);
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| {
// note this will increment the block count as part of the transaction "Posting"
let args = InitTxArgs {
src_acct_name: None,
amount: amount,
minimum_confirmations: 2,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: true,
..Default::default()
};
let slate_i = sender_api.init_send_tx(m, args)?;
assert_eq!(slate_i.state, SlateState::Standard1);
// Check we are creating a tx with the expected lock_height of 0.
// We will check this produces a Plain kernel later.
assert_eq!(0, slate.kernel_features);
slate = client1.send_tx_slate_direct("wallet2", &slate_i)?;
assert_eq!(slate.state, SlateState::Standard2);
sender_api.tx_lock_outputs(m, &slate)?;
slate = sender_api.finalize_tx(m, &slate)?;
assert_eq!(slate.state, SlateState::Standard3);
// Check we have a single kernel and that it is a Plain kernel (no lock_height).
// fees for 7 inputs, 2 outputs, 1 kernel (weight 52)
assert_eq!(slate.tx_or_err()?.kernels().len(), 1);
assert_eq!(
slate
.tx_or_err()?
.kernels()
.first()
.map(|k| k.features)
.unwrap(),
transaction::KernelFeatures::Plain {
fee: 26_000_000.into()
}
);
Ok(())
})?;
// Check transaction log for wallet 1
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert!(refreshed);
let fee = core::libtx::tx_fee(
wallet1_info.last_confirmed_height as usize - cm as usize,
2,
1,
);
// we should have a transaction entry for this slate
let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id));
assert!(tx.is_some());
let tx = tx.unwrap();
assert!(!tx.confirmed);
assert!(tx.confirmation_ts.is_none());
assert_eq!(tx.amount_debited - tx.amount_credited, fee + amount);
println!("tx: {:?}", tx);
assert_eq!(Some(fee.try_into().unwrap()), tx.fee);
Ok(())
})?;
// Check transaction log for wallet 2
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert!(refreshed);
// we should have a transaction entry for this slate
let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id));
assert!(tx.is_some());
let tx = tx.unwrap();
assert!(!tx.confirmed);
assert!(tx.confirmation_ts.is_none());
assert_eq!(amount, tx.amount_credited);
assert_eq!(0, tx.amount_debited);
assert_eq!(None, tx.fee);
Ok(())
})?;
// post transaction
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
api.post_tx(m, &slate, false)?;
Ok(())
})?;
// Check wallet 1 contents are as expected
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
debug!(
"Wallet 1 Info Post Transaction, after {} blocks: {:?}",
wallet1_info.last_confirmed_height, wallet1_info
);
let fee = core::libtx::tx_fee(
wallet1_info.last_confirmed_height as usize - 1 - cm as usize,
2,
1,
);
assert!(wallet1_refreshed);
// wallet 1 received fees, so amount should be the same
assert_eq!(
wallet1_info.total,
amount * wallet1_info.last_confirmed_height - amount
);
assert_eq!(
wallet1_info.amount_currently_spendable,
(wallet1_info.last_confirmed_height - cm) * reward - amount - fee
);
assert_eq!(wallet1_info.amount_immature, cm * reward + fee);
// check tx log entry is confirmed
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert!(refreshed);
let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id));
assert!(tx.is_some());
let tx = tx.unwrap();
assert!(tx.confirmed);
assert!(tx.confirmation_ts.is_some());
Ok(())
})?;
// mine a few more blocks
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false);
// refresh wallets and retrieve info/tests for each wallet after maturity
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
debug!("Wallet 1 Info: {:?}", wallet1_info);
assert!(wallet1_refreshed);
assert_eq!(
wallet1_info.total,
amount * wallet1_info.last_confirmed_height - amount
);
assert_eq!(
wallet1_info.amount_currently_spendable,
(wallet1_info.last_confirmed_height - cm - 1) * reward
);
Ok(())
})?;
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?;
assert!(wallet2_refreshed);
assert_eq!(wallet2_info.amount_currently_spendable, amount);
// check tx log entry is confirmed
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert!(refreshed);
let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id));
assert!(tx.is_some());
let tx = tx.unwrap();
assert!(tx.confirmed);
assert!(tx.confirmation_ts.is_some());
Ok(())
})?;
// Estimate fee and locked amount for a transaction
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| {
let init_args = InitTxArgs {
src_acct_name: None,
amount: amount * 2,
minimum_confirmations: 2,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: true,
estimate_only: Some(true),
..Default::default()
};
let est = sender_api.init_send_tx(m, init_args)?;
assert_eq!(est.amount, 600_000_000_000);
// fees for 5 inputs, 2 outputs, 1 kernel (weight 50)
assert_eq!(est.fee_fields.fee(), 25_000_000);
let init_args = InitTxArgs {
src_acct_name: None,
amount: amount * 2,
minimum_confirmations: 2,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: false, //select smallest number
estimate_only: Some(true),
..Default::default()
};
let est = sender_api.init_send_tx(m, init_args)?;
assert_eq!(est.amount, 180_000_000_000);
// fees for 3 inputs, 2 outputs, 1 kernel (weight 48)
assert_eq!(est.fee_fields.fee(), 24_000_000);
Ok(())
})?;
// Send another transaction, but don't post to chain immediately and use
// the stored transaction instead
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| {
// note this will increment the block count as part of the transaction "Posting"
let args = InitTxArgs {
src_acct_name: None,
amount: amount * 2,
minimum_confirmations: 2,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: true,
..Default::default()
};
let slate_i = sender_api.init_send_tx(m, args)?;
slate = client1.send_tx_slate_direct("wallet2", &slate_i)?;
sender_api.tx_lock_outputs(m, &slate)?;
slate = sender_api.finalize_tx(m, &slate)?;
Ok(())
})?;
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| {
let (refreshed, _wallet1_info) = sender_api.retrieve_summary_info(m, true, 1)?;
assert!(refreshed);
let (_, txs) = sender_api.retrieve_txs(m, true, None, None, None)?;
// find the transaction
let tx = txs
.iter()
.find(|t| t.tx_slate_id == Some(slate.id))
.unwrap();
let stored_tx_slate = sender_api
.get_stored_tx(m, None, Some(&tx.tx_slate_id.unwrap()))?
.unwrap();
sender_api.post_tx(m, &stored_tx_slate, false)?;
let (_, wallet1_info) = sender_api.retrieve_summary_info(m, true, 1)?;
// should be mined now
assert_eq!(
wallet1_info.total,
amount * wallet1_info.last_confirmed_height - amount * 3
);
Ok(())
})?;
// mine a few more blocks
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false);
// check wallet2 has stored transaction
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?;
assert!(wallet2_refreshed);
assert_eq!(wallet2_info.amount_currently_spendable, amount * 3);
// check tx log entry is confirmed
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert!(refreshed);
let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id));
assert!(tx.is_some());
let tx = tx.unwrap();
assert!(tx.confirmed);
assert!(tx.confirmation_ts.is_some());
Ok(())
})?;
// try to send a transaction with amount inclusive of fees, but amount too
// small to cover fees. Should fail.
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| {
// note this will increment the block count as part of the transaction "Posting"
let args = InitTxArgs {
src_acct_name: None,
amount: 1,
amount_includes_fee: Some(true),
minimum_confirmations: 2,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: true,
..Default::default()
};
let res = sender_api.init_send_tx(m, args);
assert!(res.is_err());
Ok(())
})?;
// try to build a transaction with amount inclusive of fees. Confirm that tx
// amount + fee is equal to the originally specified amount
let amount = 60_000_000_000;
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| {
// note this will increment the block count as part of the transaction "Posting"
let args = InitTxArgs {
src_acct_name: None,
amount: amount,
amount_includes_fee: Some(true),
minimum_confirmations: 2,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: true,
..Default::default()
};
let slate_i = sender_api.init_send_tx(m, args)?;
assert_eq!(slate_i.state, SlateState::Standard1);
let total_spend: u64 = slate_i.amount + slate_i.fee_fields.fee();
assert_eq!(amount, total_spend);
Ok(())
})?;
// let logging finish
stopper.store(false, Ordering::Relaxed);
thread::sleep(Duration::from_millis(200));
Ok(())
}
/// Test rolling back transactions and outputs when a transaction is never
/// posted to a chain
fn tx_rollback(test_dir: &'static str) -> Result<(), libwallet::Error> {
// Create a new proxy to simulate server and wallet responses
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();
create_wallet_and_add!(
client2,
wallet2,
mask2_i,
test_dir,
"wallet2",
None,
&mut wallet_proxy,
false
);
let mask2 = (&mask2_i).as_ref();
// Set the wallet proxy listener running
thread::spawn(move || {
if let Err(e) = wallet_proxy.run() {
error!("Wallet Proxy error: {}", e);
}
});
// few values to keep things shorter
let reward = core::consensus::REWARD;
let cm = global::coinbase_maturity(); // assume all testing precedes soft fork height
// mine a few blocks
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 5, false);
let amount = 30_000_000_000;
let mut slate = Slate::blank(1, false);
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| {
// note this will increment the block count as part of the transaction "Posting"
let args = InitTxArgs {
src_acct_name: None,
amount: amount,
minimum_confirmations: 2,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: true,
..Default::default()
};
let slate_i = sender_api.init_send_tx(m, args)?;
slate = client1.send_tx_slate_direct("wallet2", &slate_i)?;
sender_api.tx_lock_outputs(m, &slate)?;
slate = sender_api.finalize_tx(m, &slate)?;
Ok(())
})?;
// Check transaction log for wallet 1
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
println!(
"last confirmed height: {}",
wallet1_info.last_confirmed_height
);
assert!(refreshed);
let (_, txs) = api.retrieve_txs(m, true, None, None, None)?;
// we should have a transaction entry for this slate
let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id));
assert!(tx.is_some());
let mut locked_count = 0;
let mut unconfirmed_count = 0;
// get the tx entry, check outputs are as expected
let (_, output_mappings) = api.retrieve_outputs(m, true, false, Some(tx.unwrap().id))?;
for m in output_mappings.clone() {
if m.output.status == OutputStatus::Locked {
locked_count = locked_count + 1;
}
if m.output.status == OutputStatus::Unconfirmed {
unconfirmed_count = unconfirmed_count + 1;
}
}
assert_eq!(output_mappings.len(), 3);
assert_eq!(locked_count, 2);
assert_eq!(unconfirmed_count, 1);
Ok(())
})?;
// Check transaction log for wallet 2
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert!(refreshed);
let mut unconfirmed_count = 0;
let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id));
assert!(tx.is_some());
// get the tx entry, check outputs are as expected
let (_, outputs) = api.retrieve_outputs(m, true, false, Some(tx.unwrap().id))?;
for m in outputs.clone() {
if m.output.status == OutputStatus::Unconfirmed {
unconfirmed_count = unconfirmed_count + 1;
}
}
assert_eq!(outputs.len(), 1);
assert_eq!(unconfirmed_count, 1);
let (refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?;
assert!(refreshed);
assert_eq!(wallet2_info.amount_currently_spendable, 0,);
assert_eq!(wallet2_info.amount_awaiting_finalization, amount);
Ok(())
})?;
// wallet 1 is bold and doesn't ever post the transaction
// mine a few more blocks
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 5, false);
// Wallet 1 decides to roll back instead
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
// can't roll back coinbase
let res = api.cancel_tx(m, Some(1), None);
assert!(res.is_err());
let (_, txs) = api.retrieve_txs(m, true, None, None, None)?;
let tx = txs
.iter()
.find(|t| t.tx_slate_id == Some(slate.id))
.unwrap();
api.cancel_tx(m, Some(tx.id), None)?;
let (refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
assert!(refreshed);
println!(
"last confirmed height: {}",
wallet1_info.last_confirmed_height
);
// check all eligible inputs should be now be spendable
println!("cm: {}", cm);
assert_eq!(
wallet1_info.amount_currently_spendable,
(wallet1_info.last_confirmed_height - cm) * reward
);
// can't roll back again
let res = api.cancel_tx(m, Some(tx.id), None);
assert!(res.is_err());
Ok(())
})?;
// Wallet 2 rolls back
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
let (_, txs) = api.retrieve_txs(m, true, None, None, None)?;
let tx = txs
.iter()
.find(|t| t.tx_slate_id == Some(slate.id))
.unwrap();
api.cancel_tx(m, Some(tx.id), None)?;
let (refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?;
assert!(refreshed);
// check all eligible inputs should be now be spendable
assert_eq!(wallet2_info.amount_currently_spendable, 0,);
assert_eq!(wallet2_info.total, 0,);
// can't roll back again
let res = api.cancel_tx(m, Some(tx.id), None);
assert!(res.is_err());
Ok(())
})?;
// let logging finish
stopper.store(false, Ordering::Relaxed);
thread::sleep(Duration::from_millis(200));
Ok(())
}
#[test]
fn db_wallet_basic_transaction_api() {
let test_dir = "test_output/basic_transaction_api";
setup(test_dir);
if let Err(e) = basic_transaction_api(test_dir) {
panic!("Libwallet Error: {}", e);
}
clean_output_dir(test_dir);
}
#[test]
fn db_wallet_tx_rollback() {
let test_dir = "test_output/tx_rollback";
setup(test_dir);
if let Err(e) = tx_rollback(test_dir) {
panic!("Libwallet Error: {}", e);
}
clean_output_dir(test_dir);
}
-185
View File
@@ -1,185 +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.
//! tests ttl_cutoff blocks
#[macro_use]
extern crate log;
extern crate grin_wallet_controller as wallet;
extern crate grin_wallet_impls as impls;
extern crate grin_wallet_util;
use grin_wallet_libwallet as libwallet;
use impls::test_framework::{self, LocalWalletClient};
use libwallet::{InitTxArgs, Slate, TxLogEntryType};
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
#[macro_use]
mod common;
use common::{clean_output_dir, create_wallet_proxy, setup};
/// Test cutoff block times
fn ttl_cutoff_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// Create a new proxy to simulate server and wallet responses
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();
create_wallet_and_add!(
client2,
wallet2,
mask2_i,
test_dir,
"wallet2",
None,
&mut wallet_proxy,
false
);
let mask2 = (&mask2_i).as_ref();
// Set the wallet proxy listener running
thread::spawn(move || {
if let Err(e) = wallet_proxy.run() {
error!("Wallet Proxy error: {}", e);
}
});
// few values to keep things shorter
// Do some mining
let bh = 10u64;
let _ =
test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false);
let amount = 60_000_000_000;
let mut slate = Slate::blank(1, false);
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| {
// note this will increment the block count as part of the transaction "Posting"
let args = InitTxArgs {
src_acct_name: None,
amount: amount,
minimum_confirmations: 2,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: true,
ttl_blocks: Some(2),
..Default::default()
};
let slate_i = sender_api.init_send_tx(m, args)?;
slate = client1.send_tx_slate_direct("wallet2", &slate_i)?;
sender_api.tx_lock_outputs(m, &slate)?;
let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id), None)?;
let tx = txs[0].clone();
assert_eq!(tx.ttl_cutoff_height, Some(12));
Ok(())
})?;
// Now mine past the block, and check again. Transaction should be gone.
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 2, false);
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| {
let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id), None)?;
let tx = txs[0].clone();
assert_eq!(tx.ttl_cutoff_height, Some(12));
assert!(tx.tx_type == TxLogEntryType::TxSentCancelled);
Ok(())
})?;
// Should also be gone in wallet 2, and output gone
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |sender_api, m| {
let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id), None)?;
let tx = txs[0].clone();
let outputs = sender_api.retrieve_outputs(m, false, true, None)?.1;
assert_eq!(outputs.len(), 0);
assert_eq!(tx.ttl_cutoff_height, Some(12));
assert!(tx.tx_type == TxLogEntryType::TxReceivedCancelled);
Ok(())
})?;
// try again, except try and send off the transaction for completion beyond the expiry
let mut slate = Slate::blank(1, false);
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| {
// note this will increment the block count as part of the transaction "Posting"
let args = InitTxArgs {
src_acct_name: None,
amount: amount,
minimum_confirmations: 2,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: true,
ttl_blocks: Some(2),
..Default::default()
};
let slate_i = sender_api.init_send_tx(m, args)?;
sender_api.tx_lock_outputs(m, &slate_i)?;
slate = slate_i;
let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id), None)?;
let tx = txs[0].clone();
assert_eq!(tx.ttl_cutoff_height, Some(14));
Ok(())
})?;
// Mine past the ttl block and try to send
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 2, false);
// Wallet 2 will need to have updated past the TTL
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |sender_api, m| {
let (_, _) = sender_api.retrieve_txs(m, true, None, Some(slate.id), None)?;
Ok(())
})?;
// And when wallet 1 sends, should be rejected
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |_sender_api, _m| {
let res = client1.send_tx_slate_direct("wallet2", &slate);
println!("Send after TTL result is: {:?}", res);
assert!(res.is_err());
Ok(())
})?;
// let logging finish
stopper.store(false, Ordering::Relaxed);
thread::sleep(Duration::from_millis(200));
Ok(())
}
#[test]
fn ttl_cutoff() {
let test_dir = "test_output/ttl_cutoff";
setup(test_dir);
if let Err(e) = ttl_cutoff_test_impl(test_dir) {
panic!("Libwallet Error: {}", e);
}
clean_output_dir(test_dir);
}
-360
View File
@@ -1,360 +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.
//! tests of advanced TX filtering
#[macro_use]
extern crate log;
extern crate grin_wallet_controller as wallet;
extern crate grin_wallet_impls as impls;
extern crate grin_wallet_libwallet as libwallet;
use grin_core as core;
use grin_keychain as keychain;
use grin_util as util;
use libwallet::{RetrieveTxQueryArgs, RetrieveTxQuerySortField};
use self::libwallet::{InitTxArgs, Slate};
use impls::test_framework::{self, LocalWalletClient};
use std::sync::{atomic::Ordering, Arc};
use std::thread;
use std::time::Duration;
use util::secp::key::SecretKey;
use util::Mutex;
use self::keychain::ExtKeychain;
use self::libwallet::WalletInst;
use impls::DefaultLCProvider;
mod common;
use common::{clean_output_dir, create_wallet_proxy, setup};
fn test_wallet_tx_filtering(
wallet: Arc<
Mutex<
Box<
dyn WalletInst<
'static,
DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
LocalWalletClient,
ExtKeychain,
>,
>,
>,
>,
mask: Option<&SecretKey>,
) -> Result<(), libwallet::Error> {
wallet::controller::owner_single_use(Some(wallet.clone()), mask, None, |api, _m| {
let mut tx_query_args = RetrieveTxQueryArgs::default();
tx_query_args.min_id = Some(5);
// Min ID
let tx_results = api
.retrieve_txs(mask, true, None, None, Some(tx_query_args))?
.1;
assert_eq!(tx_results[0].id, 5);
assert_eq!(tx_results[tx_results.len() - 1].id, 33);
// Max ID
let mut tx_query_args = RetrieveTxQueryArgs::default();
tx_query_args.min_id = Some(5);
tx_query_args.max_id = Some(20);
let tx_results = api
.retrieve_txs(mask, true, None, None, Some(tx_query_args))?
.1;
assert_eq!(tx_results[0].id, 5);
assert_eq!(tx_results[tx_results.len() - 1].id, 20);
// Exclude 1 cancelled
let mut tx_query_args = RetrieveTxQueryArgs::default();
tx_query_args.exclude_cancelled = Some(true);
tx_query_args.min_id = Some(5);
tx_query_args.max_id = Some(50);
let tx_results = api
.retrieve_txs(mask, true, None, None, Some(tx_query_args))?
.1;
assert_eq!(tx_results.len(), 28);
// Exclude 1 cancelled, show confirmed only
let mut tx_query_args = RetrieveTxQueryArgs::default();
tx_query_args.exclude_cancelled = Some(true);
tx_query_args.include_confirmed_only = Some(true);
tx_query_args.min_id = Some(5);
tx_query_args.max_id = Some(50);
let tx_results = api
.retrieve_txs(mask, true, None, None, Some(tx_query_args))?
.1;
assert_eq!(tx_results.len(), 14);
// show outstanding only (including cancelled)
let mut tx_query_args = RetrieveTxQueryArgs::default();
tx_query_args.exclude_cancelled = Some(false);
tx_query_args.include_outstanding_only = Some(true);
tx_query_args.min_id = Some(5);
tx_query_args.max_id = Some(50);
let tx_results = api
.retrieve_txs(mask, true, None, None, Some(tx_query_args))?
.1;
assert_eq!(tx_results.len(), 15);
// outstanding only and confirmed only should give empty set
let mut tx_query_args = RetrieveTxQueryArgs::default();
tx_query_args.exclude_cancelled = Some(false);
tx_query_args.include_outstanding_only = Some(true);
tx_query_args.include_confirmed_only = Some(true);
tx_query_args.min_id = Some(5);
tx_query_args.max_id = Some(50);
let tx_results = api
.retrieve_txs(mask, true, None, None, Some(tx_query_args))?
.1;
assert_eq!(tx_results.len(), 0);
// include sent only
let mut tx_query_args = RetrieveTxQueryArgs::default();
tx_query_args.include_sent_only = Some(true);
let tx_results = api
.retrieve_txs(mask, true, None, None, Some(tx_query_args))?
.1;
assert_eq!(tx_results.len(), 15);
// include received only (none in this set)
let mut tx_query_args = RetrieveTxQueryArgs::default();
tx_query_args.include_received_only = Some(true);
let tx_results = api
.retrieve_txs(mask, true, None, None, Some(tx_query_args))?
.1;
assert_eq!(tx_results.len(), 0);
// include reverted only (none in this set)
let mut tx_query_args = RetrieveTxQueryArgs::default();
tx_query_args.include_reverted_only = Some(true);
let tx_results = api
.retrieve_txs(mask, true, None, None, Some(tx_query_args))?
.1;
assert_eq!(tx_results.len(), 0);
// include coinbase only
let mut tx_query_args = RetrieveTxQueryArgs::default();
tx_query_args.include_coinbase_only = Some(true);
let tx_results = api
.retrieve_txs(mask, true, None, None, Some(tx_query_args))?
.1;
assert_eq!(tx_results.len(), 19);
// Amounts
let mut tx_query_args = RetrieveTxQueryArgs::default();
tx_query_args.min_amount = Some(60_000_000_000 - 59_963_300_000);
let tx_results = api
.retrieve_txs(mask, true, None, None, Some(tx_query_args))?
.1;
assert_eq!(tx_results.len(), 27);
// amount, should see as above with coinbases excluded
let mut tx_query_args = RetrieveTxQueryArgs::default();
tx_query_args.min_amount = Some(60_000_000_000 - 59_963_300_000);
tx_query_args.max_amount = Some(60_000_000_000 - 1);
let tx_results = api
.retrieve_txs(mask, true, None, None, Some(tx_query_args))?
.1;
assert_eq!(tx_results.len(), 8);
// Amount - should only see coinbase (incoming)
let mut tx_query_args = RetrieveTxQueryArgs::default();
tx_query_args.min_amount = Some(60_000_000_000);
let tx_results = api
.retrieve_txs(mask, true, None, None, Some(tx_query_args))?
.1;
assert_eq!(tx_results.len(), 19);
// sort order
let mut tx_query_args = RetrieveTxQueryArgs::default();
tx_query_args.sort_order = Some(libwallet::RetrieveTxQuerySortOrder::Desc);
let tx_results = api
.retrieve_txs(mask, true, None, None, Some(tx_query_args))?
.1;
assert_eq!(tx_results[0].id, 33);
assert_eq!(tx_results[tx_results.len() - 1].id, 0);
// change sort field to amount desc, should have coinbases first
let mut tx_query_args = RetrieveTxQueryArgs::default();
tx_query_args.sort_order = Some(libwallet::RetrieveTxQuerySortOrder::Desc);
tx_query_args.sort_field = Some(RetrieveTxQuerySortField::TotalAmount);
let tx_results = api
.retrieve_txs(mask, true, None, None, Some(tx_query_args))?
.1;
assert_eq!(tx_results[0].amount_credited, 60_000_000_000);
/*for entry in tx_results.iter() {
println!("{:?}", entry);
}*/
Ok(())
})?;
Ok(())
}
/// Builds a wallet + chain with a few transactions, and return wallet for further testing
fn build_chain_for_tx_filtering(
test_dir: &'static str,
block_height: usize,
) -> Result<(), libwallet::Error> {
// Create a new proxy to simulate server and wallet responses
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,
true
);
let mask1 = (&mask1_i).as_ref();
debug!("Mask1: {:?}", mask1);
create_wallet_and_add!(
client2,
wallet2,
mask2_i,
test_dir,
"wallet2",
None,
&mut wallet_proxy,
false
);
let mask2 = (&mask2_i).as_ref();
debug!("Mask2: {:?}", mask2);
// Set the wallet proxy listener running
thread::spawn(move || {
if let Err(e) = wallet_proxy.run() {
error!("Wallet Proxy error: {}", e);
}
});
// Stop the scanning updater threads because it extends the time needed to build the chain
// exponentially
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, _m| {
api.stop_updater()?;
Ok(())
})?;
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, _m| {
api.stop_updater()?;
Ok(())
})?;
// few values to keep things shorter
let reward = core::consensus::REWARD;
// Start off with a few blocks
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false);
for i in 0..block_height {
let mut wallet_1_has_funds = false;
// Check wallet 1 contents
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
debug!(
"Wallet 1 spendable - {}",
wallet1_info.amount_currently_spendable
);
if wallet1_info.amount_currently_spendable > reward {
wallet_1_has_funds = true;
}
Ok(())
})?;
if !wallet_1_has_funds {
let _ =
test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 1, false);
continue;
}
// send a random tx
let num_txs = 1;
for _ in 0..num_txs {
let amount: u64 = i as u64 * 1_000_000;
let mut slate = Slate::blank(1, false);
debug!("Creating TX for {}", amount);
wallet::controller::owner_single_use(
Some(wallet1.clone()),
mask1,
None,
|sender_api, m| {
// note this will increment the block count as part of the transaction "Posting"
let args = InitTxArgs {
src_acct_name: None,
amount: amount,
minimum_confirmations: 1,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: false,
..Default::default()
};
let slate_i = sender_api.init_send_tx(m, args)?;
slate = client1.send_tx_slate_direct("wallet2", &slate_i)?;
sender_api.tx_lock_outputs(m, &slate)?;
slate = sender_api.finalize_tx(m, &slate)?;
Ok(())
},
)?;
}
}
// Cancel a tx for filtering testing
let amount: u64 = 1_000_000;
let mut slate = Slate::blank(1, false);
debug!("Creating TX for {}", amount);
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| {
// note this will increment the block count as part of the transaction "Posting"
let args = InitTxArgs {
src_acct_name: None,
amount: amount,
minimum_confirmations: 1,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: false,
..Default::default()
};
let slate_i = sender_api.init_send_tx(m, args)?;
slate = client1.send_tx_slate_direct("wallet2", &slate_i)?;
sender_api.tx_lock_outputs(m, &slate)?;
sender_api.cancel_tx(m, Some(33), None)?;
Ok(())
})?;
// Perform actual testing
test_wallet_tx_filtering(wallet1, mask1)?;
// let logging finish
stopper.store(false, Ordering::Relaxed);
thread::sleep(Duration::from_millis(200));
Ok(())
}
#[test]
fn wallet_tx_filtering() {
let test_dir = "test_output/advanced_tx_filtering";
clean_output_dir(test_dir);
setup(test_dir);
if let Err(e) = build_chain_for_tx_filtering(test_dir, 30) {
panic!("Libwallet Error: {}", e);
}
clean_output_dir(test_dir);
}
-123
View File
@@ -1,123 +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.
//! Test a wallet repost command
#[macro_use]
extern crate log;
extern crate grin_wallet_api as api;
extern crate grin_wallet_controller as wallet;
extern crate grin_wallet_impls as impls;
extern crate grin_wallet_libwallet as libwallet;
// use crate::libwallet::api_impl::owner_updater::{start_updater_log_thread, StatusMessage};
// use grin_wallet_util::grin_core as core;
use impls::test_framework::{self, LocalWalletClient};
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
#[macro_use]
mod common;
use common::{clean_output_dir, create_wallet_proxy, setup, setup_global_chain_type};
/// updater thread test impl
fn updater_thread_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// Create a new proxy to simulate server and wallet responses
let mut wallet_proxy = create_wallet_proxy(test_dir);
let chain = wallet_proxy.chain.clone();
let stopper = wallet_proxy.running.clone();
// Create a new wallet test client, and set its queues to communicate with the
// proxy
create_wallet_and_add!(
client1,
wallet1,
mask1_i,
test_dir,
"wallet1",
None,
&mut wallet_proxy,
false
);
let mask1 = (&mask1_i).as_ref();
create_wallet_and_add!(
client2,
wallet2,
mask2_i,
test_dir,
"wallet2",
None,
&mut wallet_proxy,
false
);
let mask2 = (&mask2_i).as_ref();
// Set the wallet proxy listener running
thread::spawn(move || {
if let Err(e) = wallet_proxy.run() {
error!("Wallet Proxy error: {}", e);
}
});
// add some accounts
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
api.create_account_path(m, "mining")?;
api.create_account_path(m, "listener")?;
Ok(())
})?;
// add some accounts
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
api.create_account_path(m, "account1")?;
api.create_account_path(m, "account2")?;
Ok(())
})?;
// Get some mining done
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("mining")?;
}
let bh = 10u64;
let _ =
test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false);
let owner_api = api::Owner::new(wallet1, None);
owner_api.start_updater(mask1, Duration::from_secs(5))?;
// let updater thread run a bit
thread::sleep(Duration::from_secs(10));
let messages = owner_api.get_updater_messages(1000)?;
assert_eq!(messages.len(), 32);
owner_api.stop_updater()?;
stopper.store(false, Ordering::Relaxed);
thread::sleep(Duration::from_secs(2));
Ok(())
}
#[test]
fn updater_thread() {
// The "updater" kicks off a new thread so we need to ensure the global chain_type
// is set for this to work correctly.
setup_global_chain_type();
let test_dir = "test_output/updater_thread";
setup(test_dir);
if let Err(e) = updater_thread_test_impl(test_dir) {
panic!("Libwallet Error: {}", e);
}
clean_output_dir(test_dir);
}