[CONTRACTS] Contracts branch update for 1.80 (#718)

* Simple contracts restructured v3 (#675)

* Add prototype contract implementation

Lacks:
- Payment proofs (add early payment proofs)
- Tests
- Better structure

* Separate contract utilities

* Simplify the main setup/sign code flow

* Remove commented initial_sec_key assignment

* Simplify sign::compute function

* Add logic for "removal" of secret keys from the context

* Further simplify a bit sign and setup

* No need for mutable context when adding outputs

* Refactor the commented code (lol)

* Refactor a bit

* tmp

---------

Co-authored-by: oryhp <gtrphyro@gmail.com>

* contract test fixes (#676)

* [Contracts] Experimental Slate v5 (#677)

* beginning to add and modify slate version 5

* completion of conversions from V5 to V4 Slate

* timestamp and memo fields

* upgrade/downgrade serialization of v5 slates

* add v5 binary slate versions + start of tests

* add bin slate ser/deser to tests

* ensure serialization of timestamp always excludes milliseconds

* start to update v5 tests, update v5 documentation

* add fn to generate populated internal slate for conversion testing

* add basic tests to convert all slate versions

* update

* commit cargo.lock

* [WIP] [Contracts] Early payment proofs (#681)

* add types and beginnings of signature utils

* add proof serialization

* serialisation of proof data + signature operation

* add serialization type for invoice proof + separate bin wrapper version

* add witness data + serializion to invoice payment proof, insert verfication functions in place in order to begin verification testing

* tests and infrastructure in place for validation

* verification of promise sig

* added verification of promise signature, infrastructure up to the point where a signature must be subtracted

* attempting to figure out differences between recipient nonce that's getting stored and calculated recipient nonce

* implementation of witness verification function, retrieve relevant values and re-validate derived recipient partial signature

* move stored portion of invoice proof into core types for storage, need to rename invoice proof

* define/refine the stored portion of payment proofs type 2?

* Folding all proof data into tx log entry storage

* back to importing master

* remove cargo files from diffs

* remove a lot of extra debug output

* return proof witness as part of proof retrieval, define json serialization of invoice proof + witness fields

* finish adding verification steps to foreign API

* remove redundant promise sig field

* move lcation of sign/verify calls

* Replace Azure Pipelines with Github Actions (#688)

* Update CI Badge on README.MD (#690)

* Trigger CI on push and pull request (#693)

* Update versioning to 5.2.0-beta.1 against grin 5.2.0-beta.3 (#691)

* update versioning to 5.2.0-beta.1 against grin 5.2.0-beta.3

* tweak for CI trigger

---------

Co-authored-by: Quentin Le Sceller <q.lesceller@gmail.com>

---------

Co-authored-by: Quentin Le Sceller <q.lesceller@gmail.com>

* fix Cargo.lock

* Contracts - Test fixes + warning cleanups (#694)

* clean up warnings in libwallet crate

* clean up warnings in controller crate

* update all contract tests with awareness of new proof structure

* added explicit payment proof tests for both rsr and srs (#695)

* Basic mwmixnet send (#696)

* integrating onion library

* updates and changes to support newly included mwmixnet types

* add (incorrect) owner api function

* turn off test for now

* switch working grin branch to master

* fix doctests for build

* update cargo lock in attempt to fix croaring build on CI server

* update cargo lock with upstream thiserror crate

* update test dependency for croaring

* [Contracts] Add self spend transaction state (#697)

* Add self spend transaction state

* subtle errors with output states and tx lookups - fixes

* [Contracts] Slatepack v5 Deserialization fix (#698)

* add V5 deserialization test + fixes

* clarify comment

* upwrap fix during v4 deserialization

* further unwrap removal

* update cargo lock, use latest grin packages

* addition of self-spend cancel + tests (#699)

* add tests + legacy self send cancel (#702)

* [Contracts] Cancel self-spend TX Pt. 2 (#703)

* add tests + legacy self send cancel

* add missing file

* Update grin to latest master, change versioning label to include contracts (#712)

* Add owner_api_listen_interface as hidden configuration field (#714)

* updates for rust 1.80

* updates for rust 1.80

---------

Co-authored-by: oryhp <gtrphyro@gmail.com>
Co-authored-by: Quentin Le Sceller <q.lesceller@gmail.com>
This commit is contained in:
Yeastplume
2024-08-27 12:30:22 +01:00
committed by GitHub
co-authored by oryhp Quentin Le Sceller
parent 765f11b168
commit cdd63fb565
78 changed files with 10170 additions and 220 deletions
+11 -11
View File
@@ -1,6 +1,6 @@
[package]
name = "grin_wallet_controller"
version = "5.4.0-alpha.1"
version = "5.4.0-contracts.0"
authors = ["Grin Developers <mimblewimble@lists.launchpad.net>"]
description = "Controllers for grin wallet instantiation"
license = "Apache-2.0"
@@ -30,19 +30,19 @@ 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_wallet_util = { path = "../util", version = "5.4.0-contracts.0" }
grin_wallet_api = { path = "../api", version = "5.4.0-contracts.0" }
grin_wallet_impls = { path = "../impls", version = "5.4.0-contracts.0" }
grin_wallet_libwallet = { path = "../libwallet", version = "5.4.0-contracts.0" }
grin_wallet_config = { path = "../config", version = "5.4.0-contracts.0" }
##### Grin Imports
# For Release
# grin_core = "5.4.0-alpha.1"
# grin_keychain = "5.4.0-alpha.1"
# grin_util = "5.4.0-alpha.1"
# grin_api = "5.4.0-alpha.1"
# grin_core = "5.4.0-contracts.0"
# grin_keychain = "5.4.0-contracts.0"
# grin_util = "5.4.0-contracts.0"
# grin_api = "5.4.0-contracts.0"
# For beta release
@@ -72,7 +72,7 @@ remove_dir_all = "0.7"
##### Grin Imports
# For Release
# grin_chain = "5.4.0-alpha.1"
# grin_chain = "5.4.0-contracts.0"
# For beta release
+480 -1
View File
@@ -13,7 +13,6 @@
// limitations under the License.
//! Grin wallet command-line function implementations
use crate::api::TLSConfig;
use crate::apiwallet::{try_slatepack_sync_workflow, Owner};
use crate::config::{TorConfig, WalletConfig, WALLET_CONFIG_FILE_NAME};
@@ -22,6 +21,11 @@ use crate::error::Error;
use crate::impls::PathToSlatepack;
use crate::impls::SlateGetter as _;
use crate::keychain;
use crate::libwallet::api_impl::owner;
use crate::libwallet::contract::can_finalize;
use crate::libwallet::contract::types::{
ContractNewArgsAPI, ContractRevokeArgsAPI, ContractSetupArgsAPI, OutputSelectionArgs,
};
use crate::libwallet::{
self, InitTxArgs, IssueInvoiceTxArgs, NodeClient, PaymentProof, Slate, SlateState, Slatepack,
SlatepackAddress, Slatepacker, SlatepackerArgs, WalletLCProvider,
@@ -31,9 +35,12 @@ use crate::util::{Mutex, ZeroingString};
use crate::{controller, display};
use ::core::time;
use qr_code::QrCode;
use serde::{Deserialize, Serialize};
use serde_json as json;
use std::convert::TryFrom;
use std::fmt;
use std::fs::File;
use std::io;
use std::io::{Read, Write};
use std::sync::atomic::Ordering;
use std::sync::Arc;
@@ -558,6 +565,140 @@ where
Ok(())
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct SlatepackOut {
/// Is slatepack encrypted
pub is_encrypted: bool,
/// Is slatepack finalized
pub is_finalized: bool,
/// File where slatepack is saved
pub out_file: String,
/// Slatepack message. Encrypted or not.
pub message: String,
}
impl fmt::Display for SlatepackOut {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let start_meta = "--------------- SLATEPACK METADATA --------------";
let meta = format!(
"Slate encrypted: {}\nSlate finalized: {}\nSlate saved to file: {}",
self.is_encrypted, self.is_finalized, self.out_file
);
let start_slatepack = "-------------- CUT BELOW THIS LINE --------------";
let end_slatepack = "-------------- CUT ABOVE THIS LINE --------------";
write!(
f,
"{start_meta}\n\n{meta}\n\n{start_slatepack}\n\n{}\n\n{end_slatepack}",
self.message
)
}
}
impl SlatepackOut {
fn as_json(&self) -> String {
serde_json::to_string_pretty(&self).unwrap()
}
pub fn print(&self, as_json: bool) -> () {
if !self.is_finalized {
if as_json {
println!("{}", self.as_json());
} else {
println!("{}", self);
}
} else {
println!("Transaction was broadcasted."); // TODO: as_json makes no sense here, fix later.
}
}
}
pub fn print_slatepack<L, C, K>(
api: &mut Owner<L, C, K>,
keychain_mask: Option<&SecretKey>,
slate: &Slate,
counterparty_addr: &str,
out_file: Option<String>,
as_json: bool,
) -> ()
where
L: WalletLCProvider<'static, C, K> + 'static,
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
// For now, we don't compact slates with sl.compact(). We first make them work without compaction.
let slate_out =
prepare_slatepack(api, keychain_mask, &slate, &counterparty_addr, out_file).unwrap();
slate_out.print(as_json);
}
pub fn prepare_slatepack<L, C, K>(
owner_api: &mut Owner<L, C, K>,
keychain_mask: Option<&SecretKey>,
slate: &Slate,
dest: &str,
out_file_override: Option<String>,
) -> Result<SlatepackOut, libwallet::Error>
where
L: WalletLCProvider<'static, C, K> + 'static,
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
// Same as output_slatepack except that we don't write to stdout, care about locking or whether the slate was finalized.
// Output the slatepack file to stdout and to a file
let mut message = String::from("");
let mut address = None;
let mut tld = String::from("");
controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| {
address = match SlatepackAddress::try_from(dest) {
Ok(a) => Some(a),
Err(_) => None,
};
// encrypt for recipient by default
let recipients = match address.clone() {
Some(a) => vec![a],
None => vec![],
};
// TODO: what is sender_index?
message = api.create_slatepack_message(m, &slate, Some(0), recipients)?;
// Trim the \n at the end.
let len_withoutcrlf = message.trim_end().len();
message.truncate(len_withoutcrlf);
tld = api.get_top_level_directory()?;
Ok(())
})?;
// create a directory to which files will be output
let slate_dir = format!("{}/{}", tld, "slatepack");
let _ = std::fs::create_dir_all(slate_dir.clone());
let out_file_name = match out_file_override {
None => format!("{}/{}.{}.slatepack", slate_dir, slate.id, slate.state),
Some(f) => f,
};
let mut output = File::create(out_file_name.clone())?;
output.write_all(&message.as_bytes())?;
output.sync_all()?;
// Since we always finalize if we can, we can also use this to know if the tx is finalized
let is_finalized = can_finalize(slate);
let slate_out = SlatepackOut {
is_encrypted: address.is_some(),
is_finalized: is_finalized,
out_file: out_file_name,
message: message,
};
// TODO: We save the slatepack, but it is encrypted for the counterparty. It seems hard to
// know which slatepack is which if we can't decrypt them. Either add some more metadata
// to slatepacks e.g. timestamp, counterparty address or save also a version that is encrypted with
// our own address so we can view it.
Ok(slate_out)
}
// Parse a slate and slatepack from a message
pub fn parse_slatepack<L, C, K>(
owner_api: &mut Owner<L, C, K>,
@@ -1474,3 +1615,341 @@ where
})?;
Ok(())
}
/// Create new contract command arguments
#[derive(Clone)]
pub struct ContractNewArgs {
/// Address of the counterparty
pub counterparty_addr: String,
/// Receive amount
pub receive: Option<u64>,
/// Send amount
pub send: Option<u64>,
/// The human readable account name from which to draw outputs
/// for the transaction, overriding whatever the active account is as set via the
/// [`set_active_account`](../grin_wallet_api/owner/struct.Owner.html#method.set_active_account) method.
pub src_acct_name: Option<String>,
/// Number of participants in a contract (either 1 or 2)
pub num_participants: u8,
/// Show the resulting slatepack as JSON
pub as_json: bool,
/// Use the specified inputs (comma separated input commitments)
pub use_inputs: Option<String>,
/// How to separate outputs (command separated amounts)
pub make_outputs: Option<String>,
// Future features
/// Custom fee contribution
pub fee_rate: Option<u32>,
/// Save slatepack to a specific filename
pub outfile: Option<String>,
/// Select outputs early
pub add_outputs: bool,
}
impl ContractNewArgs {
fn get_net_change(&self) -> i64 {
// TODO: could the cast 'as i64' overflow or something?
match self.receive {
None => match self.send {
None => panic!("Send or receive not specified."),
Some(v) => -(v as i64), // negative net change on send
},
Some(v) => v as i64, // positive net change on receive
}
}
// Create a ContractNewArgsAPI from the ContractNewArgs
fn to_api_args(&self) -> ContractNewArgsAPI {
let net_change = self.get_net_change();
ContractNewArgsAPI {
setup_args: ContractSetupArgsAPI {
src_acct_name: match self.src_acct_name.as_ref() {
Some(v) => Some(v.to_string()),
None => None,
},
net_change: Some(net_change),
num_participants: self.num_participants,
add_outputs: self.add_outputs,
selection_args: OutputSelectionArgs {
use_inputs: match self.use_inputs.as_ref() {
Some(v) => Some(v.to_string()),
None => None,
},
make_outputs: match self.make_outputs.as_ref() {
Some(v) => Some(v.to_string()),
None => None,
},
..Default::default()
},
proof_args: Default::default(),
},
..Default::default()
}
}
}
pub fn contract_new<L, C, K>(
owner_api: &mut Owner<L, C, K>,
keychain_mask: Option<&SecretKey>,
args: ContractNewArgs,
) -> Result<(), Error>
where
L: WalletLCProvider<'static, C, K>,
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| {
let contract_new_args = args.to_api_args();
let slate = api.contract_new(m, &contract_new_args)?;
print_slatepack(
api,
keychain_mask,
&slate,
&args.counterparty_addr,
args.outfile,
args.as_json,
);
Ok(())
})?;
Ok(())
}
/// Sign contract command argument
#[derive(Clone)]
pub struct ContractSetupArgs {
/// Address of the counterparty
pub counterparty_addr: Option<String>,
/// Receive amount
pub receive: Option<u64>,
/// Send amount
pub send: Option<u64>,
/// Show the resulting slatepack as JSON
pub as_json: bool,
/// Use the specified inputs (comma separated input commitments)
pub use_inputs: Option<String>,
/// How to separate outputs (command separated amounts)
pub make_outputs: Option<String>,
// Future features
/// Whether we should automatically sign a receive of any value
// pub auto_receive: Option<bool>,
/// Custom fee contribution
pub fee_rate: Option<u32>,
/// Save slatepack to a specific filename
pub outfile: Option<String>,
/// Add outputs
pub add_outputs: bool, // lock outputs early
}
impl ContractSetupArgs {
fn get_net_change(&self) -> Option<i64> {
let mut net_change: Option<i64> = None;
// TODO: Check bounds before casting to i64.
if self.receive.is_some() && self.send.is_some() {
panic!("Can't pass both --receive and --send parameters.");
}
if self.receive.is_some() {
net_change = Some(self.receive.unwrap() as i64);
}
if self.send.is_some() {
net_change = Some(-(self.send.unwrap() as i64));
}
net_change
}
// Create a ContractSetupArgsAPI from the ContractSetupArgs
fn to_api_args(&self) -> ContractSetupArgsAPI {
let net_change = self.get_net_change();
ContractSetupArgsAPI {
// TODO: num_participants is derived here. It should be an Option and read from the slate.
// Need to check no attack are possible regarding kernel fee contribution.
net_change: net_change,
add_outputs: self.add_outputs,
selection_args: OutputSelectionArgs {
use_inputs: match self.use_inputs.as_ref() {
Some(v) => Some(v.to_string()),
None => None,
},
make_outputs: match self.make_outputs.as_ref() {
Some(v) => Some(v.to_string()),
None => None,
},
..Default::default()
},
..Default::default()
}
}
}
// pub fn contract_setup<L, C, K>(
// owner_api: &mut Owner<L, C, K>,
// keychain_mask: Option<&SecretKey>,
// args: ContractSetupArgs,
// ) -> Result<(), Error>
// where
// L: WalletLCProvider<'static, C, K>,
// C: NodeClient + 'static,
// K: keychain::Keychain + 'static,
// {
// controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| {
// // Read the slatepack from stdin
// println!("Paste slatepack:");
// let mut slatepack_msg = String::new();
// io::stdin()
// .read_line(&mut slatepack_msg)
// .expect("Failed to read from stdin");
// let mut contract_setup_args = args.to_api_args();
// // Decrypt the slate, perform setup on it and encrypt it for the next party
// // TODO: Make sure you get the counterparty_addr and slate with 1 call.
// let slatepack = owner::decode_slatepack_message(
// api.wallet_inst.clone(),
// keychain_mask,
// String::from(slatepack_msg.clone()),
// vec![0],
// )?;
// let counterparty_addr =
// if args.counterparty_addr.is_some() {
// args.counterparty_addr.unwrap()
// } else {
// if !slatepack.sender.is_some() {
// panic!("No address to encrypt for. Contracts only support encrypted slates right now.");
// }
// String::try_from(&slatepack.sender.unwrap())?
// };
// let mut slate = owner::slate_from_slatepack_message(
// api.wallet_inst.clone(),
// keychain_mask,
// String::from(slatepack_msg),
// vec![0],
// )?;
// // We read the number of participants from the slate that was already created. We need this to
// // avoid taking the default value of 2 in case of 3-party computation and thus incorrectly computing
// // our kernel cost contribution.
// contract_setup_args.num_participants = slate.num_participants;
// slate = api.contract_setup(m, &slate, &contract_setup_args)?;
// print_slatepack(
// api,
// keychain_mask,
// &slate,
// &counterparty_addr,
// args.outfile,
// args.as_json,
// );
// Ok(())
// })?;
// Ok(())
// }
pub fn contract_sign<L, C, K>(
owner_api: &mut Owner<L, C, K>,
keychain_mask: Option<&SecretKey>,
args: ContractSetupArgs,
broadcast_tx: bool,
) -> Result<(), Error>
where
L: WalletLCProvider<'static, C, K>,
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| {
// Read the slatepack from stdin
println!("Paste slatepack:");
let mut slatepack_msg = String::new();
io::stdin()
.read_line(&mut slatepack_msg)
.expect("Failed to read from stdin");
// Args for signing are just setup args
let contract_sign_args = args.to_api_args();
// Decrypt the slate, sign it and encrypt it for the next party
// TODO: Make sure you get the counterparty_addr and slate with 1 call.
let slatepack = owner::decode_slatepack_message(
api.wallet_inst.clone(),
keychain_mask,
String::from(slatepack_msg.clone()),
vec![0],
)?;
let counterparty_addr =
if args.counterparty_addr.is_some() {
args.counterparty_addr.unwrap()
} else {
if !slatepack.sender.is_some() {
panic!("No address to encrypt for. Contracts only support encrypted slates right now.");
}
String::try_from(&slatepack.sender.unwrap())?
};
let mut slate = owner::slate_from_slatepack_message(
api.wallet_inst.clone(),
keychain_mask,
String::from(slatepack_msg),
vec![0],
)?;
slate = api.contract_sign(m, &slate, &contract_sign_args)?;
print_slatepack(
api,
keychain_mask,
&slate,
&counterparty_addr,
args.outfile,
args.as_json,
);
if broadcast_tx {
let is_finalized = can_finalize(&slate);
if is_finalized {
api.post_tx(keychain_mask, &slate, true)?;
}
}
Ok(())
})?;
Ok(())
}
#[derive(Clone)]
pub struct ContractRevokeArgs {
/// Id of a transaction we want to cancel
pub tx_id: u32,
}
pub fn contract_revoke<L, C, K>(
owner_api: &mut Owner<L, C, K>,
keychain_mask: Option<&SecretKey>,
args: ContractRevokeArgs,
) -> Result<(), Error>
where
L: WalletLCProvider<'static, C, K>,
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| {
let slate_opt = api.contract_revoke(m, &ContractRevokeArgsAPI { tx_id: args.tx_id })?;
// TODO: replace dest="nope" with our own address and add --as-json support
if slate_opt.is_some() {
let slate_out =
prepare_slatepack(api, keychain_mask, &slate_opt.unwrap(), "nope", None)?;
println!("{}", slate_out);
}
Ok(())
})?;
Ok(())
}
+138 -2
View File
@@ -15,18 +15,24 @@
extern crate grin_wallet_controller as wallet;
extern crate grin_wallet_impls as impls;
extern crate grin_wallet_libwallet as libwallet;
extern crate log;
use grin_chain as chain;
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::keychain::{ExtKeychain, Keychain};
use self::libwallet::WalletInst;
use impls::test_framework::{LocalWalletClient, WalletProxy};
use chain::Chain;
use grin_wallet_controller::Error;
use impls::test_framework::{self, LocalWalletClient, WalletProxy};
use impls::{DefaultLCProvider, DefaultWalletImpl};
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use std::thread;
use util::secp::key::SecretKey;
use util::{Mutex, ZeroingString};
@@ -176,3 +182,133 @@ pub fn open_local_wallet(
.unwrap();
(Arc::new(Mutex::new(wallet)), mask)
}
// Creates the given number of wallets and spawns a thread that runs the wallet proxy
#[allow(dead_code)]
pub fn create_wallets(
wallets_def: Vec<Vec<(&'static str, u64)>>, // a vector of boolean that represent whether we mine into a wallet
test_dir: &'static str,
) -> Result<
(
Vec<(
Arc<
Mutex<
Box<
dyn WalletInst<
'static,
DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
LocalWalletClient,
ExtKeychain,
>,
>,
>,
>,
Option<SecretKey>,
)>, // wallets
Arc<Chain>, // chain
Arc<AtomicBool>, // stopper
u64, // block height
),
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();
let mut wallets = vec![];
for i in 0..wallets_def.len() {
let name = format!("wallet{}", i + 1);
let wclient = LocalWalletClient::new(&name, wallet_proxy.tx.clone());
let (wallet1, mask1) = create_local_wallet(
test_dir,
&name,
None,
// $seed_phrase.clone(),
wclient.clone(),
true,
);
wallet_proxy.add_wallet(
&name,
wclient.get_send_instance(),
wallet1.clone(),
mask1.clone(),
);
wallets.push((wallet1, mask1));
}
// Set the wallet proxy listener running
thread::spawn(move || {
if let Err(e) = wallet_proxy.run() {
log::error!("Wallet Proxy error: {}", e);
}
});
// // Mine values into wallets
// // few values to keep things shorter
let reward = core::consensus::REWARD;
let mut bh = 0u64;
for (idx, accs) in wallets_def.iter().enumerate() {
let wallet1 = wallets[idx].0.clone();
let mask1 = wallets[idx].1.as_ref();
for (acc_idx, (acc_name, num_mined_blocks)) in accs.iter().enumerate() {
// create the account
if acc_name.to_string() != "default" {
wallet::controller::owner_single_use(
Some(wallet1.clone()),
mask1,
None,
|api, m| {
let new_path = api.create_account_path(m, acc_name)?;
assert_eq!(
new_path,
ExtKeychain::derive_key_id(2, acc_idx as u32, 0, 0, 0) // NOTE: default should always be at 0 and is already created
);
Ok(())
},
)?;
}
// Get some mining done
if *num_mined_blocks == 0 {
continue;
}
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name(acc_name)?;
}
let _ = test_framework::award_blocks_to_wallet(
&chain,
wallet1.clone(),
mask1,
*num_mined_blocks as usize,
false,
);
bh += num_mined_blocks;
// 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, num_mined_blocks * reward);
Ok(())
})?;
}
// Sanity check the number of accounts
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let accounts = api.accounts(m)?;
assert_eq!(accounts.len(), accs.len());
Ok(())
})?;
// Set the account on the wallet to "default"
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("default")?;
}
}
Ok((wallets, chain, stopper, bh))
}
+310
View File
@@ -0,0 +1,310 @@
// Copyright 2022 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 contract utils
#[macro_use]
extern crate log;
extern crate grin_wallet_controller as wallet;
extern crate grin_wallet_impls as impls;
use grin_wallet_libwallet as libwallet;
use grin_wallet_util::grin_core as core;
use grin_wallet_util::grin_keychain as keychain;
use grin_wallet_util::grin_util as util;
use self::keychain::ExtKeychain;
use self::libwallet::WalletInst;
// use impls::test_framework::{LocalWalletClient, WalletProxy};
use crate::chain::Chain;
use grin_wallet_util::grin_chain as chain;
use impls::{DefaultLCProvider, DefaultWalletImpl};
use std::sync::Arc;
use util::secp::key::SecretKey;
use util::{Mutex, ZeroingString};
use impls::test_framework::{self, LocalWalletClient, WalletProxy};
use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI};
use libwallet::{Slate, SlateState};
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
// #[macro_use]
mod common;
use common::{clean_output_dir, create_local_wallet, create_wallet_proxy, setup};
pub fn create_wallets(
test_dir: &'static str,
) -> (
Vec<(
Arc<
Mutex<
Box<
dyn WalletInst<
'static,
DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
LocalWalletClient,
ExtKeychain,
>,
>,
>,
>,
Option<SecretKey>,
)>,
Arc<Chain>, // chain
Arc<AtomicBool>, // stopper
) {
// Create a new proxy to simulate server and wallet responses
let mut wallet_proxy = common::create_wallet_proxy(test_dir);
let chain = wallet_proxy.chain.clone();
let stopper = wallet_proxy.running.clone();
let mut wallets = vec![];
for i in 0..2 {
let name = format!("wallet{}", i + 1);
let wclient = LocalWalletClient::new(&name, wallet_proxy.tx.clone());
let (wallet1, mask1) = common::create_local_wallet(
test_dir,
&name,
None,
// $seed_phrase.clone(),
wclient.clone(),
true,
);
wallet_proxy.add_wallet(
&name,
wclient.get_send_instance(),
wallet1.clone(),
mask1.clone(),
);
// create_wallet_and_add!(
// client1,
// wallet1,
// mask1_i,
// test_dir,
// "wallet1",
// None,
// &mut wallet_proxy,
// true
// );
wallets.push((wallet1, mask1));
}
// Set the wallet proxy listener running
thread::spawn(move || {
if let Err(e) = wallet_proxy.run() {
error!("Wallet Proxy error: {}", e);
}
});
(wallets, chain, stopper)
// 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! create_wallets {
// ($client:ident, $wallet: ident, $mask: ident, $test_dir: expr, $name: expr, $seed_phrase: expr, $proxy: expr, $create_mask: expr) => {
// // 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();
// let rv = vec![];
// for i in 0..wallets.len() {
// let name = format!("wallet{}", i + 1);
// let wclient = LocalWalletClient::new(name, wallet_proxy.tx.clone());
// let (wallet1, mask1) = common::create_local_wallet(
// $test_dir,
// name,
// None,
// // $seed_phrase.clone(),
// wallet_proxy.clone(),
// true,
// );
// wallet_proxy.add_wallet(
// name,
// wclient.get_send_instance(),
// wallet1.clone(),
// mask1.clone(),
// );
// // create_wallet_and_add!(
// // client1,
// // wallet1,
// // mask1_i,
// // test_dir,
// // "wallet1",
// // None,
// // &mut wallet_proxy,
// // true
// // );
// rv.push((wallet1, mask1));
// }
// rv
// // 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(),
// // );
// };
// }
// prepare wallets
// fn create_wallets(
// wallets: Vec<u64>,
// test_dir: &'static str,
// // wallet_proxy: WalletProxy<
// // 'static,
// // DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
// // LocalWalletClient,
// // ExtKeychain,
// // >,
// ) -> Vec<(
// Arc<
// Mutex<
// Box<
// dyn WalletInst<
// 'static,
// DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
// LocalWalletClient,
// ExtKeychain,
// >,
// >,
// >,
// >,
// Option<SecretKey>,
// )> {
// // 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();
// let rv = vec![];
// for _ in 0..wallets.len() {
// create_wallet_and_add!(
// client1,
// wallet1,
// mask1_i,
// test_dir,
// "wallet1",
// None,
// &mut wallet_proxy,
// true
// );
// rv.push((wallet1, mask1_i));
// }
// rv
// }
// /// prepare two wallets for testing
// fn prepare_wallets(n_wallets: u8, test_dir: &'static str) -> Result<Vec<()>, 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 logging finish
// stopper.store(false, Ordering::Relaxed);
// thread::sleep(Duration::from_millis(200));
// Ok(())
// }
// #[test]
// fn wallet_contract_rsr_tx() -> Result<(), libwallet::Error> {
// let test_dir = "test_output/contract_rsr_tx";
// setup(test_dir);
// contract_rsr_tx_impl(test_dir)?;
// clean_output_dir(test_dir);
// Ok(())
// }
+259
View File
@@ -0,0 +1,259 @@
// Copyright 2022 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 doing contracts with different accounts
// #[macro_use]
extern crate grin_wallet_controller as wallet;
extern crate grin_wallet_impls as impls;
extern crate log;
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};
use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI};
use libwallet::{Slate, SlateState};
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
#[macro_use]
mod common;
use common::{clean_output_dir, create_wallets, setup};
/// contract accounts testing (mostly the same as accounts.rs)
fn contract_accounts_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// create two wallets with some extra accounts and don't mine anything in them
let (wallets, chain, stopper, _bh) = create_wallets(
vec![
vec![
("default", 0),
("account1", 0),
("account2", 0),
("account3", 0),
],
vec![("default", 0), ("listener_account", 0)],
],
test_dir,
)
.unwrap();
let wallet1 = wallets[0].0.clone();
let mask1 = wallets[0].1.as_ref();
let wallet2 = wallets[1].0.clone();
let mask2 = wallets[1].1.as_ref();
// few values to keep things shorter
let reward = core::consensus::REWARD;
let cm = global::coinbase_maturity(); // assume all testing precedes soft fork height
// 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
{
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(())
})?;
// TODO: check what send_tx_slate_direct call does in accounts.rs test
// TODO: check that you can't call send on the default account because you have no funds
// Send a tx from wallet1::account1 -> wallet2::listener_account
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("account1")?;
}
let mut slate = Slate::blank(0, true); // this gets overriden below
let mut sender_address = None;
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
// Send wallet inititates a standard transaction with --send=5
let args = &ContractNewArgsAPI {
setup_args: ContractSetupArgsAPI {
net_change: Some(-5_000_000_000),
..Default::default()
},
..Default::default()
};
slate = api.contract_new(m, args)?;
sender_address = Some(api.get_slatepack_address(mask1, 0)?.pub_key);
Ok(())
})?;
assert_eq!(slate.state, SlateState::Standard1);
let mut recipient_address = None;
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
// Receive wallet calls --receive=5
let args = &mut ContractSetupArgsAPI {
net_change: Some(5_000_000_000),
..Default::default()
};
args.proof_args.sender_address = sender_address;
slate = api.contract_sign(m, &slate, args)?;
recipient_address = Some(api.get_slatepack_address(mask2, 0)?.pub_key);
Ok(())
})?;
assert_eq!(slate.state, SlateState::Standard2);
// Send wallet finalizes and posts
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let args = &ContractSetupArgsAPI {
..Default::default()
};
slate = api.contract_sign(m, &slate, args)?;
Ok(())
})?;
assert_eq!(slate.state, SlateState::Standard3);
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
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 wallet_contract_accounts() -> Result<(), libwallet::Error> {
let test_dir = "test_output/contract_accounts";
setup(test_dir);
contract_accounts_impl(test_dir)?;
clean_output_dir(test_dir);
Ok(())
}
@@ -0,0 +1,212 @@
// Copyright 2022 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 doing contracts with different accounts and switching between them
// #[macro_use]
extern crate grin_wallet_controller as wallet;
extern crate grin_wallet_impls as impls;
extern crate log;
use grin_core as core;
use grin_wallet_libwallet as libwallet;
use libwallet::contract::my_fee_contribution;
use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI};
use libwallet::{Slate, SlateState};
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
#[macro_use]
mod common;
use common::{clean_output_dir, create_wallets, setup};
/// contract accounts testing when switching between accounts during transaction building
fn contract_accounts_switch_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// create two wallets with some extra accounts and don't mine anything in them
let (wallets, _chain, stopper, _bh) = create_wallets(
vec![
vec![("default", 0), ("account1", 1), ("account2", 2)],
vec![("default", 0), ("account1", 3), ("account2", 4)],
],
test_dir,
)
.unwrap();
let wallet1 = wallets[0].0.clone();
let mask1 = wallets[0].1.as_ref();
let wallet2 = wallets[1].0.clone();
let mask2 = wallets[1].1.as_ref();
let reward = core::consensus::REWARD;
// wallet1::account1 should have 1 in account1 (1 spendable)
{
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 (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
assert!(wallet1_refreshed);
assert_eq!(wallet1_info.last_confirmed_height, 10);
assert_eq!(wallet1_info.total, 1 * reward);
assert_eq!(wallet1_info.amount_currently_spendable, 1 * reward);
let (_, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert_eq!(txs.len(), 1);
Ok(())
})?;
// wallet1::account2 should have 2 in account1 (2 spendable)
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("account2")?;
}
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, 3);
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
assert!(wallet1_refreshed);
assert_eq!(wallet1_info.last_confirmed_height, 10);
assert_eq!(wallet1_info.total, 2 * reward);
assert_eq!(wallet1_info.amount_currently_spendable, 2 * reward);
// check tx log as well
let (_, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert_eq!(txs.len(), 2);
Ok(())
})?;
// Make a transaction by sending 5 coins from wallet1::account1 -> wallet2::account2
{
wallet_inst!(wallet1, w);
w.set_parent_key_id_by_name("account1")?;
}
let mut slate = Slate::blank(0, true); // this gets overriden below
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
// Send wallet inititates a standard transaction with --send=5
let args = &ContractNewArgsAPI {
setup_args: ContractSetupArgsAPI {
net_change: Some(-5_000_000_000),
..Default::default()
},
..Default::default()
};
slate = api.contract_new(m, args)?;
Ok(())
})?;
assert_eq!(slate.state, SlateState::Standard1);
// Receiver gets their coins on account2 where they can payjoin
{
wallet_inst!(wallet2, w);
w.set_parent_key_id_by_name("account2")?;
}
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
// Receive wallet calls --receive=5
let args = &mut ContractSetupArgsAPI {
net_change: Some(5_000_000_000),
..Default::default()
};
args.proof_args.suppress_proof = true;
slate = api.contract_sign(m, &slate, args)?;
Ok(())
})?;
assert_eq!(slate.state, SlateState::Standard2);
// Switch account for wallet1 to account2 and finish the transaction (should use account1 to complete)
{
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 args = &ContractSetupArgsAPI {
..Default::default()
};
slate = api.contract_sign(m, &slate, args)?;
Ok(())
})?;
assert_eq!(slate.state, SlateState::Standard3);
// post tx and mine a block to wallet1::account2
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
api.post_tx(m, &slate, false)?;
Ok(())
})?;
// The currently set account (account2) should not be affected by this transaction because they weren't a part of it,
// but it did mine a block and pick the transaction fees
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, 11);
assert_eq!(
wallet1_info.total,
3 * reward + core::libtx::tx_fee(2, 2, 1) // we have received a block reward and the tx fee (payjoin)
);
assert_eq!(wallet1_info.amount_currently_spendable, 2 * reward);
let (_, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert_eq!(txs.len(), 3);
Ok(())
})?;
// Switch to wallet1::account1 and check that it sent 5 coins
{
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 (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
assert!(wallet1_refreshed);
assert_eq!(wallet1_info.last_confirmed_height, 11);
assert_eq!(
wallet1_info.total,
1 * reward - 5_000_000_000 - my_fee_contribution(1, 1, 1, 2)?.fee() // we subtract also our fee contribution
);
let (_, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert_eq!(txs.len(), 2);
Ok(())
})?;
// Switch to wallet2::account2 and check that it received 5 coins
{
wallet_inst!(wallet2, w);
w.set_parent_key_id_by_name("account2")?;
}
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, 11);
assert_eq!(
wallet2_info.total,
4 * reward + 5_000_000_000 - my_fee_contribution(1, 1, 1, 2)?.fee() // we subtract also our fee contribution for a payjoin
);
let (_, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert_eq!(txs.len(), 5);
Ok(())
})?;
// let logging finish
stopper.store(false, Ordering::Relaxed);
thread::sleep(Duration::from_millis(200));
Ok(())
}
#[test]
fn wallet_contract_accounts_switch() -> Result<(), libwallet::Error> {
let test_dir = "test_output/contract_accounts_switch";
setup(test_dir);
contract_accounts_switch_impl(test_dir)?;
clean_output_dir(test_dir);
Ok(())
}
+103
View File
@@ -0,0 +1,103 @@
// Copyright 2022 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 doing contract early lock when using --add-outputs
// #[macro_use]
extern crate grin_wallet_controller as wallet;
extern crate grin_wallet_impls as impls;
extern crate log;
use grin_wallet_libwallet as libwallet;
use grin_core::consensus;
use libwallet::contract::my_fee_contribution;
use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI};
use libwallet::{OutputStatus, Slate, SlateState};
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
#[macro_use]
mod common;
use common::{clean_output_dir, create_wallets, setup};
/// contract new with --add-outputs
fn contract_early_lock_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// create a single wallet and mine 5 blocks
let (wallets, _chain, stopper, _bh) =
create_wallets(vec![vec![("default", 5)]], test_dir).unwrap();
let send_wallet = wallets[0].0.clone();
let send_mask = wallets[0].1.as_ref();
// Confirm all our outputs are unspent
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
let (_, commits) = api.retrieve_outputs(m, true, false, None)?;
for commit in commits.iter() {
assert_eq!(commit.output.status, OutputStatus::Unspent);
}
Ok(())
})?;
let mut slate = Slate::blank(0, false); // this gets overriden below
// Call contract 'new' with --add-outputs
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
// Send wallet inititates a standard transaction with --send=80
let args = &ContractNewArgsAPI {
setup_args: ContractSetupArgsAPI {
net_change: Some(-80_000_000_000),
num_participants: 2,
add_outputs: true,
..Default::default()
},
..Default::default()
};
slate = api.contract_new(m, args)?;
Ok(())
})?;
assert_eq!(slate.state, SlateState::Standard1);
// Assert we locked 2 inputs and prepared an unconfirmed change output
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
let (_, commits) = api.retrieve_outputs(m, true, false, None)?;
// we locked the first two coinbase outputs
assert_eq!(commits[0].output.status, OutputStatus::Locked);
assert_eq!(commits[1].output.status, OutputStatus::Locked);
// we added a new unconfirmed change output
let new_output_idx = commits.len() - 1;
assert_eq!(
commits[new_output_idx].output.status,
OutputStatus::Unconfirmed
);
assert_eq!(
commits[new_output_idx].output.value,
2 * consensus::REWARD - 80_000_000_000 - my_fee_contribution(2, 1, 1, 2)?.fee()
);
Ok(())
})?;
// let logging finish
stopper.store(false, Ordering::Relaxed);
thread::sleep(Duration::from_millis(200));
Ok(())
}
#[test]
fn wallet_contract_early_lock_tx() -> Result<(), libwallet::Error> {
let test_dir = "test_output/contract_early_lock_tx";
setup(test_dir);
contract_early_lock_tx_impl(test_dir)?;
clean_output_dir(test_dir);
Ok(())
}
@@ -0,0 +1,202 @@
// Copyright 2023 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.
//! Development and testing of early payment proofs, restricted at the moment
//! to contract-style transactions for experimental purposes
//!
//! https://github.com/mimblewimble/grin-rfcs/pull/70
//!
//!
extern crate grin_wallet_controller as wallet;
extern crate grin_wallet_impls as impls;
extern crate log;
use grin_wallet_libwallet as libwallet;
use impls::test_framework::{self};
use libwallet::contract::my_fee_contribution;
use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI};
use libwallet::{Slate, SlateState, Slatepack, Slatepacker, SlatepackerArgs, TxLogEntryType};
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
#[macro_use]
mod common;
use common::{clean_output_dir, create_wallets, setup};
/// Development + Tests of early payment proof functionality - RSR workflow
fn contract_early_proofs_rsr_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// create two wallets and mine 4 blocks in each (we want both to have balance to get a payjoin)
let (wallets, chain, stopper, mut bh) =
create_wallets(vec![vec![("default", 4)], vec![("default", 4)]], test_dir).unwrap();
let send_wallet = wallets[0].0.clone();
let send_mask = wallets[0].1.as_ref();
let recv_wallet = wallets[1].0.clone();
let recv_mask = wallets[1].1.as_ref();
let mut slate = Slate::blank(0, true); // this gets overriden below
let mut sender_address = None;
// Get sender address explicitly
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
sender_address = Some(api.get_slatepack_address(send_mask, 0)?.pub_key);
Ok(())
})?;
let mut recipient_address = None;
wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| {
// Receive wallet (invoice) calls --receive=5
let args = &mut ContractNewArgsAPI {
setup_args: ContractSetupArgsAPI {
net_change: Some(5_000_000_000),
..Default::default()
},
..Default::default()
};
args.setup_args.proof_args.sender_address = sender_address;
println!("SENDER ADDRESS: {:?}", sender_address);
slate = api.contract_new(m, args)?;
recipient_address = Some(api.get_slatepack_address(recv_mask, 0)?.pub_key);
Ok(())
})?;
assert_eq!(slate.state, SlateState::Invoice1);
println!("I1 State slate: {}", slate);
// Serialize slate into slatepack
let slatepacker_args = SlatepackerArgs {
sender: None,
recipients: vec![],
dec_key: None,
};
let slate_packer = Slatepacker::new(slatepacker_args);
let slate_packed = slate_packer.create_slatepack(&slate).unwrap();
let slate_unpacked = slate_packer.get_slate(&slate_packed).unwrap();
println!("I2 Slate unpacked: {}", slate_unpacked);
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
// Sending wallet (invoice) signs
let args = &ContractSetupArgsAPI {
net_change: Some(-5_000_000_000),
..Default::default()
};
slate = api.contract_sign(m, &slate_unpacked, args)?;
Ok(())
})?;
println!("I2 State slate: {}", slate);
assert_eq!(slate.state, SlateState::Invoice2);
// Send wallet finalizes and posts
wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| {
let args = &ContractSetupArgsAPI {
..Default::default()
};
slate = api.contract_sign(m, &slate, args)?;
Ok(())
})?;
assert_eq!(slate.state, SlateState::Invoice3);
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
api.post_tx(m, &slate, false)?;
Ok(())
})?;
bh += 1;
let _ =
test_framework::award_blocks_to_wallet(&chain, send_wallet.clone(), send_mask, 3, false);
bh += 3;
// Assert changes in receive wallet
wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| {
let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?;
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert_eq!(wallet_info.last_confirmed_height, bh);
assert!(refreshed);
assert_eq!(txs.len(), 5); // 4 mined and 1 received
let tx_log = txs[4].clone();
assert_eq!(tx_log.tx_type, TxLogEntryType::TxReceived);
assert_eq!(tx_log.amount_credited, 5_000_000_000);
assert_eq!(tx_log.amount_debited, 0);
assert_eq!(tx_log.num_inputs, 1);
assert_eq!(tx_log.num_outputs, 1);
let expected_fees_paid = Some(my_fee_contribution(1, 1, 1, 2)?);
assert_eq!(tx_log.fee, expected_fees_paid);
assert_eq!(
wallet_info.amount_currently_spendable,
4 * 60_000_000_000 + 5_000_000_000 - expected_fees_paid.unwrap().fee() // we expect the balance of 4 mined blocks + 5 Grin - fees paid
);
Ok(())
})?;
// Assert changes in send wallet
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?;
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert_eq!(wallet_info.last_confirmed_height, bh);
assert!(refreshed);
assert_eq!(txs.len() as u64, bh - 4 + 1); // send wallet didn't mine 4 blocks and made 1 tx
let tx_log = txs[txs.len() - 5].clone(); // TODO: why -5 and not -4?
assert_eq!(tx_log.tx_type, TxLogEntryType::TxSent);
assert_eq!(tx_log.amount_credited, 0);
assert_eq!(tx_log.amount_debited, 5_000_000_000);
assert_eq!(tx_log.num_inputs, 1);
assert_eq!(tx_log.num_outputs, 1);
assert_eq!(tx_log.fee, Some(my_fee_contribution(1, 1, 1, 2)?));
Ok(())
})?;
let mut invoice_proof = None;
// Now some time has passed, sender retrieves and verify the payment proof
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, _m| {
// Extract the stored data as an invoice proof
invoice_proof =
Some(api.retrieve_payment_proof_invoice(send_mask, true, None, Some(slate.id))?);
Ok(())
})?;
let invoice_proof = invoice_proof.unwrap();
let invoice_proof_json = serde_json::to_string(&invoice_proof).unwrap();
// Should have all proof fields filled out
println!("INVOICE PROOF: {}", invoice_proof_json);
wallet::controller::foreign_single_use(recv_wallet.clone(), recv_mask.cloned(), |api| {
let mut proof = serde_json::from_str(&invoice_proof_json).unwrap();
api.verify_payment_proof_invoice(recipient_address.as_ref().unwrap(), &proof)?;
// tweak something and it shouldn't verify
proof.amount = 400000;
let retval = api.verify_payment_proof_invoice(recipient_address.as_ref().unwrap(), &proof);
assert!(retval.is_err());
Ok(())
})?;
// let logging finish
stopper.store(false, Ordering::Relaxed);
thread::sleep(Duration::from_millis(200));
Ok(())
}
#[test]
fn contract_early_proofs_rsr() -> Result<(), libwallet::Error> {
let test_dir = "test_output/contract_early_proofs_rsr";
setup(test_dir);
contract_early_proofs_rsr_test_impl(test_dir)?;
clean_output_dir(test_dir);
Ok(())
}
@@ -0,0 +1,182 @@
// Copyright 2023 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.
//! Development and testing of early payment proofs, restricted at the moment
//! to contract-style transactions for experimental purposes
//!
//! https://github.com/mimblewimble/grin-rfcs/pull/70
//!
//!
extern crate grin_wallet_controller as wallet;
extern crate grin_wallet_impls as impls;
extern crate log;
use grin_wallet_libwallet as libwallet;
use impls::test_framework::{self};
use libwallet::contract::my_fee_contribution;
use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI};
use libwallet::{Slate, SlateState, TxLogEntryType};
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
#[macro_use]
mod common;
use common::{clean_output_dir, create_wallets, setup};
/// Development + Tests of early payment proof functionality
fn contract_early_proofs_srs_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// create two wallets and mine 4 blocks in each (we want both to have balance to get a payjoin)
let (wallets, chain, stopper, mut bh) =
create_wallets(vec![vec![("default", 4)], vec![("default", 4)]], test_dir).unwrap();
let send_wallet = wallets[0].0.clone();
let send_mask = wallets[0].1.as_ref();
let recv_wallet = wallets[1].0.clone();
let recv_mask = wallets[1].1.as_ref();
let mut slate = Slate::blank(0, true); // this gets overriden below
let mut sender_address = None;
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
// Send wallet inititates a standard transaction with --send=5
let args = &ContractNewArgsAPI {
setup_args: ContractSetupArgsAPI {
net_change: Some(-5_000_000_000),
..Default::default()
},
..Default::default()
};
slate = api.contract_new(m, args)?;
sender_address = Some(api.get_slatepack_address(send_mask, 0)?.pub_key);
Ok(())
})?;
assert_eq!(slate.state, SlateState::Standard1);
let mut recipient_address = None;
wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| {
// Receive wallet calls --receive=5
let args = &mut ContractSetupArgsAPI {
net_change: Some(5_000_000_000),
..Default::default()
};
// Note sender address explicity added here
args.proof_args.sender_address = sender_address;
slate = api.contract_sign(m, &slate, args)?;
recipient_address = Some(api.get_slatepack_address(recv_mask, 0)?.pub_key);
Ok(())
})?;
assert_eq!(slate.state, SlateState::Standard2);
// Send wallet finalizes and posts
//let mut sender_part_sig = None;
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
let args = &ContractSetupArgsAPI {
..Default::default()
};
slate = api.contract_sign(m, &slate, args)?;
Ok(())
})?;
assert_eq!(slate.state, SlateState::Standard3);
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
api.post_tx(m, &slate, false)?;
Ok(())
})?;
bh += 1;
let _ =
test_framework::award_blocks_to_wallet(&chain, send_wallet.clone(), send_mask, 3, false);
bh += 3;
// Assert changes in receive wallet
wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| {
let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?;
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert_eq!(wallet_info.last_confirmed_height, bh);
assert!(refreshed);
assert_eq!(txs.len(), 5); // 4 mined and 1 received
let tx_log = txs[4].clone();
assert_eq!(tx_log.tx_type, TxLogEntryType::TxReceived);
assert_eq!(tx_log.amount_credited, 5_000_000_000);
assert_eq!(tx_log.amount_debited, 0);
assert_eq!(tx_log.num_inputs, 1);
assert_eq!(tx_log.num_outputs, 1);
let expected_fees_paid = Some(my_fee_contribution(1, 1, 1, 2)?);
assert_eq!(tx_log.fee, expected_fees_paid);
assert_eq!(
wallet_info.amount_currently_spendable,
4 * 60_000_000_000 + 5_000_000_000 - expected_fees_paid.unwrap().fee() // we expect the balance of 4 mined blocks + 5 Grin - fees paid
);
Ok(())
})?;
// Assert changes in send wallet
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?;
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert_eq!(wallet_info.last_confirmed_height, bh);
assert!(refreshed);
assert_eq!(txs.len() as u64, bh - 4 + 1); // send wallet didn't mine 4 blocks and made 1 tx
let tx_log = txs[txs.len() - 5].clone(); // TODO: why -5 and not -4?
assert_eq!(tx_log.tx_type, TxLogEntryType::TxSent);
assert_eq!(tx_log.amount_credited, 0);
assert_eq!(tx_log.amount_debited, 5_000_000_000);
assert_eq!(tx_log.num_inputs, 1);
assert_eq!(tx_log.num_outputs, 1);
assert_eq!(tx_log.fee, Some(my_fee_contribution(1, 1, 1, 2)?));
Ok(())
})?;
let mut invoice_proof = None;
// Now some time has passed, sender retrieves and verify the payment proof
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, _m| {
// Extract the stored data as an invoice proof
invoice_proof =
Some(api.retrieve_payment_proof_invoice(send_mask, true, None, Some(slate.id))?);
Ok(())
})?;
let invoice_proof = invoice_proof.unwrap();
let invoice_proof_json = serde_json::to_string(&invoice_proof).unwrap();
// Should have all proof fields filled out
println!("INVOICE PROOF: {}", invoice_proof_json);
wallet::controller::foreign_single_use(recv_wallet.clone(), recv_mask.cloned(), |api| {
let mut proof = serde_json::from_str(&invoice_proof_json).unwrap();
api.verify_payment_proof_invoice(recipient_address.as_ref().unwrap(), &proof)?;
// tweak something and it shouldn't verify
proof.amount = 400000;
let retval = api.verify_payment_proof_invoice(recipient_address.as_ref().unwrap(), &proof);
assert!(retval.is_err());
Ok(())
})?;
// let logging finish
stopper.store(false, Ordering::Relaxed);
thread::sleep(Duration::from_millis(200));
Ok(())
}
#[test]
fn contract_early_proofs_srs() -> Result<(), libwallet::Error> {
let test_dir = "test_output/contract_early_proofs_src";
setup(test_dir);
contract_early_proofs_srs_test_impl(test_dir)?;
clean_output_dir(test_dir);
Ok(())
}
+153
View File
@@ -0,0 +1,153 @@
// Copyright 2022 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 doing contract RSR flow
// #[macro_use]
extern crate grin_wallet_controller as wallet;
extern crate grin_wallet_impls as impls;
extern crate log;
use grin_wallet_libwallet as libwallet;
use impls::test_framework::{self};
use libwallet::contract::my_fee_contribution;
use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI};
use libwallet::{Slate, SlateState, TxLogEntryType};
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
#[macro_use]
mod common;
use common::{clean_output_dir, create_wallets, setup};
/// contract RSR flow
fn contract_rsr_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// create two wallets and mine 4 blocks in each (we want both to have balance to get a payjoin)
let (wallets, chain, stopper, mut bh) =
create_wallets(vec![vec![("default", 4)], vec![("default", 4)]], test_dir).unwrap();
let send_wallet = wallets[0].0.clone();
let send_mask = wallets[0].1.as_ref();
let recv_wallet = wallets[1].0.clone();
let recv_mask = wallets[1].1.as_ref();
let mut slate = Slate::blank(0, true); // this gets overriden below
wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| {
// Receive wallet inititates an invoice transaction with --receive=5
let args = &mut ContractNewArgsAPI {
setup_args: ContractSetupArgsAPI {
net_change: Some(5_000_000_000),
..Default::default()
},
..Default::default()
};
args.setup_args.proof_args.suppress_proof = true;
slate = api.contract_new(m, args)?;
Ok(())
})?;
assert_eq!(slate.state, SlateState::Invoice1);
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
// Send Wallet calls --send=5
let args = &ContractSetupArgsAPI {
net_change: Some(-5_000_000_000),
..Default::default()
};
slate = api.contract_sign(m, &slate, args)?;
Ok(())
})?;
assert_eq!(slate.state, SlateState::Invoice2);
// Receive wallet finalizes and posts
wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| {
let args = &mut ContractSetupArgsAPI {
..Default::default()
};
// TODO: This possibly shouldn't be needed here if no proof?
args.proof_args.suppress_proof = true;
slate = api.contract_sign(m, &slate, args)?;
Ok(())
})?;
assert_eq!(slate.state, SlateState::Invoice3);
// Send wallet posts so receive wallet doesn't get the mined amount
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
api.post_tx(m, &slate, false)?;
Ok(())
})?;
bh += 1;
let _ =
test_framework::award_blocks_to_wallet(&chain, send_wallet.clone(), send_mask, 3, false);
bh += 3;
// Assert changes in receive wallet
wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| {
let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?;
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert_eq!(wallet_info.last_confirmed_height, bh);
assert!(refreshed);
assert_eq!(txs.len(), 5); // 4 mined and 1 received
let tx_log = txs[4].clone();
assert_eq!(tx_log.tx_type, TxLogEntryType::TxReceived);
assert_eq!(tx_log.amount_credited, 5_000_000_000);
assert_eq!(tx_log.amount_debited, 0);
assert_eq!(tx_log.num_inputs, 1);
assert_eq!(tx_log.num_outputs, 1);
let expected_fees_paid = Some(my_fee_contribution(1, 1, 1, 2)?);
assert_eq!(tx_log.fee, expected_fees_paid);
assert_eq!(
wallet_info.amount_currently_spendable,
4 * 60_000_000_000 + 5_000_000_000 - expected_fees_paid.unwrap().fee() // we expect the balance of 4 mined blocks + 5 Grin - fees paid
);
// println!("txlogentry: {:#?}", tx_log);
// println!("wallet info: {:#?}", wallet_info);
// let (validated, commits) = api.retrieve_outputs(m, true, false, Some(tx_log.id))?;
// println!("commits: {:#?}", commits);
// panic!("lala");
Ok(())
})?;
// Assert changes in send wallet
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?;
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert_eq!(wallet_info.last_confirmed_height, bh);
assert!(refreshed);
assert_eq!(txs.len() as u64, bh - 4 + 1); // send_wallet didn't mine 4 blocks and made 1 tx
let tx_log = txs[txs.len() - 5].clone(); // TODO: why -5 and not -4?
assert_eq!(tx_log.tx_type, TxLogEntryType::TxSent);
assert_eq!(tx_log.amount_credited, 0);
assert_eq!(tx_log.amount_debited, 5_000_000_000);
assert_eq!(tx_log.num_inputs, 1);
assert_eq!(tx_log.num_outputs, 1);
assert_eq!(tx_log.fee, Some(my_fee_contribution(1, 1, 1, 2)?));
Ok(())
})?;
// let logging finish
stopper.store(false, Ordering::Relaxed);
thread::sleep(Duration::from_millis(200));
Ok(())
}
#[test]
fn wallet_contract_rsr_tx() -> Result<(), libwallet::Error> {
let test_dir = "test_output/contract_rsr_tx";
setup(test_dir);
contract_rsr_tx_impl(test_dir)?;
clean_output_dir(test_dir);
Ok(())
}
+111
View File
@@ -0,0 +1,111 @@
// Copyright 2022 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 doing contract self-spend flow
// #[macro_use]
extern crate grin_wallet_controller as wallet;
extern crate grin_wallet_impls as impls;
extern crate log;
use grin_wallet_libwallet as libwallet;
use impls::test_framework::{self};
use libwallet::contract::my_fee_contribution;
use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI};
use libwallet::{Slate, SlateState, TxLogEntryType};
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
#[macro_use]
mod common;
use common::{clean_output_dir, create_wallets, setup};
/// contract self-spend flow
fn contract_self_spend_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// create a single wallet and mine 4 blocks
let (wallets, chain, stopper, mut bh) =
create_wallets(vec![vec![("default", 4)]], test_dir).unwrap();
let send_wallet = wallets[0].0.clone();
let send_mask = wallets[0].1.as_ref();
let mut slate = Slate::blank(0, true); // this gets overriden below
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
// Send wallet inititates a standard transaction with --send=0
let args = &ContractNewArgsAPI {
setup_args: ContractSetupArgsAPI {
net_change: Some(0),
num_participants: 1,
..Default::default()
},
..Default::default()
};
slate = api.contract_new(m, args)?;
Ok(())
})?;
assert_eq!(slate.state, SlateState::Standard1);
// Send wallet finalizes and posts
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
let args = &ContractSetupArgsAPI {
..Default::default()
};
slate = api.contract_sign(m, &slate, args)?;
Ok(())
})?;
// In the case of a self-spend, we just finish the slate when it's in the Standard2 state
assert_eq!(slate.state, SlateState::Standard2);
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
api.post_tx(m, &slate, false)?;
Ok(())
})?;
bh += 1;
let _ =
test_framework::award_blocks_to_wallet(&chain, send_wallet.clone(), send_mask, 3, false);
bh += 3;
// Assert changes in send wallet
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?;
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert_eq!(wallet_info.last_confirmed_height, bh);
assert!(refreshed);
assert_eq!(txs.len() as u64, bh + 1); // send wallet didn't mine 4 blocks and made 1 tx
let tx_log = txs[txs.len() - 5].clone(); // TODO: why -5 and not -4?
assert_eq!(tx_log.tx_type, TxLogEntryType::TxSelfSpend);
assert_eq!(tx_log.amount_credited, 0);
assert_eq!(tx_log.amount_debited, 0);
assert_eq!(tx_log.num_inputs, 1);
assert_eq!(tx_log.num_outputs, 1);
assert_eq!(tx_log.fee, Some(my_fee_contribution(1, 1, 1, 1)?));
Ok(())
})?;
// let logging finish
stopper.store(false, Ordering::Relaxed);
thread::sleep(Duration::from_millis(200));
Ok(())
}
#[test]
fn wallet_contract_self_spend_tx() -> Result<(), libwallet::Error> {
let test_dir = "test_output/contract_self_spend_tx";
setup(test_dir);
contract_self_spend_tx_impl(test_dir)?;
clean_output_dir(test_dir);
Ok(())
}
@@ -0,0 +1,93 @@
// Copyright 2022 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 doing contract self-spend flow
// #[macro_use]
extern crate grin_wallet_controller as wallet;
extern crate grin_wallet_impls as impls;
extern crate log;
use grin_wallet_libwallet as libwallet;
use impls::test_framework::{self};
use libwallet::contract::my_fee_contribution;
use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI};
use libwallet::{Slate, SlateState, TxLogEntryType};
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
#[macro_use]
mod common;
use common::{clean_output_dir, create_wallets, setup};
/// contract self-spend flow
fn contract_self_spend_cancel_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// create a single wallet and mine 4 blocks
let (wallets, chain, stopper, mut bh) =
create_wallets(vec![vec![("default", 4)]], test_dir).unwrap();
let send_wallet = wallets[0].0.clone();
let send_mask = wallets[0].1.as_ref();
let mut slate = Slate::blank(0, true); // this gets overriden below
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
// Send wallet initiates a standard transaction with --send=0
let args = &ContractNewArgsAPI {
setup_args: ContractSetupArgsAPI {
net_change: Some(0),
num_participants: 1,
..Default::default()
},
..Default::default()
};
slate = api.contract_new(m, args)?;
Ok(())
})?;
assert_eq!(slate.state, SlateState::Standard1);
// Send wallet finalizes and posts
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
api.cancel_tx(m, None, Some(slate.id))?;
Ok(())
})?;
// Assert tx log has been cancelled
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
let query_args = libwallet::RetrieveTxQueryArgs {
exclude_cancelled: Some(false),
..Default::default()
};
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, Some(query_args))?;
assert!(refreshed);
assert_eq!(txs.len() as u64, 5); // send wallet didn't mine 4 blocks and made 1 tx
let tx_log = txs[4].clone(); // TODO: why -5 and not -4?
assert_eq!(tx_log.tx_type, TxLogEntryType::TxSelfSpendCancelled);
Ok(())
})?;
// let logging finish
stopper.store(false, Ordering::Relaxed);
thread::sleep(Duration::from_millis(200));
Ok(())
}
#[test]
fn wallet_contract_self_spend_cancel() -> Result<(), libwallet::Error> {
let test_dir = "test_output/contract_self_spend_cancel";
setup(test_dir);
contract_self_spend_cancel_impl(test_dir)?;
clean_output_dir(test_dir);
Ok(())
}
@@ -0,0 +1,160 @@
// Copyright 2022 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 doing contract self-spend flow by using custom inputs and creating custom outputs
// #[macro_use]
extern crate grin_wallet_controller as wallet;
extern crate grin_wallet_impls as impls;
extern crate log;
use grin_wallet_libwallet as libwallet;
use grin_core::consensus;
use impls::test_framework::{self};
use libwallet::contract::my_fee_contribution;
use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI, OutputSelectionArgs};
use libwallet::{OutputStatus, Slate, SlateState, TxLogEntryType};
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
#[macro_use]
mod common;
use common::{clean_output_dir, create_wallets, setup};
/// contract self-spend flow with custom picked inputs and outputs
fn contract_self_spend_custom_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// create a single wallet and mine 4 blocks
let (wallets, chain, stopper, mut bh) =
create_wallets(vec![vec![("default", 10)]], test_dir).unwrap();
let send_wallet = wallets[0].0.clone();
let send_mask = wallets[0].1.as_ref();
let mut use_inputs = String::from("");
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
let (_, commits) = api.retrieve_outputs(m, true, false, None)?;
println!("OOOT: {:?}", commits[0].output);
use_inputs = format!(
"{},{}",
commits[0].output.commit.as_ref().unwrap(),
commits[1].output.commit.as_ref().unwrap()
);
Ok(())
})?;
let mut slate = Slate::blank(0, true); // this gets overriden below
let selection_args = OutputSelectionArgs {
min_input_confirmation: 0,
use_inputs: Some(use_inputs.clone()), // we will use two coinbase inputs
make_outputs: Some(String::from("88,35,3,0.2,15")), // the sum is such that it will need to pick another input making total of 3 inputs
};
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
// Send wallet inititates a standard transaction with --send=0
let args = &ContractNewArgsAPI {
setup_args: ContractSetupArgsAPI {
net_change: Some(0),
num_participants: 1,
selection_args: selection_args.clone(),
..Default::default()
},
..Default::default()
};
slate = api.contract_new(m, args)?;
Ok(())
})?;
assert_eq!(slate.state, SlateState::Standard1);
// Send wallet finalizes and posts
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
let args = &ContractSetupArgsAPI {
..Default::default()
};
slate = api.contract_sign(m, &slate, args)?;
Ok(())
})?;
// In the case of a self-spend, we just finish the slate when it's in the Standard2 state
assert_eq!(slate.state, SlateState::Standard2);
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
api.post_tx(m, &slate, false)?;
Ok(())
})?;
bh += 1;
let _ =
test_framework::award_blocks_to_wallet(&chain, send_wallet.clone(), send_mask, 3, false);
bh += 3;
// Assert changes in send wallet
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?;
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert_eq!(wallet_info.last_confirmed_height, bh);
assert!(refreshed);
assert_eq!(txs.len() as u64, bh + 1); // send wallet didn't mine 4 blocks and made 1 tx
let tx_log = txs[txs.len() - 5].clone(); // TODO: why -5 and not -4?
assert_eq!(tx_log.tx_type, TxLogEntryType::TxSelfSpend);
assert_eq!(tx_log.amount_credited, 0);
assert_eq!(tx_log.amount_debited, 0);
assert_eq!(tx_log.num_inputs, 3);
assert_eq!(tx_log.num_outputs, 6);
assert_eq!(tx_log.fee, Some(my_fee_contribution(3, 6, 1, 1)?));
Ok(())
})?;
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
let (_, commits) = api.retrieve_outputs(m, true, false, None)?;
// Assert used inputs are the ones we specified
let used_inputs = use_inputs.split(",").collect::<Vec<&str>>();
assert_eq!(commits[0].output.status, OutputStatus::Spent);
assert_eq!(commits[0].output.commit.as_ref().unwrap(), used_inputs[0]);
assert_eq!(commits[1].output.status, OutputStatus::Spent);
assert_eq!(commits[1].output.commit.as_ref().unwrap(), used_inputs[1]);
assert_eq!(commits[2].output.status, OutputStatus::Spent);
// Assert expected outputs were created
// 88, 35, 3, 0.2, 15 and a change output
assert_eq!(commits[10].output.value, 88 * consensus::GRIN_BASE);
assert_eq!(commits[11].output.value, 35 * consensus::GRIN_BASE);
assert_eq!(commits[12].output.value, 3 * consensus::GRIN_BASE);
assert_eq!(
commits[13].output.value,
(0.2 * consensus::GRIN_BASE as f64) as u64
);
assert_eq!(commits[14].output.value, 15 * consensus::GRIN_BASE);
// change output is 3*reward - (88-35-3-0.2-15) - my_fees
assert_eq!(
commits[15].output.value,
3 * consensus::REWARD
- selection_args.sum_output_amounts()
- my_fee_contribution(3, 6, 1, 1)?.fee()
);
Ok(())
})?;
// let logging finish
stopper.store(false, Ordering::Relaxed);
thread::sleep(Duration::from_millis(200));
Ok(())
}
#[test]
fn wallet_contract_self_spend_custom_tx() -> Result<(), libwallet::Error> {
let test_dir = "test_output/contract_self_spend_custom_tx";
setup(test_dir);
contract_self_spend_custom_tx_impl(test_dir)?;
clean_output_dir(test_dir);
Ok(())
}
+146
View File
@@ -0,0 +1,146 @@
// Copyright 2022 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 doing contract SRS flow
// #[macro_use]
extern crate grin_wallet_controller as wallet;
extern crate grin_wallet_impls as impls;
extern crate log;
use grin_wallet_libwallet as libwallet;
use impls::test_framework::{self};
use libwallet::contract::my_fee_contribution;
use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI};
use libwallet::{Slate, SlateState, TxLogEntryType};
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
#[macro_use]
mod common;
use common::{clean_output_dir, create_wallets, setup};
/// contract SRS flow
fn contract_srs_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// create two wallets and mine 4 blocks in each (we want both to have balance to get a payjoin)
let (wallets, chain, stopper, mut bh) =
create_wallets(vec![vec![("default", 4)], vec![("default", 4)]], test_dir).unwrap();
let send_wallet = wallets[0].0.clone();
let send_mask = wallets[0].1.as_ref();
let recv_wallet = wallets[1].0.clone();
let recv_mask = wallets[1].1.as_ref();
let mut slate = Slate::blank(0, true); // this gets overriden below
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
// Send wallet inititates a standard transaction with --send=5
let args = &mut ContractNewArgsAPI {
setup_args: ContractSetupArgsAPI {
net_change: Some(-5_000_000_000),
..Default::default()
},
..Default::default()
};
slate = api.contract_new(m, args)?;
Ok(())
})?;
assert_eq!(slate.state, SlateState::Standard1);
wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| {
// Receive wallet calls --receive=5
let args = &mut ContractSetupArgsAPI {
net_change: Some(5_000_000_000),
..Default::default()
};
args.proof_args.suppress_proof = true;
slate = api.contract_sign(m, &slate, args)?;
Ok(())
})?;
assert_eq!(slate.state, SlateState::Standard2);
// Send wallet finalizes and posts
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
let args = &mut ContractSetupArgsAPI {
..Default::default()
};
args.proof_args.suppress_proof = true;
slate = api.contract_sign(m, &slate, args)?;
Ok(())
})?;
assert_eq!(slate.state, SlateState::Standard3);
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
api.post_tx(m, &slate, false)?;
Ok(())
})?;
bh += 1;
let _ =
test_framework::award_blocks_to_wallet(&chain, send_wallet.clone(), send_mask, 3, false);
bh += 3;
// Assert changes in receive wallet
wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| {
let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?;
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert_eq!(wallet_info.last_confirmed_height, bh);
assert!(refreshed);
assert_eq!(txs.len(), 5); // 4 mined and 1 received
let tx_log = txs[4].clone();
assert_eq!(tx_log.tx_type, TxLogEntryType::TxReceived);
assert_eq!(tx_log.amount_credited, 5_000_000_000);
assert_eq!(tx_log.amount_debited, 0);
assert_eq!(tx_log.num_inputs, 1);
assert_eq!(tx_log.num_outputs, 1);
let expected_fees_paid = Some(my_fee_contribution(1, 1, 1, 2)?);
assert_eq!(tx_log.fee, expected_fees_paid);
assert_eq!(
wallet_info.amount_currently_spendable,
4 * 60_000_000_000 + 5_000_000_000 - expected_fees_paid.unwrap().fee() // we expect the balance of 4 mined blocks + 5 Grin - fees paid
);
Ok(())
})?;
// Assert changes in send wallet
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?;
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert_eq!(wallet_info.last_confirmed_height, bh);
assert!(refreshed);
assert_eq!(txs.len() as u64, bh - 4 + 1); // send wallet didn't mine 4 blocks and made 1 tx
let tx_log = txs[txs.len() - 5].clone(); // TODO: why -5 and not -4?
assert_eq!(tx_log.tx_type, TxLogEntryType::TxSent);
assert_eq!(tx_log.amount_credited, 0);
assert_eq!(tx_log.amount_debited, 5_000_000_000);
assert_eq!(tx_log.num_inputs, 1);
assert_eq!(tx_log.num_outputs, 1);
assert_eq!(tx_log.fee, Some(my_fee_contribution(1, 1, 1, 2)?));
Ok(())
})?;
// let logging finish
stopper.store(false, Ordering::Relaxed);
thread::sleep(Duration::from_millis(200));
Ok(())
}
#[test]
fn wallet_contract_srs_tx() -> Result<(), libwallet::Error> {
let test_dir = "test_output/contract_srs_tx";
setup(test_dir);
contract_srs_tx_impl(test_dir)?;
clean_output_dir(test_dir);
Ok(())
}
+156
View File
@@ -0,0 +1,156 @@
// Copyright 2022 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 doing contract SRS flow
// #[macro_use]
extern crate grin_wallet_controller as wallet;
extern crate grin_wallet_impls as impls;
extern crate log;
use grin_wallet_libwallet as libwallet;
use impls::test_framework::{self};
use libwallet::contract::my_fee_contribution;
use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI};
use libwallet::mwmixnet::onion::crypto::secp;
use libwallet::mwmixnet::types::MixnetReqCreationParams;
use libwallet::{Slate, SlateState, TxLogEntryType};
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
#[macro_use]
mod common;
use common::{clean_output_dir, create_wallets, setup};
/// contract SRS flow - just creating an mwmixnet tx at the moment
fn contract_srs_mwmixnet_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
// create two wallets and mine 4 blocks in each (we want both to have balance to get a payjoin)
let (wallets, chain, stopper, mut bh) =
create_wallets(vec![vec![("default", 4)], vec![("default", 4)]], test_dir).unwrap();
let send_wallet = wallets[0].0.clone();
let send_mask = wallets[0].1.as_ref();
let recv_wallet = wallets[1].0.clone();
let recv_mask = wallets[1].1.as_ref();
let mut slate = Slate::blank(0, true); // this gets overriden below
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
// Send wallet inititates a standard transaction with --send=5
let args = &mut ContractNewArgsAPI {
setup_args: ContractSetupArgsAPI {
net_change: Some(-5_000_000_000),
..Default::default()
},
..Default::default()
};
slate = api.contract_new(m, args)?;
Ok(())
})?;
assert_eq!(slate.state, SlateState::Standard1);
wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| {
// Receive wallet calls --receive=5
let args = &mut ContractSetupArgsAPI {
net_change: Some(5_000_000_000),
..Default::default()
};
args.proof_args.suppress_proof = true;
slate = api.contract_sign(m, &slate, args)?;
Ok(())
})?;
assert_eq!(slate.state, SlateState::Standard2);
// Send wallet finalizes and posts
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
let args = &mut ContractSetupArgsAPI {
..Default::default()
};
args.proof_args.suppress_proof = true;
slate = api.contract_sign(m, &slate, args)?;
Ok(())
})?;
assert_eq!(slate.state, SlateState::Standard3);
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
let server_key_1 = secp::random_secret();
let server_key_2 = secp::random_secret();
let params = MixnetReqCreationParams {
server_keys: vec![server_key_1, server_key_2],
fee_per_hop: 50_000_000,
};
//api.create_mwmixnet_req(send_mask, &params, &slate)?;
Ok(())
})?;
bh += 1;
/*
let _ =
test_framework::award_blocks_to_wallet(&chain, send_wallet.clone(), send_mask, 3, false);
bh += 3;
// Assert changes in receive wallet
wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| {
let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?;
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert_eq!(wallet_info.last_confirmed_height, bh);
assert!(refreshed);
assert_eq!(txs.len(), 5); // 4 mined and 1 received
let tx_log = txs[4].clone();
assert_eq!(tx_log.tx_type, TxLogEntryType::TxReceived);
assert_eq!(tx_log.amount_credited, 5_000_000_000);
assert_eq!(tx_log.amount_debited, 0);
assert_eq!(tx_log.num_inputs, 1);
assert_eq!(tx_log.num_outputs, 1);
let expected_fees_paid = Some(my_fee_contribution(1, 1, 1, 2)?);
assert_eq!(tx_log.fee, expected_fees_paid);
assert_eq!(
wallet_info.amount_currently_spendable,
4 * 60_000_000_000 + 5_000_000_000 - expected_fees_paid.unwrap().fee() // we expect the balance of 4 mined blocks + 5 Grin - fees paid
);
Ok(())
})?;
// Assert changes in send wallet
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?;
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?;
assert_eq!(wallet_info.last_confirmed_height, bh);
assert!(refreshed);
assert_eq!(txs.len() as u64, bh - 4 + 1); // send wallet didn't mine 4 blocks and made 1 tx
let tx_log = txs[txs.len() - 5].clone(); // TODO: why -5 and not -4?
assert_eq!(tx_log.tx_type, TxLogEntryType::TxSent);
assert_eq!(tx_log.amount_credited, 0);
assert_eq!(tx_log.amount_debited, 5_000_000_000);
assert_eq!(tx_log.num_inputs, 1);
assert_eq!(tx_log.num_outputs, 1);
assert_eq!(tx_log.fee, Some(my_fee_contribution(1, 1, 1, 2)?));
Ok(())
})?;*/
// let logging finish
stopper.store(false, Ordering::Relaxed);
thread::sleep(Duration::from_millis(200));
Ok(())
}
#[test]
fn wallet_contract_srs_mwmixnet_tx() -> Result<(), libwallet::Error> {
let test_dir = "test_output/contract_srs_mwmixnet_tx";
setup(test_dir);
contract_srs_mwmixnet_tx_impl(test_dir)?;
clean_output_dir(test_dir);
Ok(())
}
+125
View File
@@ -0,0 +1,125 @@
// 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, RetrieveTxQueryArgs};
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_cancel_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;
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: None,
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, None, None)?;
Ok(())
})?;
// Now cancel the transaction
api.cancel_tx(mask1, None, Some(slate.id.clone()))?;
bh += 1;
Ok(())
})?;
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let query = RetrieveTxQueryArgs {
include_outstanding_only: Some(true),
exclude_cancelled: Some(true),
..Default::default()
};
let txs = api.retrieve_txs(mask1, true, None, None, Some(query))?;
for tx in txs.1.iter() {
println!("Tx: {:?}", tx);
}
assert!(txs.1.is_empty());
Ok(())
})?;
// let logging finish
stopper.store(false, Ordering::Relaxed);
thread::sleep(Duration::from_millis(1000));
Ok(())
}
#[test]
fn wallet_self_send_cancel() {
let test_dir = "test_output/self_send_cancel";
setup(test_dir);
if let Err(e) = self_send_cancel_test_impl(test_dir) {
panic!("Libwallet Error: {}", e);
}
clean_output_dir(test_dir);
}