initial commit

This commit is contained in:
yeastplume
2019-02-13 15:05:19 +00:00
commit da288f0139
57 changed files with 17941 additions and 0 deletions
+259
View File
@@ -0,0 +1,259 @@
// Copyright 2018 The Grin Developers
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! tests differing accounts in the same wallet
#[macro_use]
extern crate log;
use self::core::global;
use self::core::global::ChainTypes;
use self::keychain::{ExtKeychain, Keychain};
use self::wallet::libwallet;
use self::wallet::test_framework::{self, LocalWalletClient, WalletProxy};
use grin_core as core;
use grin_keychain as keychain;
use grin_util as util;
use grin_wallet as wallet;
use std::fs;
use std::thread;
use std::time::Duration;
fn clean_output_dir(test_dir: &str) {
let _ = fs::remove_dir_all(test_dir);
}
fn setup(test_dir: &str) {
util::init_test_logger();
clean_output_dir(test_dir);
global::set_mining_mode(ChainTypes::AutomatedTesting);
}
/// Various tests on accounts within the same wallet
fn accounts_test_impl(test_dir: &str) -> Result<(), libwallet::Error> {
setup(test_dir);
// Create a new proxy to simulate server and wallet responses
let mut wallet_proxy: WalletProxy<LocalWalletClient, ExtKeychain> = WalletProxy::new(test_dir);
let chain = wallet_proxy.chain.clone();
// Create a new wallet test client, and set its queues to communicate with the
// proxy
let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone());
let wallet1 =
test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone(), None);
wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone());
let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone());
// define recipient wallet, add to proxy
let wallet2 =
test_framework::create_wallet(&format!("{}/wallet2", test_dir), client2.clone(), None);
wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone());
// Set the wallet proxy listener running
thread::spawn(move || {
if let Err(e) = wallet_proxy.run() {
error!("Wallet Proxy error: {}", e);
}
});
// few values to keep things shorter
let reward = core::consensus::REWARD;
let cm = global::coinbase_maturity(); // assume all testing precedes soft fork height
// test default accounts exist
wallet::controller::owner_single_use(wallet1.clone(), |api| {
let accounts = api.accounts()?;
assert_eq!(accounts[0].label, "default");
assert_eq!(accounts[0].path, ExtKeychain::derive_key_id(2, 0, 0, 0, 0));
Ok(())
})?;
// add some accounts
wallet::controller::owner_single_use(wallet1.clone(), |api| {
let new_path = api.create_account_path("account1").unwrap();
assert_eq!(new_path, ExtKeychain::derive_key_id(2, 1, 0, 0, 0));
let new_path = api.create_account_path("account2").unwrap();
assert_eq!(new_path, ExtKeychain::derive_key_id(2, 2, 0, 0, 0));
let new_path = api.create_account_path("account3").unwrap();
assert_eq!(new_path, ExtKeychain::derive_key_id(2, 3, 0, 0, 0));
// trying to add same label again should fail
let res = api.create_account_path("account1");
assert!(res.is_err());
Ok(())
})?;
// add account to wallet 2
wallet::controller::owner_single_use(wallet2.clone(), |api| {
let new_path = api.create_account_path("listener_account").unwrap();
assert_eq!(new_path, ExtKeychain::derive_key_id(2, 1, 0, 0, 0));
Ok(())
})?;
// Default wallet 2 to listen on that account
{
let mut w = wallet2.lock();
w.set_parent_key_id_by_name("listener_account")?;
}
// Mine into two different accounts in the same wallet
{
let mut w = wallet1.lock();
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(), 7);
{
let mut w = wallet1.lock();
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(), 5);
// Should have 5 in account1 (5 spendable), 5 in account (2 spendable)
wallet::controller::owner_single_use(wallet1.clone(), |api| {
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(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(true, None, None)?;
assert_eq!(txs.len(), 5);
Ok(())
})?;
// now check second account
{
let mut w = wallet1.lock();
w.set_parent_key_id_by_name("account1")?;
}
wallet::controller::owner_single_use(wallet1.clone(), |api| {
// check last confirmed height on this account is different from above (should be 0)
let (_, wallet1_info) = api.retrieve_summary_info(false, 1)?;
assert_eq!(wallet1_info.last_confirmed_height, 0);
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(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(true, None, None)?;
assert_eq!(txs.len(), 7);
Ok(())
})?;
// should be nothing in default account
{
let mut w = wallet1.lock();
w.set_parent_key_id_by_name("default")?;
}
wallet::controller::owner_single_use(wallet1.clone(), |api| {
let (_, wallet1_info) = api.retrieve_summary_info(false, 1)?;
assert_eq!(wallet1_info.last_confirmed_height, 0);
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(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(true, None, None)?;
assert_eq!(txs.len(), 0);
Ok(())
})?;
// Send a tx to another wallet
{
let mut w = wallet1.lock();
w.set_parent_key_id_by_name("account1")?;
}
wallet::controller::owner_single_use(wallet1.clone(), |api| {
let (mut slate, lock_fn) = api.initiate_tx(
None, reward, // amount
2, // minimum confirmations
500, // max outputs
1, // num change outputs
true, // select all outputs
None,
)?;
slate = client1.send_tx_slate_direct("wallet2", &slate)?;
api.tx_lock_outputs(&slate, lock_fn)?;
api.finalize_tx(&mut slate)?;
api.post_tx(&slate.tx, false)?;
Ok(())
})?;
wallet::controller::owner_single_use(wallet1.clone(), |api| {
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(true, 1)?;
assert!(wallet1_refreshed);
assert_eq!(wallet1_info.last_confirmed_height, 13);
let (_, txs) = api.retrieve_txs(true, None, None)?;
assert_eq!(txs.len(), 9);
Ok(())
})?;
// other account should be untouched
{
let mut w = wallet1.lock();
w.set_parent_key_id_by_name("account2")?;
}
wallet::controller::owner_single_use(wallet1.clone(), |api| {
let (_, wallet1_info) = api.retrieve_summary_info(false, 1)?;
assert_eq!(wallet1_info.last_confirmed_height, 12);
let (_, wallet1_info) = api.retrieve_summary_info(true, 1)?;
assert_eq!(wallet1_info.last_confirmed_height, 13);
let (_, txs) = api.retrieve_txs(true, 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(wallet2.clone(), |api| {
let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(true, 1)?;
assert!(wallet2_refreshed);
assert_eq!(wallet2_info.last_confirmed_height, 13);
let (_, txs) = api.retrieve_txs(true, None, None)?;
assert_eq!(txs.len(), 1);
Ok(())
})?;
// Default account on wallet 2 should be untouched
{
let mut w = wallet2.lock();
w.set_parent_key_id_by_name("default")?;
}
wallet::controller::owner_single_use(wallet2.clone(), |api| {
let (_, wallet2_info) = api.retrieve_summary_info(false, 1)?;
assert_eq!(wallet2_info.last_confirmed_height, 0);
let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(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(true, None, None)?;
assert_eq!(txs.len(), 0);
Ok(())
})?;
// let logging finish
thread::sleep(Duration::from_millis(200));
Ok(())
}
#[test]
fn accounts() {
let test_dir = "test_output/accounts";
if let Err(e) = accounts_test_impl(test_dir) {
panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap());
}
}
+591
View File
@@ -0,0 +1,591 @@
// Copyright 2018 The Grin Developers
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! tests differing accounts in the same wallet
#[macro_use]
extern crate log;
use self::core::consensus;
use self::core::global;
use self::core::global::ChainTypes;
use self::keychain::ExtKeychain;
use self::wallet::test_framework::{self, LocalWalletClient, WalletProxy};
use self::wallet::{libwallet, FileWalletCommAdapter};
use grin_core as core;
use grin_keychain as keychain;
use grin_util as util;
use grin_wallet as wallet;
use std::fs;
use std::thread;
use std::time::Duration;
fn clean_output_dir(test_dir: &str) {
let _ = fs::remove_dir_all(test_dir);
}
fn setup(test_dir: &str) {
util::init_test_logger();
clean_output_dir(test_dir);
global::set_mining_mode(ChainTypes::AutomatedTesting);
}
/// Various tests on checking functionality
fn check_repair_impl(test_dir: &str) -> Result<(), libwallet::Error> {
setup(test_dir);
// Create a new proxy to simulate server and wallet responses
let mut wallet_proxy: WalletProxy<LocalWalletClient, ExtKeychain> = WalletProxy::new(test_dir);
let chain = wallet_proxy.chain.clone();
// Create a new wallet test client, and set its queues to communicate with the
// proxy
let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone());
let wallet1 =
test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone(), None);
wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone());
let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone());
// define recipient wallet, add to proxy
let wallet2 =
test_framework::create_wallet(&format!("{}/wallet2", test_dir), client2.clone(), None);
wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone());
// Set the wallet proxy listener running
thread::spawn(move || {
if let Err(e) = wallet_proxy.run() {
error!("Wallet Proxy error: {}", e);
}
});
// few values to keep things shorter
let reward = core::consensus::REWARD;
let cm = global::coinbase_maturity() as u64; // assume all testing precedes soft fork height
// add some accounts
wallet::controller::owner_single_use(wallet1.clone(), |api| {
api.create_account_path("account_1")?;
api.create_account_path("account_2")?;
api.create_account_path("account_3")?;
api.set_active_account("account_1")?;
Ok(())
})?;
// add account to wallet 2
wallet::controller::owner_single_use(wallet2.clone(), |api| {
api.create_account_path("account_1")?;
api.set_active_account("account_1")?;
Ok(())
})?;
// Do some mining
let bh = 20u64;
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), bh as usize);
// Sanity check contents
wallet::controller::owner_single_use(wallet1.clone(), |api| {
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(true, 1)?;
assert!(wallet1_refreshed);
assert_eq!(wallet1_info.last_confirmed_height, bh);
assert_eq!(wallet1_info.total, bh * reward);
assert_eq!(wallet1_info.amount_currently_spendable, (bh - cm) * reward);
// check tx log as well
let (_, txs) = api.retrieve_txs(true, None, None)?;
let (c, _) = libwallet::types::TxLogEntry::sum_confirmed(&txs);
assert_eq!(wallet1_info.total, c);
assert_eq!(txs.len(), bh as usize);
Ok(())
})?;
// Accidentally delete some outputs
let mut w1_outputs_commits = vec![];
wallet::controller::owner_single_use(wallet1.clone(), |api| {
w1_outputs_commits = api.retrieve_outputs(false, true, None)?.1;
Ok(())
})?;
let w1_outputs: Vec<libwallet::types::OutputData> =
w1_outputs_commits.into_iter().map(|o| o.0).collect();
{
let mut w = wallet1.lock();
w.open_with_credentials()?;
{
let mut batch = w.batch()?;
batch.delete(&w1_outputs[4].key_id, &None)?;
batch.delete(&w1_outputs[10].key_id, &None)?;
let mut accidental_spent = w1_outputs[13].clone();
accidental_spent.status = libwallet::types::OutputStatus::Spent;
batch.save(accidental_spent)?;
batch.commit()?;
}
w.close()?;
}
// check we have a problem now
wallet::controller::owner_single_use(wallet1.clone(), |api| {
let (_, wallet1_info) = api.retrieve_summary_info(true, 1)?;
let (_, txs) = api.retrieve_txs(true, None, None)?;
let (c, _) = libwallet::types::TxLogEntry::sum_confirmed(&txs);
assert!(wallet1_info.total != c);
Ok(())
})?;
// this should restore our missing outputs
wallet::controller::owner_single_use(wallet1.clone(), |api| {
api.check_repair()?;
Ok(())
})?;
// check our outputs match again
wallet::controller::owner_single_use(wallet1.clone(), |api| {
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(true, 1)?;
assert!(wallet1_refreshed);
assert_eq!(wallet1_info.total, bh * reward);
Ok(())
})?;
// perform a transaction, but don't let it finish
wallet::controller::owner_single_use(wallet1.clone(), |api| {
// send to send
let (mut slate, lock_fn) = api.initiate_tx(
None,
reward * 2, // amount
cm, // minimum confirmations
500, // max outputs
1, // num change outputs
true, // select all outputs
None, // optional message
)?;
// output tx file
let file_adapter = FileWalletCommAdapter::new();
let send_file = format!("{}/part_tx_1.tx", test_dir);
file_adapter.send_tx_async(&send_file, &mut slate)?;
api.tx_lock_outputs(&slate, lock_fn)?;
Ok(())
})?;
// check we're all locked
wallet::controller::owner_single_use(wallet1.clone(), |api| {
let (_, wallet1_info) = api.retrieve_summary_info(true, 1)?;
assert!(wallet1_info.amount_currently_spendable == 0);
Ok(())
})?;
// unlock/restore
wallet::controller::owner_single_use(wallet1.clone(), |api| {
api.check_repair()?;
Ok(())
})?;
// check spendable amount again
wallet::controller::owner_single_use(wallet1.clone(), |api| {
let (_, wallet1_info) = api.retrieve_summary_info(true, 1)?;
assert_eq!(wallet1_info.amount_currently_spendable, (bh - cm) * reward);
Ok(())
})?;
// let logging finish
thread::sleep(Duration::from_millis(200));
Ok(())
}
fn two_wallets_one_seed_impl(test_dir: &str) -> Result<(), libwallet::Error> {
setup(test_dir);
let seed_phrase =
"affair pistol cancel crush garment candy ancient flag work \
market crush dry stand focus mutual weapon offer ceiling rival turn team spring \
where swift";
// Create a new proxy to simulate server and wallet responses
let mut wallet_proxy: WalletProxy<LocalWalletClient, ExtKeychain> = WalletProxy::new(test_dir);
let chain = wallet_proxy.chain.clone();
// Create a new wallet test client, and set its queues to communicate with the
// proxy
let m_client = LocalWalletClient::new("miner", wallet_proxy.tx.clone());
let miner =
test_framework::create_wallet(&format!("{}/miner", test_dir), m_client.clone(), None);
wallet_proxy.add_wallet("miner", m_client.get_send_instance(), miner.clone());
// non-mining recipient wallets
let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone());
let wallet1 = test_framework::create_wallet(
&format!("{}/wallet1", test_dir),
client1.clone(),
Some(seed_phrase),
);
wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone());
let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone());
let wallet2 = test_framework::create_wallet(
&format!("{}/wallet2", test_dir),
client2.clone(),
Some(seed_phrase),
);
wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone());
// we'll restore into here
let client3 = LocalWalletClient::new("wallet3", wallet_proxy.tx.clone());
let wallet3 = test_framework::create_wallet(
&format!("{}/wallet3", test_dir),
client3.clone(),
Some(seed_phrase),
);
wallet_proxy.add_wallet("wallet3", client3.get_send_instance(), wallet3.clone());
// also restore into here
let client4 = LocalWalletClient::new("wallet4", wallet_proxy.tx.clone());
let wallet4 = test_framework::create_wallet(
&format!("{}/wallet4", test_dir),
client4.clone(),
Some(seed_phrase),
);
wallet_proxy.add_wallet("wallet4", client4.get_send_instance(), wallet4.clone());
// Simulate a recover from seed without restore into here
let client5 = LocalWalletClient::new("wallet5", wallet_proxy.tx.clone());
let wallet5 = test_framework::create_wallet(
&format!("{}/wallet5", test_dir),
client5.clone(),
Some(seed_phrase),
);
wallet_proxy.add_wallet("wallet5", client5.get_send_instance(), wallet5.clone());
//simulate a recover from seed without restore into here
let client6 = LocalWalletClient::new("wallet6", wallet_proxy.tx.clone());
let wallet6 = test_framework::create_wallet(
&format!("{}/wallet6", test_dir),
client6.clone(),
Some(seed_phrase),
);
wallet_proxy.add_wallet("wallet6", client6.get_send_instance(), wallet6.clone());
let client7 = LocalWalletClient::new("wallet7", wallet_proxy.tx.clone());
let wallet7 = test_framework::create_wallet(
&format!("{}/wallet7", test_dir),
client7.clone(),
Some(seed_phrase),
);
wallet_proxy.add_wallet("wallet7", client7.get_send_instance(), wallet7.clone());
let client8 = LocalWalletClient::new("wallet8", wallet_proxy.tx.clone());
let wallet8 = test_framework::create_wallet(
&format!("{}/wallet8", test_dir),
client8.clone(),
Some(seed_phrase),
);
wallet_proxy.add_wallet("wallet8", client8.get_send_instance(), wallet8.clone());
let client9 = LocalWalletClient::new("wallet9", wallet_proxy.tx.clone());
let wallet9 = test_framework::create_wallet(
&format!("{}/wallet9", test_dir),
client9.clone(),
Some(seed_phrase),
);
wallet_proxy.add_wallet("wallet9", client9.get_send_instance(), wallet9.clone());
let client10 = LocalWalletClient::new("wallet10", wallet_proxy.tx.clone());
let wallet10 = test_framework::create_wallet(
&format!("{}/wallet10", test_dir),
client10.clone(),
Some(seed_phrase),
);
wallet_proxy.add_wallet("wallet10", client10.get_send_instance(), wallet10.clone());
// Set the wallet proxy listener running
thread::spawn(move || {
if let Err(e) = wallet_proxy.run() {
error!("Wallet Proxy error: {}", e);
}
});
// few values to keep things shorter
let _reward = core::consensus::REWARD;
let cm = global::coinbase_maturity() as usize; // assume all testing precedes soft fork height
// Do some mining
let mut bh = 20u64;
let base_amount = consensus::GRIN_BASE;
let _ = test_framework::award_blocks_to_wallet(&chain, miner.clone(), bh as usize);
// send some funds to wallets 1
wallet::controller::owner_single_use(miner.clone(), |api| {
test_framework::send_to_dest(m_client.clone(), api, "wallet1", base_amount * 1)?;
test_framework::send_to_dest(m_client.clone(), api, "wallet1", base_amount * 2)?;
test_framework::send_to_dest(m_client.clone(), api, "wallet1", base_amount * 3)?;
bh += 3;
Ok(())
})?;
// 0) Check repair when all is okay should leave wallet contents alone
wallet::controller::owner_single_use(wallet1.clone(), |api| {
api.check_repair()?;
let info = test_framework::wallet_info(api)?;
assert_eq!(info.amount_currently_spendable, base_amount * 6);
assert_eq!(info.total, base_amount * 6);
Ok(())
})?;
// send some funds to wallet 2
wallet::controller::owner_single_use(miner.clone(), |api| {
test_framework::send_to_dest(m_client.clone(), api, "wallet2", base_amount * 4)?;
test_framework::send_to_dest(m_client.clone(), api, "wallet2", base_amount * 5)?;
test_framework::send_to_dest(m_client.clone(), api, "wallet2", base_amount * 6)?;
bh += 3;
Ok(())
})?;
let _ = test_framework::award_blocks_to_wallet(&chain, miner.clone(), cm);
bh += cm as u64;
// confirm balances
wallet::controller::owner_single_use(wallet1.clone(), |api| {
let info = test_framework::wallet_info(api)?;
assert_eq!(info.amount_currently_spendable, base_amount * 6);
assert_eq!(info.total, base_amount * 6);
Ok(())
})?;
wallet::controller::owner_single_use(wallet2.clone(), |api| {
let info = test_framework::wallet_info(api)?;
assert_eq!(info.amount_currently_spendable, base_amount * 15);
assert_eq!(info.total, base_amount * 15);
Ok(())
})?;
// Now there should be outputs on the chain using the same
// seed + BIP32 path.
// 1) a full restore should recover all of them:
wallet::controller::owner_single_use(wallet3.clone(), |api| {
api.restore()?;
Ok(())
})?;
wallet::controller::owner_single_use(wallet3.clone(), |api| {
let info = test_framework::wallet_info(api)?;
let outputs = api.retrieve_outputs(true, false, None)?.1;
assert_eq!(outputs.len(), 6);
assert_eq!(info.amount_currently_spendable, base_amount * 21);
assert_eq!(info.total, base_amount * 21);
Ok(())
})?;
// 2) check_repair should recover them into a single wallet
wallet::controller::owner_single_use(wallet1.clone(), |api| {
api.check_repair()?;
Ok(())
})?;
wallet::controller::owner_single_use(wallet1.clone(), |api| {
let info = test_framework::wallet_info(api)?;
let outputs = api.retrieve_outputs(true, false, None)?.1;
assert_eq!(outputs.len(), 6);
assert_eq!(info.amount_currently_spendable, base_amount * 21);
Ok(())
})?;
// 3) If I recover from seed and start using the wallet without restoring,
// check_repair should restore the older outputs
wallet::controller::owner_single_use(miner.clone(), |api| {
test_framework::send_to_dest(m_client.clone(), api, "wallet4", base_amount * 7)?;
test_framework::send_to_dest(m_client.clone(), api, "wallet4", base_amount * 8)?;
test_framework::send_to_dest(m_client.clone(), api, "wallet4", base_amount * 9)?;
bh += 3;
Ok(())
})?;
let _ = test_framework::award_blocks_to_wallet(&chain, miner.clone(), cm);
bh += cm as u64;
wallet::controller::owner_single_use(wallet4.clone(), |api| {
let info = test_framework::wallet_info(api)?;
let outputs = api.retrieve_outputs(true, false, None)?.1;
assert_eq!(outputs.len(), 3);
assert_eq!(info.amount_currently_spendable, base_amount * 24);
Ok(())
})?;
wallet::controller::owner_single_use(wallet5.clone(), |api| {
api.restore()?;
Ok(())
})?;
wallet::controller::owner_single_use(wallet5.clone(), |api| {
let info = test_framework::wallet_info(api)?;
let outputs = api.retrieve_outputs(true, false, None)?.1;
assert_eq!(outputs.len(), 9);
assert_eq!(info.amount_currently_spendable, base_amount * (45));
Ok(())
})?;
// 4) If I recover from seed and start using the wallet without restoring,
// check_repair should restore the older outputs
wallet::controller::owner_single_use(miner.clone(), |api| {
test_framework::send_to_dest(m_client.clone(), api, "wallet6", base_amount * 10)?;
test_framework::send_to_dest(m_client.clone(), api, "wallet6", base_amount * 11)?;
test_framework::send_to_dest(m_client.clone(), api, "wallet6", base_amount * 12)?;
bh += 3;
Ok(())
})?;
let _ = test_framework::award_blocks_to_wallet(&chain, miner.clone(), cm as usize);
bh += cm as u64;
wallet::controller::owner_single_use(wallet6.clone(), |api| {
let info = test_framework::wallet_info(api)?;
let outputs = api.retrieve_outputs(true, false, None)?.1;
assert_eq!(outputs.len(), 3);
assert_eq!(info.amount_currently_spendable, base_amount * 33);
Ok(())
})?;
wallet::controller::owner_single_use(wallet6.clone(), |api| {
api.check_repair()?;
Ok(())
})?;
wallet::controller::owner_single_use(wallet6.clone(), |api| {
let info = test_framework::wallet_info(api)?;
let outputs = api.retrieve_outputs(true, false, None)?.1;
assert_eq!(outputs.len(), 12);
assert_eq!(info.amount_currently_spendable, base_amount * (78));
Ok(())
})?;
// 5) Start using same seed with a different account, amounts should
// be distinct and restore should return funds from other account
wallet::controller::owner_single_use(miner.clone(), |api| {
test_framework::send_to_dest(m_client.clone(), api, "wallet7", base_amount * 13)?;
test_framework::send_to_dest(m_client.clone(), api, "wallet7", base_amount * 14)?;
test_framework::send_to_dest(m_client.clone(), api, "wallet7", base_amount * 15)?;
bh += 3;
Ok(())
})?;
// mix it up a bit
wallet::controller::owner_single_use(wallet7.clone(), |api| {
api.create_account_path("account_1")?;
api.set_active_account("account_1")?;
Ok(())
})?;
wallet::controller::owner_single_use(miner.clone(), |api| {
test_framework::send_to_dest(m_client.clone(), api, "wallet7", base_amount * 1)?;
test_framework::send_to_dest(m_client.clone(), api, "wallet7", base_amount * 2)?;
test_framework::send_to_dest(m_client.clone(), api, "wallet7", base_amount * 3)?;
bh += 3;
Ok(())
})?;
// check balances
let _ = test_framework::award_blocks_to_wallet(&chain, miner.clone(), cm);
bh += cm as u64;
wallet::controller::owner_single_use(wallet7.clone(), |api| {
let info = test_framework::wallet_info(api)?;
let outputs = api.retrieve_outputs(true, false, None)?.1;
assert_eq!(outputs.len(), 3);
assert_eq!(info.amount_currently_spendable, base_amount * 6);
api.set_active_account("default")?;
let info = test_framework::wallet_info(api)?;
let outputs = api.retrieve_outputs(true, false, None)?.1;
assert_eq!(outputs.len(), 3);
assert_eq!(info.amount_currently_spendable, base_amount * 42);
Ok(())
})?;
wallet::controller::owner_single_use(wallet8.clone(), |api| {
api.restore()?;
let info = test_framework::wallet_info(api)?;
let outputs = api.retrieve_outputs(true, false, None)?.1;
assert_eq!(outputs.len(), 15);
assert_eq!(info.amount_currently_spendable, base_amount * 120);
api.set_active_account("account_1")?;
let info = test_framework::wallet_info(api)?;
let outputs = api.retrieve_outputs(true, false, None)?.1;
assert_eq!(outputs.len(), 3);
assert_eq!(info.amount_currently_spendable, base_amount * 6);
Ok(())
})?;
// 6) Start using same seed with a different account, now overwriting
// ids on account 2 as well, check_repair should get all outputs created
// to now into 2 accounts
wallet::controller::owner_single_use(wallet9.clone(), |api| {
api.create_account_path("account_1")?;
api.set_active_account("account_1")?;
Ok(())
})?;
wallet::controller::owner_single_use(miner.clone(), |api| {
test_framework::send_to_dest(m_client.clone(), api, "wallet9", base_amount * 4)?;
test_framework::send_to_dest(m_client.clone(), api, "wallet9", base_amount * 5)?;
test_framework::send_to_dest(m_client.clone(), api, "wallet9", base_amount * 6)?;
bh += 3;
Ok(())
})?;
wallet::controller::owner_single_use(wallet9.clone(), |api| {
let info = test_framework::wallet_info(api)?;
let outputs = api.retrieve_outputs(true, false, None)?.1;
assert_eq!(outputs.len(), 3);
assert_eq!(info.amount_currently_spendable, base_amount * 15);
api.check_repair()?;
let info = test_framework::wallet_info(api)?;
let outputs = api.retrieve_outputs(true, false, None)?.1;
assert_eq!(outputs.len(), 6);
assert_eq!(info.amount_currently_spendable, base_amount * 21);
api.set_active_account("default")?;
let info = test_framework::wallet_info(api)?;
let outputs = api.retrieve_outputs(true, false, None)?.1;
assert_eq!(outputs.len(), 15);
assert_eq!(info.amount_currently_spendable, base_amount * 120);
Ok(())
})?;
let _ = test_framework::award_blocks_to_wallet(&chain, miner.clone(), cm);
// 7) Ensure check_repair creates missing accounts
wallet::controller::owner_single_use(wallet10.clone(), |api| {
api.check_repair()?;
api.set_active_account("account_1")?;
let info = test_framework::wallet_info(api)?;
let outputs = api.retrieve_outputs(true, false, None)?.1;
assert_eq!(outputs.len(), 6);
assert_eq!(info.amount_currently_spendable, base_amount * 21);
api.set_active_account("default")?;
let info = test_framework::wallet_info(api)?;
let outputs = api.retrieve_outputs(true, false, None)?.1;
assert_eq!(outputs.len(), 15);
assert_eq!(info.amount_currently_spendable, base_amount * 120);
Ok(())
})?;
// let logging finish
thread::sleep(Duration::from_millis(200));
Ok(())
}
#[test]
fn check_repair() {
let test_dir = "test_output/check_repair";
if let Err(e) = check_repair_impl(test_dir) {
panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap());
}
}
#[test]
fn two_wallets_one_seed() {
let test_dir = "test_output/two_wallets_one_seed";
if let Err(e) = two_wallets_one_seed_impl(test_dir) {
panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap());
}
}
+220
View File
@@ -0,0 +1,220 @@
// Copyright 2018 The Grin Developers
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Test a wallet file send/recieve
#[macro_use]
extern crate log;
use self::core::global;
use self::core::global::ChainTypes;
use self::keychain::ExtKeychain;
use self::wallet::test_framework::{self, LocalWalletClient, WalletProxy};
use self::wallet::{libwallet, FileWalletCommAdapter};
use grin_core as core;
use grin_keychain as keychain;
use grin_util as util;
use grin_wallet as wallet;
use std::fs;
use std::thread;
use std::time::Duration;
use serde_json;
fn clean_output_dir(test_dir: &str) {
let _ = fs::remove_dir_all(test_dir);
}
fn setup(test_dir: &str) {
util::init_test_logger();
clean_output_dir(test_dir);
global::set_mining_mode(ChainTypes::AutomatedTesting);
}
/// self send impl
fn file_exchange_test_impl(test_dir: &str) -> Result<(), libwallet::Error> {
setup(test_dir);
// Create a new proxy to simulate server and wallet responses
let mut wallet_proxy: WalletProxy<LocalWalletClient, ExtKeychain> = WalletProxy::new(test_dir);
let chain = wallet_proxy.chain.clone();
let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone());
let wallet1 =
test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone(), None);
wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone());
let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone());
let wallet2 =
test_framework::create_wallet(&format!("{}/wallet2", test_dir), client2.clone(), None);
wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone());
// 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(wallet1.clone(), |api| {
api.create_account_path("mining")?;
api.create_account_path("listener")?;
Ok(())
})?;
// add some accounts
wallet::controller::owner_single_use(wallet2.clone(), |api| {
api.create_account_path("account1")?;
api.create_account_path("account2")?;
Ok(())
})?;
// Get some mining done
{
let mut w = wallet1.lock();
w.set_parent_key_id_by_name("mining")?;
}
let mut bh = 10u64;
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), bh as usize);
let send_file = format!("{}/part_tx_1.tx", test_dir);
let receive_file = format!("{}/part_tx_2.tx", test_dir);
// test optional message
let message = "sender test message, sender test message";
// Should have 5 in account1 (5 spendable), 5 in account (2 spendable)
wallet::controller::owner_single_use(wallet1.clone(), |api| {
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(true, 1)?;
assert!(wallet1_refreshed);
assert_eq!(wallet1_info.last_confirmed_height, bh);
assert_eq!(wallet1_info.total, bh * reward);
// send to send
let (mut slate, lock_fn) = api.initiate_tx(
Some("mining"),
reward * 2, // amount
2, // minimum confirmations
500, // max outputs
1, // num change outputs
true, // select all outputs
Some(message.to_owned()), // optional message
)?;
// output tx file
let file_adapter = FileWalletCommAdapter::new();
file_adapter.send_tx_async(&send_file, &mut slate)?;
api.tx_lock_outputs(&slate, lock_fn)?;
Ok(())
})?;
// Get some mining done
{
let mut w = wallet2.lock();
w.set_parent_key_id_by_name("account1")?;
}
let adapter = FileWalletCommAdapter::new();
let mut slate = adapter.receive_tx_async(&send_file)?;
let mut naughty_slate = slate.clone();
naughty_slate.participant_data[0].message = Some("I changed the message".to_owned());
// verify messages on slate match
wallet::controller::owner_single_use(wallet1.clone(), |api| {
api.verify_slate_messages(&slate)?;
assert!(api.verify_slate_messages(&naughty_slate).is_err());
Ok(())
})?;
let sender2_message = "And this is sender 2's message".to_owned();
// wallet 2 receives file, completes, sends file back
wallet::controller::foreign_single_use(wallet2.clone(), |api| {
api.receive_tx(&mut slate, None, Some(sender2_message.clone()))?;
adapter.send_tx_async(&receive_file, &mut slate)?;
Ok(())
})?;
// wallet 1 finalises and posts
wallet::controller::owner_single_use(wallet1.clone(), |api| {
let adapter = FileWalletCommAdapter::new();
let mut slate = adapter.receive_tx_async(&receive_file)?;
api.verify_slate_messages(&slate)?;
api.finalize_tx(&mut slate)?;
api.post_tx(&slate.tx, false)?;
bh += 1;
Ok(())
})?;
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), 3);
bh += 3;
// Check total in mining account
wallet::controller::owner_single_use(wallet1.clone(), |api| {
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(true, 1)?;
assert!(wallet1_refreshed);
assert_eq!(wallet1_info.last_confirmed_height, bh);
assert_eq!(wallet1_info.total, bh * reward - reward * 2);
Ok(())
})?;
// Check total in 'wallet 2' account
wallet::controller::owner_single_use(wallet2.clone(), |api| {
let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(true, 1)?;
assert!(wallet2_refreshed);
assert_eq!(wallet2_info.last_confirmed_height, bh);
assert_eq!(wallet2_info.total, 2 * reward);
Ok(())
})?;
// Check messages, all participants should have both
wallet::controller::owner_single_use(wallet1.clone(), |api| {
let (_, tx) = api.retrieve_txs(true, None, Some(slate.id))?;
assert_eq!(
tx[0].clone().messages.unwrap().messages[0].message,
Some(message.to_owned())
);
assert_eq!(
tx[0].clone().messages.unwrap().messages[1].message,
Some(sender2_message.to_owned())
);
let msg_json = serde_json::to_string_pretty(&tx[0].clone().messages.unwrap()).unwrap();
println!("{}", msg_json);
Ok(())
})?;
wallet::controller::owner_single_use(wallet2.clone(), |api| {
let (_, tx) = api.retrieve_txs(true, None, Some(slate.id))?;
assert_eq!(
tx[0].clone().messages.unwrap().messages[0].message,
Some(message.to_owned())
);
assert_eq!(
tx[0].clone().messages.unwrap().messages[1].message,
Some(sender2_message)
);
Ok(())
})?;
// let logging finish
thread::sleep(Duration::from_millis(200));
Ok(())
}
#[test]
fn wallet_file_exchange() {
let test_dir = "test_output/file_exchange";
if let Err(e) = file_exchange_test_impl(test_dir) {
panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap());
}
}
+521
View File
@@ -0,0 +1,521 @@
// Copyright 2018 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.
//! core::libtx specific tests
use self::core::core::transaction;
use self::core::libtx::{aggsig, proof};
use self::keychain::{BlindSum, BlindingFactor, ExtKeychain, Keychain};
use self::util::secp;
use self::util::secp::key::{PublicKey, SecretKey};
use self::wallet::libwallet::types::Context;
use self::wallet::{EncryptedWalletSeed, WalletSeed};
use grin_core as core;
use grin_keychain as keychain;
use grin_util as util;
use grin_wallet as wallet;
use rand::thread_rng;
fn kernel_sig_msg() -> secp::Message {
transaction::kernel_sig_msg(0, 0, transaction::KernelFeatures::Plain).unwrap()
}
#[test]
fn aggsig_sender_receiver_interaction() {
let sender_keychain = ExtKeychain::from_random_seed(true).unwrap();
let receiver_keychain = ExtKeychain::from_random_seed(true).unwrap();
// Calculate the kernel excess here for convenience.
// Normally this would happen during transaction building.
let kernel_excess = {
let id1 = ExtKeychain::derive_key_id(1, 1, 0, 0, 0);
let skey1 = sender_keychain.derive_key(0, &id1).unwrap();
let skey2 = receiver_keychain.derive_key(0, &id1).unwrap();
let keychain = ExtKeychain::from_random_seed(true).unwrap();
let blinding_factor = keychain
.blind_sum(
&BlindSum::new()
.sub_blinding_factor(BlindingFactor::from_secret_key(skey1))
.add_blinding_factor(BlindingFactor::from_secret_key(skey2)),
)
.unwrap();
keychain
.secp()
.commit(0, blinding_factor.secret_key(&keychain.secp()).unwrap())
.unwrap()
};
let s_cx;
let mut rx_cx;
// sender starts the tx interaction
let (sender_pub_excess, _sender_pub_nonce) = {
let keychain = sender_keychain.clone();
let id1 = ExtKeychain::derive_key_id(1, 1, 0, 0, 0);
let skey = keychain.derive_key(0, &id1).unwrap();
// dealing with an input here so we need to negate the blinding_factor
// rather than use it as is
let bs = BlindSum::new();
let blinding_factor = keychain
.blind_sum(&bs.sub_blinding_factor(BlindingFactor::from_secret_key(skey)))
.unwrap();
let blind = blinding_factor.secret_key(&keychain.secp()).unwrap();
s_cx = Context::new(&keychain.secp(), blind);
s_cx.get_public_keys(&keychain.secp())
};
let pub_nonce_sum;
let pub_key_sum;
// receiver receives partial tx
let (receiver_pub_excess, _receiver_pub_nonce, rx_sig_part) = {
let keychain = receiver_keychain.clone();
let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0);
// let blind = blind_sum.secret_key(&keychain.secp())?;
let blind = keychain.derive_key(0, &key_id).unwrap();
rx_cx = Context::new(&keychain.secp(), blind);
let (pub_excess, pub_nonce) = rx_cx.get_public_keys(&keychain.secp());
rx_cx.add_output(&key_id, &None);
pub_nonce_sum = PublicKey::from_combination(
keychain.secp(),
vec![
&s_cx.get_public_keys(keychain.secp()).1,
&rx_cx.get_public_keys(keychain.secp()).1,
],
)
.unwrap();
pub_key_sum = PublicKey::from_combination(
keychain.secp(),
vec![
&s_cx.get_public_keys(keychain.secp()).0,
&rx_cx.get_public_keys(keychain.secp()).0,
],
)
.unwrap();
let msg = kernel_sig_msg();
let sig_part = aggsig::calculate_partial_sig(
&keychain.secp(),
&rx_cx.sec_key,
&rx_cx.sec_nonce,
&pub_nonce_sum,
Some(&pub_key_sum),
&msg,
)
.unwrap();
(pub_excess, pub_nonce, sig_part)
};
// check the sender can verify the partial signature
// received in the response back from the receiver
{
let keychain = sender_keychain.clone();
let msg = kernel_sig_msg();
let sig_verifies = aggsig::verify_partial_sig(
&keychain.secp(),
&rx_sig_part,
&pub_nonce_sum,
&receiver_pub_excess,
Some(&pub_key_sum),
&msg,
);
assert!(!sig_verifies.is_err());
}
// now sender signs with their key
let sender_sig_part = {
let keychain = sender_keychain.clone();
let msg = kernel_sig_msg();
let sig_part = aggsig::calculate_partial_sig(
&keychain.secp(),
&s_cx.sec_key,
&s_cx.sec_nonce,
&pub_nonce_sum,
Some(&pub_key_sum),
&msg,
)
.unwrap();
sig_part
};
// check the receiver can verify the partial signature
// received by the sender
{
let keychain = receiver_keychain.clone();
let msg = kernel_sig_msg();
let sig_verifies = aggsig::verify_partial_sig(
&keychain.secp(),
&sender_sig_part,
&pub_nonce_sum,
&sender_pub_excess,
Some(&pub_key_sum),
&msg,
);
assert!(!sig_verifies.is_err());
}
// Receiver now builds final signature from sender and receiver parts
let (final_sig, final_pubkey) = {
let keychain = receiver_keychain.clone();
let msg = kernel_sig_msg();
let our_sig_part = aggsig::calculate_partial_sig(
&keychain.secp(),
&rx_cx.sec_key,
&rx_cx.sec_nonce,
&pub_nonce_sum,
Some(&pub_key_sum),
&msg,
)
.unwrap();
// Receiver now generates final signature from the two parts
let final_sig = aggsig::add_signatures(
&keychain.secp(),
vec![&sender_sig_part, &our_sig_part],
&pub_nonce_sum,
)
.unwrap();
// Receiver calculates the final public key (to verify sig later)
let final_pubkey = PublicKey::from_combination(
keychain.secp(),
vec![
&s_cx.get_public_keys(keychain.secp()).0,
&rx_cx.get_public_keys(keychain.secp()).0,
],
)
.unwrap();
(final_sig, final_pubkey)
};
// Receiver checks the final signature verifies
{
let keychain = receiver_keychain.clone();
let msg = kernel_sig_msg();
// Receiver check the final signature verifies
let sig_verifies = aggsig::verify_completed_sig(
&keychain.secp(),
&final_sig,
&final_pubkey,
Some(&final_pubkey),
&msg,
);
assert!(!sig_verifies.is_err());
}
// Check we can verify the sig using the kernel excess
{
let keychain = ExtKeychain::from_random_seed(true).unwrap();
let msg = kernel_sig_msg();
let sig_verifies =
aggsig::verify_single_from_commit(&keychain.secp(), &final_sig, &msg, &kernel_excess);
assert!(!sig_verifies.is_err());
}
}
#[test]
fn aggsig_sender_receiver_interaction_offset() {
let sender_keychain = ExtKeychain::from_random_seed(true).unwrap();
let receiver_keychain = ExtKeychain::from_random_seed(true).unwrap();
// This is the kernel offset that we use to split the key
// Summing these at the block level prevents the
// kernels from being used to reconstruct (or identify) individual transactions
let kernel_offset = SecretKey::new(&sender_keychain.secp(), &mut thread_rng());
// Calculate the kernel excess here for convenience.
// Normally this would happen during transaction building.
let kernel_excess = {
let id1 = ExtKeychain::derive_key_id(1, 1, 0, 0, 0);
let skey1 = sender_keychain.derive_key(0, &id1).unwrap();
let skey2 = receiver_keychain.derive_key(0, &id1).unwrap();
let keychain = ExtKeychain::from_random_seed(true).unwrap();
let blinding_factor = keychain
.blind_sum(
&BlindSum::new()
.sub_blinding_factor(BlindingFactor::from_secret_key(skey1))
.add_blinding_factor(BlindingFactor::from_secret_key(skey2))
// subtract the kernel offset here like as would when
// verifying a kernel signature
.sub_blinding_factor(BlindingFactor::from_secret_key(kernel_offset)),
)
.unwrap();
keychain
.secp()
.commit(0, blinding_factor.secret_key(&keychain.secp()).unwrap())
.unwrap()
};
let s_cx;
let mut rx_cx;
// sender starts the tx interaction
let (sender_pub_excess, _sender_pub_nonce) = {
let keychain = sender_keychain.clone();
let id1 = ExtKeychain::derive_key_id(1, 1, 0, 0, 0);
let skey = keychain.derive_key(0, &id1).unwrap();
// dealing with an input here so we need to negate the blinding_factor
// rather than use it as is
let blinding_factor = keychain
.blind_sum(
&BlindSum::new()
.sub_blinding_factor(BlindingFactor::from_secret_key(skey))
// subtract the kernel offset to create an aggsig context
// with our "split" key
.sub_blinding_factor(BlindingFactor::from_secret_key(kernel_offset)),
)
.unwrap();
let blind = blinding_factor.secret_key(&keychain.secp()).unwrap();
s_cx = Context::new(&keychain.secp(), blind);
s_cx.get_public_keys(&keychain.secp())
};
// receiver receives partial tx
let pub_nonce_sum;
let pub_key_sum;
let (receiver_pub_excess, _receiver_pub_nonce, sig_part) = {
let keychain = receiver_keychain.clone();
let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0);
let blind = keychain.derive_key(0, &key_id).unwrap();
rx_cx = Context::new(&keychain.secp(), blind);
let (pub_excess, pub_nonce) = rx_cx.get_public_keys(&keychain.secp());
rx_cx.add_output(&key_id, &None);
pub_nonce_sum = PublicKey::from_combination(
keychain.secp(),
vec![
&s_cx.get_public_keys(keychain.secp()).1,
&rx_cx.get_public_keys(keychain.secp()).1,
],
)
.unwrap();
pub_key_sum = PublicKey::from_combination(
keychain.secp(),
vec![
&s_cx.get_public_keys(keychain.secp()).0,
&rx_cx.get_public_keys(keychain.secp()).0,
],
)
.unwrap();
let msg = kernel_sig_msg();
let sig_part = aggsig::calculate_partial_sig(
&keychain.secp(),
&rx_cx.sec_key,
&rx_cx.sec_nonce,
&pub_nonce_sum,
Some(&pub_key_sum),
&msg,
)
.unwrap();
(pub_excess, pub_nonce, sig_part)
};
// check the sender can verify the partial signature
// received in the response back from the receiver
{
let keychain = sender_keychain.clone();
let msg = kernel_sig_msg();
let sig_verifies = aggsig::verify_partial_sig(
&keychain.secp(),
&sig_part,
&pub_nonce_sum,
&receiver_pub_excess,
Some(&pub_key_sum),
&msg,
);
assert!(!sig_verifies.is_err());
}
// now sender signs with their key
let sender_sig_part = {
let keychain = sender_keychain.clone();
let msg = kernel_sig_msg();
let sig_part = aggsig::calculate_partial_sig(
&keychain.secp(),
&s_cx.sec_key,
&s_cx.sec_nonce,
&pub_nonce_sum,
Some(&pub_key_sum),
&msg,
)
.unwrap();
sig_part
};
// check the receiver can verify the partial signature
// received by the sender
{
let keychain = receiver_keychain.clone();
let msg = kernel_sig_msg();
let sig_verifies = aggsig::verify_partial_sig(
&keychain.secp(),
&sender_sig_part,
&pub_nonce_sum,
&sender_pub_excess,
Some(&pub_key_sum),
&msg,
);
assert!(!sig_verifies.is_err());
}
// Receiver now builds final signature from sender and receiver parts
let (final_sig, final_pubkey) = {
let keychain = receiver_keychain.clone();
let msg = kernel_sig_msg();
let our_sig_part = aggsig::calculate_partial_sig(
&keychain.secp(),
&rx_cx.sec_key,
&rx_cx.sec_nonce,
&pub_nonce_sum,
Some(&pub_key_sum),
&msg,
)
.unwrap();
// Receiver now generates final signature from the two parts
let final_sig = aggsig::add_signatures(
&keychain.secp(),
vec![&sender_sig_part, &our_sig_part],
&pub_nonce_sum,
)
.unwrap();
// Receiver calculates the final public key (to verify sig later)
let final_pubkey = PublicKey::from_combination(
keychain.secp(),
vec![
&s_cx.get_public_keys(keychain.secp()).0,
&rx_cx.get_public_keys(keychain.secp()).0,
],
)
.unwrap();
(final_sig, final_pubkey)
};
// Receiver checks the final signature verifies
{
let keychain = receiver_keychain.clone();
let msg = kernel_sig_msg();
// Receiver check the final signature verifies
let sig_verifies = aggsig::verify_completed_sig(
&keychain.secp(),
&final_sig,
&final_pubkey,
Some(&final_pubkey),
&msg,
);
assert!(!sig_verifies.is_err());
}
// Check we can verify the sig using the kernel excess
{
let keychain = ExtKeychain::from_random_seed(true).unwrap();
let msg = kernel_sig_msg();
let sig_verifies =
aggsig::verify_single_from_commit(&keychain.secp(), &final_sig, &msg, &kernel_excess);
assert!(!sig_verifies.is_err());
}
}
#[test]
fn test_rewind_range_proof() {
let keychain = ExtKeychain::from_random_seed(true).unwrap();
let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0);
let key_id2 = ExtKeychain::derive_key_id(1, 2, 0, 0, 0);
let commit = keychain.commit(5, &key_id).unwrap();
let extra_data = [99u8; 64];
let proof = proof::create(
&keychain,
5,
&key_id,
commit,
Some(extra_data.to_vec().clone()),
)
.unwrap();
let proof_info =
proof::rewind(&keychain, commit, Some(extra_data.to_vec().clone()), proof).unwrap();
assert_eq!(proof_info.success, true);
assert_eq!(proof_info.value, 5);
assert_eq!(proof_info.message.as_bytes(), key_id.serialize_path());
// cannot rewind with a different commit
let commit2 = keychain.commit(5, &key_id2).unwrap();
let proof_info =
proof::rewind(&keychain, commit2, Some(extra_data.to_vec().clone()), proof).unwrap();
assert_eq!(proof_info.success, false);
assert_eq!(proof_info.value, 0);
assert_eq!(proof_info.message, secp::pedersen::ProofMessage::empty());
// cannot rewind with a commitment to a different value
let commit3 = keychain.commit(4, &key_id).unwrap();
let proof_info =
proof::rewind(&keychain, commit3, Some(extra_data.to_vec().clone()), proof).unwrap();
assert_eq!(proof_info.success, false);
assert_eq!(proof_info.value, 0);
// cannot rewind with wrong extra committed data
let commit3 = keychain.commit(4, &key_id).unwrap();
let wrong_extra_data = [98u8; 64];
let _should_err = proof::rewind(
&keychain,
commit3,
Some(wrong_extra_data.to_vec().clone()),
proof,
)
.unwrap();
assert_eq!(proof_info.success, false);
assert_eq!(proof_info.value, 0);
}
#[test]
fn wallet_seed_encrypt() {
let password = "passwoid";
let wallet_seed = WalletSeed::init_new(32);
let mut enc_wallet_seed = EncryptedWalletSeed::from_seed(&wallet_seed, password).unwrap();
println!("EWS: {:?}", enc_wallet_seed);
let decrypted_wallet_seed = enc_wallet_seed.decrypt(password).unwrap();
assert_eq!(wallet_seed, decrypted_wallet_seed);
// Wrong password
let decrypted_wallet_seed = enc_wallet_seed.decrypt("");
assert!(decrypted_wallet_seed.is_err());
// Wrong nonce
enc_wallet_seed.nonce = "wrongnonce".to_owned();
let decrypted_wallet_seed = enc_wallet_seed.decrypt(password);
assert!(decrypted_wallet_seed.is_err());
}
+257
View File
@@ -0,0 +1,257 @@
// Copyright 2018 The Grin Developers
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Test a wallet repost command
#[macro_use]
extern crate log;
use self::core::global;
use self::core::global::ChainTypes;
use self::keychain::ExtKeychain;
use self::libwallet::slate::Slate;
use self::wallet::test_framework::{self, LocalWalletClient, WalletProxy};
use self::wallet::{libwallet, FileWalletCommAdapter};
use grin_core as core;
use grin_keychain as keychain;
use grin_util as util;
use grin_wallet as wallet;
use std::fs;
use std::thread;
use std::time::Duration;
fn clean_output_dir(test_dir: &str) {
let _ = fs::remove_dir_all(test_dir);
}
fn setup(test_dir: &str) {
util::init_test_logger();
clean_output_dir(test_dir);
global::set_mining_mode(ChainTypes::AutomatedTesting);
}
/// self send impl
fn file_repost_test_impl(test_dir: &str) -> Result<(), libwallet::Error> {
setup(test_dir);
// Create a new proxy to simulate server and wallet responses
let mut wallet_proxy: WalletProxy<LocalWalletClient, ExtKeychain> = WalletProxy::new(test_dir);
let chain = wallet_proxy.chain.clone();
let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone());
let wallet1 =
test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone(), None);
wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone());
let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone());
let wallet2 =
test_framework::create_wallet(&format!("{}/wallet2", test_dir), client2.clone(), None);
wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone());
// 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(wallet1.clone(), |api| {
api.create_account_path("mining")?;
api.create_account_path("listener")?;
Ok(())
})?;
// add some accounts
wallet::controller::owner_single_use(wallet2.clone(), |api| {
api.create_account_path("account1")?;
api.create_account_path("account2")?;
Ok(())
})?;
// Get some mining done
{
let mut w = wallet1.lock();
w.set_parent_key_id_by_name("mining")?;
}
let mut bh = 10u64;
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), bh as usize);
let send_file = format!("{}/part_tx_1.tx", test_dir);
let receive_file = format!("{}/part_tx_2.tx", test_dir);
let mut slate = Slate::blank(2);
// Should have 5 in account1 (5 spendable), 5 in account (2 spendable)
wallet::controller::owner_single_use(wallet1.clone(), |api| {
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(true, 1)?;
assert!(wallet1_refreshed);
assert_eq!(wallet1_info.last_confirmed_height, bh);
assert_eq!(wallet1_info.total, bh * reward);
// send to send
let (mut slate, lock_fn) = api.initiate_tx(
Some("mining"),
reward * 2, // amount
2, // minimum confirmations
500, // max outputs
1, // num change outputs
true, // select all outputs
None,
)?;
// output tx file
let file_adapter = FileWalletCommAdapter::new();
file_adapter.send_tx_async(&send_file, &mut slate)?;
api.tx_lock_outputs(&slate, lock_fn)?;
Ok(())
})?;
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), 3);
bh += 3;
// wallet 1 receives file to different account, completes
{
let mut w = wallet1.lock();
w.set_parent_key_id_by_name("listener")?;
}
wallet::controller::foreign_single_use(wallet1.clone(), |api| {
let adapter = FileWalletCommAdapter::new();
slate = adapter.receive_tx_async(&send_file)?;
api.receive_tx(&mut slate, None, None)?;
adapter.send_tx_async(&receive_file, &mut slate)?;
Ok(())
})?;
// wallet 1 receives file to different account, completes
{
let mut w = wallet1.lock();
w.set_parent_key_id_by_name("mining")?;
}
// wallet 1 finalize
wallet::controller::owner_single_use(wallet1.clone(), |api| {
let adapter = FileWalletCommAdapter::new();
slate = adapter.receive_tx_async(&receive_file)?;
api.finalize_tx(&mut slate)?;
Ok(())
})?;
// Now repost from cached
wallet::controller::owner_single_use(wallet1.clone(), |api| {
let (_, txs) = api.retrieve_txs(true, None, Some(slate.id))?;
let stored_tx = api.get_stored_tx(&txs[0])?;
api.post_tx(&stored_tx.unwrap(), false)?;
bh += 1;
Ok(())
})?;
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), 3);
bh += 3;
// update/test contents of both accounts
wallet::controller::owner_single_use(wallet1.clone(), |api| {
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(true, 1)?;
assert!(wallet1_refreshed);
assert_eq!(wallet1_info.last_confirmed_height, bh);
assert_eq!(wallet1_info.total, bh * reward - reward * 2);
Ok(())
})?;
{
let mut w = wallet1.lock();
w.set_parent_key_id_by_name("listener")?;
}
wallet::controller::owner_single_use(wallet1.clone(), |api| {
let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(true, 1)?;
assert!(wallet2_refreshed);
assert_eq!(wallet2_info.last_confirmed_height, bh);
assert_eq!(wallet2_info.total, 2 * reward);
Ok(())
})?;
// as above, but syncronously
{
let mut w = wallet1.lock();
w.set_parent_key_id_by_name("mining")?;
}
{
let mut w = wallet2.lock();
w.set_parent_key_id_by_name("account1")?;
}
let mut slate = Slate::blank(2);
let amount = 60_000_000_000;
wallet::controller::owner_single_use(wallet1.clone(), |sender_api| {
// note this will increment the block count as part of the transaction "Posting"
let (slate_i, lock_fn) = sender_api.initiate_tx(
None,
amount * 2, // amount
2, // minimum confirmations
500, // max outputs
1, // num change outputs
true, // select all outputs
None,
)?;
slate = client1.send_tx_slate_direct("wallet2", &slate_i)?;
sender_api.tx_lock_outputs(&slate, lock_fn)?;
sender_api.finalize_tx(&mut slate)?;
Ok(())
})?;
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), 3);
bh += 3;
// Now repost from cached
wallet::controller::owner_single_use(wallet1.clone(), |api| {
let (_, txs) = api.retrieve_txs(true, None, Some(slate.id))?;
let stored_tx = api.get_stored_tx(&txs[0])?;
api.post_tx(&stored_tx.unwrap(), false)?;
bh += 1;
Ok(())
})?;
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), 3);
bh += 3;
//
// update/test contents of both accounts
wallet::controller::owner_single_use(wallet1.clone(), |api| {
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(true, 1)?;
assert!(wallet1_refreshed);
assert_eq!(wallet1_info.last_confirmed_height, bh);
assert_eq!(wallet1_info.total, bh * reward - reward * 4);
Ok(())
})?;
wallet::controller::owner_single_use(wallet2.clone(), |api| {
let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(true, 1)?;
assert!(wallet2_refreshed);
assert_eq!(wallet2_info.last_confirmed_height, bh);
assert_eq!(wallet2_info.total, 2 * amount);
Ok(())
})?;
// let logging finish
thread::sleep(Duration::from_millis(200));
Ok(())
}
#[test]
fn wallet_file_repost() {
let test_dir = "test_output/file_repost";
if let Err(e) = file_repost_test_impl(test_dir) {
panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap());
}
}
+389
View File
@@ -0,0 +1,389 @@
// Copyright 2018 The Grin Developers
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! tests for wallet restore
#[macro_use]
extern crate log;
use self::core::global;
use self::core::global::ChainTypes;
use self::keychain::{ExtKeychain, Identifier, Keychain};
use self::libwallet::slate::Slate;
use self::wallet::libwallet;
use self::wallet::libwallet::types::AcctPathMapping;
use self::wallet::test_framework::{self, LocalWalletClient, WalletProxy};
use grin_core as core;
use grin_keychain as keychain;
use grin_util as util;
use grin_wallet as wallet;
use std::fs;
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
fn clean_output_dir(test_dir: &str) {
let _ = fs::remove_dir_all(test_dir);
}
fn setup(test_dir: &str) {
util::init_test_logger();
clean_output_dir(test_dir);
global::set_mining_mode(ChainTypes::AutomatedTesting);
}
fn restore_wallet(base_dir: &str, wallet_dir: &str) -> Result<(), libwallet::Error> {
let source_seed = format!("{}/{}/wallet.seed", base_dir, wallet_dir);
let dest_dir = format!("{}/{}_restore", base_dir, wallet_dir);
fs::create_dir_all(dest_dir.clone())?;
let dest_seed = format!("{}/wallet.seed", dest_dir);
println!("Source: {}, Dest: {}", source_seed, dest_seed);
fs::copy(source_seed, dest_seed)?;
let mut wallet_proxy: WalletProxy<LocalWalletClient, ExtKeychain> = WalletProxy::new(base_dir);
let client = LocalWalletClient::new(wallet_dir, wallet_proxy.tx.clone());
let wallet = test_framework::create_wallet(&dest_dir, client.clone(), None);
wallet_proxy.add_wallet(wallet_dir, client.get_send_instance(), wallet.clone());
// Set the wallet proxy listener running
let wp_running = wallet_proxy.running.clone();
thread::spawn(move || {
if let Err(e) = wallet_proxy.run() {
error!("Wallet Proxy error: {}", e);
}
});
// perform the restore and update wallet info
wallet::controller::owner_single_use(wallet.clone(), |api| {
let _ = api.restore()?;
let _ = api.retrieve_summary_info(true, 1)?;
Ok(())
})?;
wp_running.store(false, Ordering::Relaxed);
//thread::sleep(Duration::from_millis(1000));
Ok(())
}
fn compare_wallet_restore(
base_dir: &str,
wallet_dir: &str,
account_path: &Identifier,
) -> Result<(), libwallet::Error> {
let restore_name = format!("{}_restore", wallet_dir);
let source_dir = format!("{}/{}", base_dir, wallet_dir);
let dest_dir = format!("{}/{}", base_dir, restore_name);
let mut wallet_proxy: WalletProxy<LocalWalletClient, ExtKeychain> = WalletProxy::new(base_dir);
let client = LocalWalletClient::new(wallet_dir, wallet_proxy.tx.clone());
let wallet_source = test_framework::create_wallet(&source_dir, client.clone(), None);
wallet_proxy.add_wallet(
&wallet_dir,
client.get_send_instance(),
wallet_source.clone(),
);
let client = LocalWalletClient::new(&restore_name, wallet_proxy.tx.clone());
let wallet_dest = test_framework::create_wallet(&dest_dir, client.clone(), None);
wallet_proxy.add_wallet(
&restore_name,
client.get_send_instance(),
wallet_dest.clone(),
);
{
let mut w = wallet_source.lock();
w.set_parent_key_id(account_path.clone());
}
{
let mut w = wallet_dest.lock();
w.set_parent_key_id(account_path.clone());
}
// Set the wallet proxy listener running
let wp_running = wallet_proxy.running.clone();
thread::spawn(move || {
if let Err(e) = wallet_proxy.run() {
error!("Wallet Proxy error: {}", e);
}
});
let mut src_info: Option<libwallet::types::WalletInfo> = None;
let mut dest_info: Option<libwallet::types::WalletInfo> = None;
let mut src_txs: Option<Vec<libwallet::types::TxLogEntry>> = None;
let mut dest_txs: Option<Vec<libwallet::types::TxLogEntry>> = None;
let mut src_accts: Option<Vec<AcctPathMapping>> = None;
let mut dest_accts: Option<Vec<AcctPathMapping>> = None;
// Overall wallet info should be the same
wallet::controller::owner_single_use(wallet_source.clone(), |api| {
src_info = Some(api.retrieve_summary_info(true, 1)?.1);
src_txs = Some(api.retrieve_txs(true, None, None)?.1);
src_accts = Some(api.accounts()?);
Ok(())
})?;
wallet::controller::owner_single_use(wallet_dest.clone(), |api| {
dest_info = Some(api.retrieve_summary_info(true, 1)?.1);
dest_txs = Some(api.retrieve_txs(true, None, None)?.1);
dest_accts = Some(api.accounts()?);
Ok(())
})?;
// Info should all be the same
assert_eq!(src_info, dest_info);
// Net differences in TX logs should be the same
let src_sum: i64 = src_txs
.clone()
.unwrap()
.iter()
.map(|t| t.amount_credited as i64 - t.amount_debited as i64)
.sum();
let dest_sum: i64 = dest_txs
.clone()
.unwrap()
.iter()
.map(|t| t.amount_credited as i64 - t.amount_debited as i64)
.sum();
assert_eq!(src_sum, dest_sum);
// Number of created accounts should be the same
assert_eq!(
src_accts.as_ref().unwrap().len(),
dest_accts.as_ref().unwrap().len()
);
wp_running.store(false, Ordering::Relaxed);
//thread::sleep(Duration::from_millis(1000));
Ok(())
}
/// Build up 2 wallets, perform a few transactions on them
/// Then attempt to restore them in separate directories and check contents are the same
fn setup_restore(test_dir: &str) -> Result<(), libwallet::Error> {
setup(test_dir);
// Create a new proxy to simulate server and wallet responses
let mut wallet_proxy: WalletProxy<LocalWalletClient, ExtKeychain> = WalletProxy::new(test_dir);
let chain = wallet_proxy.chain.clone();
// Create a new wallet test client, and set its queues to communicate with the
// proxy
let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone());
let wallet1 =
test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone(), None);
wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone());
// define recipient wallet, add to proxy
let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone());
let wallet2 =
test_framework::create_wallet(&format!("{}/wallet2", test_dir), client2.clone(), None);
wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone());
// wallet 2 will use another account
wallet::controller::owner_single_use(wallet2.clone(), |api| {
api.create_account_path("account1")?;
api.create_account_path("account2")?;
Ok(())
})?;
// Default wallet 2 to listen on that account
{
let mut w = wallet2.lock();
w.set_parent_key_id_by_name("account1")?;
}
// Another wallet
let client3 = LocalWalletClient::new("wallet3", wallet_proxy.tx.clone());
let wallet3 =
test_framework::create_wallet(&format!("{}/wallet3", test_dir), client3.clone(), None);
wallet_proxy.add_wallet("wallet3", client3.get_send_instance(), wallet3.clone());
// Set the wallet proxy listener running
let wp_running = wallet_proxy.running.clone();
thread::spawn(move || {
if let Err(e) = wallet_proxy.run() {
error!("Wallet Proxy error: {}", e);
}
});
// mine a few blocks
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), 10);
// assert wallet contents
// and a single use api for a send command
let amount = 60_000_000_000;
let mut slate = Slate::blank(1);
wallet::controller::owner_single_use(wallet1.clone(), |sender_api| {
// note this will increment the block count as part of the transaction "Posting"
let (slate_i, lock_fn) = sender_api.initiate_tx(
None, amount, // amount
2, // minimum confirmations
500, // max outputs
1, // num change outputs
true, // select all outputs
None,
)?;
slate = client1.send_tx_slate_direct("wallet2", &slate_i)?;
sender_api.tx_lock_outputs(&slate, lock_fn)?;
sender_api.finalize_tx(&mut slate)?;
sender_api.post_tx(&slate.tx, false)?;
Ok(())
})?;
// mine a few more blocks
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), 3);
// Send some to wallet 3
wallet::controller::owner_single_use(wallet1.clone(), |sender_api| {
// note this will increment the block count as part of the transaction "Posting"
let (slate_i, lock_fn) = sender_api.initiate_tx(
None,
amount * 2, // amount
2, // minimum confirmations
500, // max outputs
1, // num change outputs
true, // select all outputs
None,
)?;
slate = client1.send_tx_slate_direct("wallet3", &slate_i)?;
sender_api.tx_lock_outputs(&slate, lock_fn)?;
sender_api.finalize_tx(&mut slate)?;
sender_api.post_tx(&slate.tx, false)?;
Ok(())
})?;
// mine a few more blocks
let _ = test_framework::award_blocks_to_wallet(&chain, wallet3.clone(), 10);
// Wallet3 to wallet 2
wallet::controller::owner_single_use(wallet3.clone(), |sender_api| {
// note this will increment the block count as part of the transaction "Posting"
let (slate_i, lock_fn) = sender_api.initiate_tx(
None,
amount * 3, // amount
2, // minimum confirmations
500, // max outputs
1, // num change outputs
true, // select all outputs
None,
)?;
slate = client3.send_tx_slate_direct("wallet2", &slate_i)?;
sender_api.tx_lock_outputs(&slate, lock_fn)?;
sender_api.finalize_tx(&mut slate)?;
sender_api.post_tx(&slate.tx, false)?;
Ok(())
})?;
// Another listener account on wallet 2
{
let mut w = wallet2.lock();
w.set_parent_key_id_by_name("account2")?;
}
// mine a few more blocks
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), 2);
// Wallet3 to wallet 2 again (to another account)
wallet::controller::owner_single_use(wallet3.clone(), |sender_api| {
// note this will increment the block count as part of the transaction "Posting"
let (slate_i, lock_fn) = sender_api.initiate_tx(
None,
amount * 3, // amount
2, // minimum confirmations
500, // max outputs
1, // num change outputs
true, // select all outputs
None,
)?;
slate = client3.send_tx_slate_direct("wallet2", &slate_i)?;
sender_api.tx_lock_outputs(&slate, lock_fn)?;
sender_api.finalize_tx(&mut slate)?;
sender_api.post_tx(&slate.tx, false)?;
Ok(())
})?;
// mine a few more blocks
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), 5);
// update everyone
wallet::controller::owner_single_use(wallet1.clone(), |api| {
let _ = api.retrieve_summary_info(true, 1)?;
Ok(())
})?;
wallet::controller::owner_single_use(wallet2.clone(), |api| {
let _ = api.retrieve_summary_info(true, 1)?;
Ok(())
})?;
wallet::controller::owner_single_use(wallet3.clone(), |api| {
let _ = api.retrieve_summary_info(true, 1)?;
Ok(())
})?;
wp_running.store(false, Ordering::Relaxed);
Ok(())
}
fn perform_restore(test_dir: &str) -> Result<(), libwallet::Error> {
restore_wallet(test_dir, "wallet1")?;
compare_wallet_restore(
test_dir,
"wallet1",
&ExtKeychain::derive_key_id(2, 0, 0, 0, 0),
)?;
restore_wallet(test_dir, "wallet2")?;
compare_wallet_restore(
test_dir,
"wallet2",
&ExtKeychain::derive_key_id(2, 0, 0, 0, 0),
)?;
compare_wallet_restore(
test_dir,
"wallet2",
&ExtKeychain::derive_key_id(2, 1, 0, 0, 0),
)?;
compare_wallet_restore(
test_dir,
"wallet2",
&ExtKeychain::derive_key_id(2, 2, 0, 0, 0),
)?;
restore_wallet(test_dir, "wallet3")?;
compare_wallet_restore(
test_dir,
"wallet3",
&ExtKeychain::derive_key_id(2, 0, 0, 0, 0),
)?;
Ok(())
}
#[test]
fn wallet_restore() {
let test_dir = "test_output/wallet_restore";
if let Err(e) = setup_restore(test_dir) {
panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap());
}
if let Err(e) = perform_restore(test_dir) {
panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap());
}
// let logging finish
thread::sleep(Duration::from_millis(200));
}
+144
View File
@@ -0,0 +1,144 @@
// Copyright 2018 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;
use self::core::global;
use self::core::global::ChainTypes;
use self::keychain::ExtKeychain;
use self::wallet::libwallet;
use self::wallet::test_framework::{self, LocalWalletClient, WalletProxy};
use grin_core as core;
use grin_keychain as keychain;
use grin_util as util;
use grin_wallet as wallet;
use std::fs;
use std::thread;
use std::time::Duration;
fn clean_output_dir(test_dir: &str) {
let _ = fs::remove_dir_all(test_dir);
}
fn setup(test_dir: &str) {
util::init_test_logger();
clean_output_dir(test_dir);
global::set_mining_mode(ChainTypes::AutomatedTesting);
}
/// self send impl
fn self_send_test_impl(test_dir: &str) -> Result<(), libwallet::Error> {
setup(test_dir);
// Create a new proxy to simulate server and wallet responses
let mut wallet_proxy: WalletProxy<LocalWalletClient, ExtKeychain> = WalletProxy::new(test_dir);
let chain = wallet_proxy.chain.clone();
// Create a new wallet test client, and set its queues to communicate with the
// proxy
let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone());
let wallet1 =
test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone(), None);
wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone());
// 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(wallet1.clone(), |api| {
api.create_account_path("mining")?;
api.create_account_path("listener")?;
Ok(())
})?;
// Get some mining done
{
let mut w = wallet1.lock();
w.set_parent_key_id_by_name("mining")?;
}
let mut bh = 10u64;
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), bh as usize);
// Should have 5 in account1 (5 spendable), 5 in account (2 spendable)
wallet::controller::owner_single_use(wallet1.clone(), |api| {
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(true, 1)?;
assert!(wallet1_refreshed);
assert_eq!(wallet1_info.last_confirmed_height, bh);
assert_eq!(wallet1_info.total, bh * reward);
// send to send
let (mut slate, lock_fn) = api.initiate_tx(
Some("mining"),
reward * 2, // amount
2, // minimum confirmations
500, // max outputs
1, // num change outputs
true, // select all outputs
None,
)?;
api.tx_lock_outputs(&slate, lock_fn)?;
// Send directly to self
wallet::controller::foreign_single_use(wallet1.clone(), |api| {
api.receive_tx(&mut slate, Some("listener"), None)?;
Ok(())
})?;
api.finalize_tx(&mut slate)?;
api.post_tx(&slate.tx, false)?; // mines a block
bh += 1;
Ok(())
})?;
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), 3);
bh += 3;
// Check total in mining account
wallet::controller::owner_single_use(wallet1.clone(), |api| {
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(true, 1)?;
assert!(wallet1_refreshed);
assert_eq!(wallet1_info.last_confirmed_height, bh);
assert_eq!(wallet1_info.total, bh * reward - reward * 2);
Ok(())
})?;
// Check total in 'listener' account
{
let mut w = wallet1.lock();
w.set_parent_key_id_by_name("listener")?;
}
wallet::controller::owner_single_use(wallet1.clone(), |api| {
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(true, 1)?;
assert!(wallet1_refreshed);
assert_eq!(wallet1_info.last_confirmed_height, bh);
assert_eq!(wallet1_info.total, 2 * reward);
Ok(())
})?;
// let logging finish
thread::sleep(Duration::from_millis(200));
Ok(())
}
#[test]
fn wallet_self_send() {
let test_dir = "test_output/self_send";
if let Err(e) = self_send_test_impl(test_dir) {
panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap());
}
}
+498
View File
@@ -0,0 +1,498 @@
// Copyright 2018 The Grin Developers
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! tests for transactions building within core::libtx
#[macro_use]
extern crate log;
use self::core::global;
use self::core::global::ChainTypes;
use self::keychain::ExtKeychain;
use self::libwallet::slate::Slate;
use self::wallet::libwallet;
use self::wallet::libwallet::types::OutputStatus;
use self::wallet::test_framework::{self, LocalWalletClient, WalletProxy};
use grin_core as core;
use grin_keychain as keychain;
use grin_util as util;
use grin_wallet as wallet;
use std::fs;
use std::thread;
use std::time::Duration;
fn clean_output_dir(test_dir: &str) {
let _ = fs::remove_dir_all(test_dir);
}
fn setup(test_dir: &str) {
util::init_test_logger();
clean_output_dir(test_dir);
global::set_mining_mode(ChainTypes::AutomatedTesting);
}
/// Exercises the Transaction API fully with a test NodeClient operating
/// directly on a chain instance
/// Callable with any type of wallet
fn basic_transaction_api(test_dir: &str) -> Result<(), libwallet::Error> {
setup(test_dir);
// Create a new proxy to simulate server and wallet responses
let mut wallet_proxy: WalletProxy<LocalWalletClient, ExtKeychain> = WalletProxy::new(test_dir);
let chain = wallet_proxy.chain.clone();
// Create a new wallet test client, and set its queues to communicate with the
// proxy
let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone());
let wallet1 =
test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone(), None);
wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone());
let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone());
// define recipient wallet, add to proxy
let wallet2 =
test_framework::create_wallet(&format!("{}/wallet2", test_dir), client2.clone(), None);
wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone());
// Set the wallet proxy listener running
thread::spawn(move || {
if let Err(e) = wallet_proxy.run() {
error!("Wallet Proxy error: {}", e);
}
});
// few values to keep things shorter
let reward = core::consensus::REWARD;
let cm = global::coinbase_maturity(); // assume all testing precedes soft fork height
// mine a few blocks
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), 10);
// Check wallet 1 contents are as expected
wallet::controller::owner_single_use(wallet1.clone(), |api| {
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(true, 1)?;
debug!(
"Wallet 1 Info Pre-Transaction, after {} blocks: {:?}",
wallet1_info.last_confirmed_height, wallet1_info
);
assert!(wallet1_refreshed);
assert_eq!(
wallet1_info.amount_currently_spendable,
(wallet1_info.last_confirmed_height - cm) * reward
);
assert_eq!(wallet1_info.amount_immature, cm * reward);
Ok(())
})?;
// assert wallet contents
// and a single use api for a send command
let amount = 60_000_000_000;
let mut slate = Slate::blank(1);
wallet::controller::owner_single_use(wallet1.clone(), |sender_api| {
// note this will increment the block count as part of the transaction "Posting"
let (slate_i, lock_fn) = sender_api.initiate_tx(
None, amount, // amount
2, // minimum confirmations
500, // max outputs
1, // num change outputs
true, // select all outputs
None,
)?;
slate = client1.send_tx_slate_direct("wallet2", &slate_i)?;
sender_api.tx_lock_outputs(&slate, lock_fn)?;
sender_api.finalize_tx(&mut slate)?;
Ok(())
})?;
// Check transaction log for wallet 1
wallet::controller::owner_single_use(wallet1.clone(), |api| {
let (_, wallet1_info) = api.retrieve_summary_info(true, 1)?;
let (refreshed, txs) = api.retrieve_txs(true, None, None)?;
assert!(refreshed);
let fee = core::libtx::tx_fee(
wallet1_info.last_confirmed_height as usize - cm as usize,
2,
1,
None,
);
// we should have a transaction entry for this slate
let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id));
assert!(tx.is_some());
let tx = tx.unwrap();
assert!(!tx.confirmed);
assert!(tx.confirmation_ts.is_none());
assert_eq!(tx.amount_debited - tx.amount_credited, fee + amount);
assert_eq!(Some(fee), tx.fee);
Ok(())
})?;
// Check transaction log for wallet 2
wallet::controller::owner_single_use(wallet2.clone(), |api| {
let (refreshed, txs) = api.retrieve_txs(true, None, None)?;
assert!(refreshed);
// we should have a transaction entry for this slate
let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id));
assert!(tx.is_some());
let tx = tx.unwrap();
assert!(!tx.confirmed);
assert!(tx.confirmation_ts.is_none());
assert_eq!(amount, tx.amount_credited);
assert_eq!(0, tx.amount_debited);
assert_eq!(None, tx.fee);
Ok(())
})?;
// post transaction
wallet::controller::owner_single_use(wallet1.clone(), |api| {
api.post_tx(&slate.tx, false)?;
Ok(())
})?;
// Check wallet 1 contents are as expected
wallet::controller::owner_single_use(wallet1.clone(), |api| {
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(true, 1)?;
debug!(
"Wallet 1 Info Post Transaction, after {} blocks: {:?}",
wallet1_info.last_confirmed_height, wallet1_info
);
let fee = core::libtx::tx_fee(
wallet1_info.last_confirmed_height as usize - 1 - cm as usize,
2,
1,
None,
);
assert!(wallet1_refreshed);
// wallet 1 received fees, so amount should be the same
assert_eq!(
wallet1_info.total,
amount * wallet1_info.last_confirmed_height - amount
);
assert_eq!(
wallet1_info.amount_currently_spendable,
(wallet1_info.last_confirmed_height - cm) * reward - amount - fee
);
assert_eq!(wallet1_info.amount_immature, cm * reward + fee);
// check tx log entry is confirmed
let (refreshed, txs) = api.retrieve_txs(true, None, None)?;
assert!(refreshed);
let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id));
assert!(tx.is_some());
let tx = tx.unwrap();
assert!(tx.confirmed);
assert!(tx.confirmation_ts.is_some());
Ok(())
})?;
// mine a few more blocks
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), 3);
// refresh wallets and retrieve info/tests for each wallet after maturity
wallet::controller::owner_single_use(wallet1.clone(), |api| {
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(true, 1)?;
debug!("Wallet 1 Info: {:?}", wallet1_info);
assert!(wallet1_refreshed);
assert_eq!(
wallet1_info.total,
amount * wallet1_info.last_confirmed_height - amount
);
assert_eq!(
wallet1_info.amount_currently_spendable,
(wallet1_info.last_confirmed_height - cm - 1) * reward
);
Ok(())
})?;
wallet::controller::owner_single_use(wallet2.clone(), |api| {
let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(true, 1)?;
assert!(wallet2_refreshed);
assert_eq!(wallet2_info.amount_currently_spendable, amount);
// check tx log entry is confirmed
let (refreshed, txs) = api.retrieve_txs(true, None, None)?;
assert!(refreshed);
let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id));
assert!(tx.is_some());
let tx = tx.unwrap();
assert!(tx.confirmed);
assert!(tx.confirmation_ts.is_some());
Ok(())
})?;
// Estimate fee and locked amount for a transaction
wallet::controller::owner_single_use(wallet1.clone(), |sender_api| {
let (total, fee) = sender_api.estimate_initiate_tx(
None,
amount * 2, // amount
2, // minimum confirmations
500, // max outputs
1, // num change outputs
true, // select all outputs
)?;
assert_eq!(total, 600_000_000_000);
assert_eq!(fee, 4_000_000);
let (total, fee) = sender_api.estimate_initiate_tx(
None,
amount * 2, // amount
2, // minimum confirmations
500, // max outputs
1, // num change outputs
false, // select the smallest amount of outputs
)?;
assert_eq!(total, 180_000_000_000);
assert_eq!(fee, 6_000_000);
Ok(())
})?;
// Send another transaction, but don't post to chain immediately and use
// the stored transaction instead
wallet::controller::owner_single_use(wallet1.clone(), |sender_api| {
// note this will increment the block count as part of the transaction "Posting"
let (slate_i, lock_fn) = sender_api.initiate_tx(
None,
amount * 2, // amount
2, // minimum confirmations
500, // max outputs
1, // num change outputs
true, // select all outputs
None,
)?;
slate = client1.send_tx_slate_direct("wallet2", &slate_i)?;
sender_api.tx_lock_outputs(&slate, lock_fn)?;
sender_api.finalize_tx(&mut slate)?;
Ok(())
})?;
wallet::controller::owner_single_use(wallet1.clone(), |sender_api| {
let (refreshed, _wallet1_info) = sender_api.retrieve_summary_info(true, 1)?;
assert!(refreshed);
let (_, txs) = sender_api.retrieve_txs(true, None, None)?;
// find the transaction
let tx = txs
.iter()
.find(|t| t.tx_slate_id == Some(slate.id))
.unwrap();
let stored_tx = sender_api.get_stored_tx(&tx)?;
sender_api.post_tx(&stored_tx.unwrap(), false)?;
let (_, wallet1_info) = sender_api.retrieve_summary_info(true, 1)?;
// should be mined now
assert_eq!(
wallet1_info.total,
amount * wallet1_info.last_confirmed_height - amount * 3
);
Ok(())
})?;
// mine a few more blocks
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), 3);
// check wallet2 has stored transaction
wallet::controller::owner_single_use(wallet2.clone(), |api| {
let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(true, 1)?;
assert!(wallet2_refreshed);
assert_eq!(wallet2_info.amount_currently_spendable, amount * 3);
// check tx log entry is confirmed
let (refreshed, txs) = api.retrieve_txs(true, None, None)?;
assert!(refreshed);
let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id));
assert!(tx.is_some());
let tx = tx.unwrap();
assert!(tx.confirmed);
assert!(tx.confirmation_ts.is_some());
Ok(())
})?;
// let logging finish
thread::sleep(Duration::from_millis(200));
Ok(())
}
/// Test rolling back transactions and outputs when a transaction is never
/// posted to a chain
fn tx_rollback(test_dir: &str) -> Result<(), libwallet::Error> {
setup(test_dir);
// Create a new proxy to simulate server and wallet responses
let mut wallet_proxy: WalletProxy<LocalWalletClient, ExtKeychain> = WalletProxy::new(test_dir);
let chain = wallet_proxy.chain.clone();
// Create a new wallet test client, and set its queues to communicate with the
// proxy
let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone());
let wallet1 =
test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone(), None);
wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone());
// define recipient wallet, add to proxy
let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone());
let wallet2 =
test_framework::create_wallet(&format!("{}/wallet2", test_dir), client2.clone(), None);
wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone());
// Set the wallet proxy listener running
thread::spawn(move || {
if let Err(e) = wallet_proxy.run() {
error!("Wallet Proxy error: {}", e);
}
});
// few values to keep things shorter
let reward = core::consensus::REWARD;
let cm = global::coinbase_maturity(); // assume all testing precedes soft fork height
// mine a few blocks
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), 5);
let amount = 30_000_000_000;
let mut slate = Slate::blank(1);
wallet::controller::owner_single_use(wallet1.clone(), |sender_api| {
// note this will increment the block count as part of the transaction "Posting"
let (slate_i, lock_fn) = sender_api.initiate_tx(
None, amount, // amount
2, // minimum confirmations
500, // max outputs
1, // num change outputs
true, // select all outputs
None,
)?;
slate = client1.send_tx_slate_direct("wallet2", &slate_i)?;
sender_api.tx_lock_outputs(&slate, lock_fn)?;
sender_api.finalize_tx(&mut slate)?;
Ok(())
})?;
// Check transaction log for wallet 1
wallet::controller::owner_single_use(wallet1.clone(), |api| {
let (refreshed, wallet1_info) = api.retrieve_summary_info(true, 1)?;
println!(
"last confirmed height: {}",
wallet1_info.last_confirmed_height
);
assert!(refreshed);
let (_, txs) = api.retrieve_txs(true, None, None)?;
// we should have a transaction entry for this slate
let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id));
assert!(tx.is_some());
let mut locked_count = 0;
let mut unconfirmed_count = 0;
// get the tx entry, check outputs are as expected
let (_, outputs) = api.retrieve_outputs(true, false, Some(tx.unwrap().id))?;
for (o, _) in outputs.clone() {
if o.status == OutputStatus::Locked {
locked_count = locked_count + 1;
}
if o.status == OutputStatus::Unconfirmed {
unconfirmed_count = unconfirmed_count + 1;
}
}
assert_eq!(outputs.len(), 3);
assert_eq!(locked_count, 2);
assert_eq!(unconfirmed_count, 1);
Ok(())
})?;
// Check transaction log for wallet 2
wallet::controller::owner_single_use(wallet2.clone(), |api| {
let (refreshed, txs) = api.retrieve_txs(true, None, None)?;
assert!(refreshed);
let mut unconfirmed_count = 0;
let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id));
assert!(tx.is_some());
// get the tx entry, check outputs are as expected
let (_, outputs) = api.retrieve_outputs(true, false, Some(tx.unwrap().id))?;
for (o, _) in outputs.clone() {
if o.status == OutputStatus::Unconfirmed {
unconfirmed_count = unconfirmed_count + 1;
}
}
assert_eq!(outputs.len(), 1);
assert_eq!(unconfirmed_count, 1);
let (refreshed, wallet2_info) = api.retrieve_summary_info(true, 1)?;
assert!(refreshed);
assert_eq!(wallet2_info.amount_currently_spendable, 0,);
assert_eq!(wallet2_info.total, amount);
Ok(())
})?;
// wallet 1 is bold and doesn't ever post the transaction mine a few more blocks
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), 5);
// Wallet 1 decides to roll back instead
wallet::controller::owner_single_use(wallet1.clone(), |api| {
// can't roll back coinbase
let res = api.cancel_tx(Some(1), None);
assert!(res.is_err());
let (_, txs) = api.retrieve_txs(true, None, None)?;
let tx = txs
.iter()
.find(|t| t.tx_slate_id == Some(slate.id))
.unwrap();
api.cancel_tx(Some(tx.id), None)?;
let (refreshed, wallet1_info) = api.retrieve_summary_info(true, 1)?;
assert!(refreshed);
println!(
"last confirmed height: {}",
wallet1_info.last_confirmed_height
);
// check all eligible inputs should be now be spendable
println!("cm: {}", cm);
assert_eq!(
wallet1_info.amount_currently_spendable,
(wallet1_info.last_confirmed_height - cm) * reward
);
// can't roll back again
let res = api.cancel_tx(Some(tx.id), None);
assert!(res.is_err());
Ok(())
})?;
// Wallet 2 rolls back
wallet::controller::owner_single_use(wallet2.clone(), |api| {
let (_, txs) = api.retrieve_txs(true, None, None)?;
let tx = txs
.iter()
.find(|t| t.tx_slate_id == Some(slate.id))
.unwrap();
api.cancel_tx(Some(tx.id), None)?;
let (refreshed, wallet2_info) = api.retrieve_summary_info(true, 1)?;
assert!(refreshed);
// check all eligible inputs should be now be spendable
assert_eq!(wallet2_info.amount_currently_spendable, 0,);
assert_eq!(wallet2_info.total, 0,);
// can't roll back again
let res = api.cancel_tx(Some(tx.id), None);
assert!(res.is_err());
Ok(())
})?;
// let logging finish
thread::sleep(Duration::from_millis(200));
Ok(())
}
#[test]
fn db_wallet_basic_transaction_api() {
let test_dir = "test_output/basic_transaction_api";
if let Err(e) = basic_transaction_api(test_dir) {
panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap());
}
}
#[test]
fn db_wallet_tx_rollback() {
let test_dir = "test_output/tx_rollback";
if let Err(e) = tx_rollback(test_dir) {
panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap());
}
}