[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
+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);
}