grin/wallet/tests/transaction.rs

236 lines
7.2 KiB
Rust
Raw Normal View History

// 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 libwallet
extern crate grin_chain as chain;
extern crate grin_core as core;
extern crate grin_keychain as keychain;
extern crate grin_util as util;
extern crate grin_wallet as wallet;
extern crate rand;
#[macro_use]
extern crate slog;
extern crate time;
extern crate uuid;
mod common;
use std::fs;
use std::sync::Arc;
use uuid::Uuid;
use chain::Chain;
use chain::types::*;
use core::{global, pow};
use core::global::ChainTypes;
use wallet::libwallet::{aggsig, transaction};
use wallet::grinwallet::{keys, selection};
use wallet::types::{OutputData, OutputStatus, WalletData};
use util::LOGGER;
fn clean_output_dir(test_dir: &str) {
let _ = fs::remove_dir_all(test_dir);
}
fn setup(test_dir: &str, chain_dir: &str) -> Chain {
util::init_test_logger();
clean_output_dir(test_dir);
global::set_mining_mode(ChainTypes::AutomatedTesting);
let genesis_block = pow::mine_genesis_block().unwrap();
let dir_name = format!("{}/{}", test_dir, chain_dir);
chain::Chain::init(
dir_name.to_string(),
Arc::new(NoopAdapter {}),
genesis_block,
pow::verify_size,
).unwrap()
}
/// Build a transaction between 2 parties
#[cfg(test)]
#[test]
fn build_transaction() {
let chain = setup("test_output", "build_transaction/.grin");
let wallet1 = common::create_wallet("test_output/build_transaction/wallet1");
let wallet2 = common::create_wallet("test_output/build_transaction/wallet2");
common::award_blocks_to_wallet(&chain, &wallet1, 10);
// Wallet 1 has 600 Grins, wallet 2 has 0. Create a transaction that sends
// 300 Grins from wallet 1 to wallet 2, using libwallet
// Sender creates a new aggsig context
// SENDER (create sender initiation)
let mut sender_context_manager = aggsig::ContextManager::new();
let tx_id = Uuid::new_v4();
// Get lock height
let chain_tip = chain.head().unwrap();
// ensure outputs we're selecting are up to date
let res = common::refresh_output_state_local(&wallet1.0, &wallet1.1, &chain);
let amount = 300_000_000_000;
// Select our outputs
let tx_data = selection::build_send_tx(
&wallet1.0,
&wallet1.1,
amount,
chain_tip.height,
3,
chain_tip.height,
1000,
true,
).unwrap();
if let Err(e) = res {
panic!("Unable to refresh sender wallet outputs: {}", e);
}
let partial_tx = transaction::sender_initiation(
&wallet1.1,
&tx_id,
&mut sender_context_manager,
chain_tip.height,
tx_data,
).unwrap();
let sender_context = sender_context_manager.get_context(&tx_id);
// TODO: Might make more sense to do this before the transaction
// building call
// Closure to acquire wallet lock and lock the coins being spent
// so we avoid accidental double spend attempt.
let update_sender_wallet = || {
WalletData::with_wallet(&wallet1.0.data_file_dir, |wallet_data| {
for id in sender_context.get_outputs().clone() {
let coin = wallet_data.get_output(&id).unwrap().clone();
wallet_data.lock_output(&coin);
}
})
};
debug!(LOGGER, "PartialTx after step 1: sender initiation");
debug!(LOGGER, "-----------------------------------------");
debug!(LOGGER, "{:?}", partial_tx);
// RECIPIENT (Handle sender initiation)
let mut recipient_context_manager = aggsig::ContextManager::new();
// Create a potential output for this transaction
let (key_id, derivation) = WalletData::with_wallet(&wallet2.0.data_file_dir, |wallet_data| {
keys::next_available_key(&wallet_data, &wallet2.1)
}).unwrap();
let partial_tx = transaction::recipient_initiation(
&wallet2.1,
&mut recipient_context_manager,
&partial_tx,
&key_id,
).unwrap();
let mut context = recipient_context_manager.get_context(&partial_tx.id);
// Add the output to recipient's wallet
let _ = WalletData::with_wallet(&wallet2.0.data_file_dir, |wallet_data| {
wallet_data.add_output(OutputData {
root_key_id: wallet2.1.root_key_id(),
key_id: key_id.clone(),
n_child: derivation,
value: partial_tx.amount - context.fee,
status: OutputStatus::Unconfirmed,
height: 0,
lock_height: 0,
is_coinbase: false,
block: None,
merkle_proof: None,
});
}).unwrap();
context.add_output(&key_id);
recipient_context_manager.save_context(context);
debug!(LOGGER, "PartialTx after step 2: recipient initiation");
debug!(LOGGER, "--------------------------------------------");
debug!(LOGGER, "{:?}", partial_tx);
// TODO: We want to allow the sender to be able to calculate this, but also need
// the recipient's output information available, and the recipient needs to know
// whether to finalize the output in their wallet
let _tx_with_recipients_pubkeys = partial_tx.clone();
// SENDER Part 3: Sender confirmation
let partial_tx =
transaction::sender_confirmation(&wallet1.1, &mut sender_context_manager, partial_tx)
.unwrap();
debug!(LOGGER, "PartialTx after step 3: sender confirmation");
debug!(LOGGER, "--------------------------------------------");
debug!(LOGGER, "{:?}", partial_tx);
// RECIPIENT Part 4: Recipient confirmation
// Get output we created in earlier step
let context = recipient_context_manager.get_context(&partial_tx.id);
let output_vec = context.get_outputs();
let root_key_id = &wallet2.1.root_key_id();
// operate within a lock on wallet data
let (key_id, derivation) = WalletData::with_wallet(&wallet2.0.data_file_dir, |wallet_data| {
let (key_id, derivation) = keys::retrieve_existing_key(&wallet_data, output_vec[0].clone());
wallet_data.add_output(OutputData {
root_key_id: root_key_id.clone(),
key_id: key_id.clone(),
n_child: derivation,
value: partial_tx.amount - context.fee,
status: OutputStatus::Unconfirmed,
height: 0,
lock_height: 0,
is_coinbase: false,
block: None,
merkle_proof: None,
});
(key_id, derivation)
}).unwrap();
let final_tx_recipient = transaction::finalize_transaction(
&wallet2.1,
&mut recipient_context_manager,
&partial_tx,
&partial_tx,
&key_id,
derivation,
);
if let Err(e) = final_tx_recipient {
panic!("Error creating final tx: {:?}", e);
}
debug!(LOGGER, "Recipient calculates final transaction as:");
debug!(LOGGER, "--------------------------------------------");
debug!(LOGGER, "{:?}", final_tx_recipient);
let _ = update_sender_wallet();
// Insert this transaction into a new block, then mine till confirmation
common::award_block_to_wallet(&chain, vec![&final_tx_recipient.unwrap()], &wallet1);
common::award_blocks_to_wallet(&chain, &wallet1, 3);
// Refresh wallets
let res = common::refresh_output_state_local(&wallet2.0, &wallet2.1, &chain);
if let Err(e) = res {
panic!("Error refreshing output state for wallet: {:?}", e);
}
let chain_tip = chain.head().unwrap();
let balances = common::get_wallet_balances(&wallet2.0, &wallet2.1, chain_tip.height).unwrap();
assert_eq!(balances.3, 300_000_000_000);
}