mirror of
https://github.com/mimblewimble/grin.git
synced 2025-01-21 19:41:08 +03:00
4bbaa8d05f
* refactoring transaction building code * serialise return transaction * move shared functions into transactions, ensure wallet manipulation is only done outside of aggsig transaction lib * remove unneeded wallet config from fn * adding test functions to facilitate libwallet transaction testing * rustfmt * refactoring checker somewhat, adding ability to create and transactions against local copy of chain for simpler testing * finish transaction testing functionality which verifies transactions work properly * Remove wallet output manipulation from transaction building lib * ensure sender expects full transaction back on last phase * ensure sender expects full transaction back on last phase
235 lines
7.2 KiB
Rust
235 lines
7.2 KiB
Rust
// 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);
|
|
}
|