mirror of
https://github.com/jwinterm/grin-wallet.git
synced 2026-10-17 15:21:39 +02:00
initial commit
This commit is contained in:
@@ -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
@@ -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
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user