mirror of
https://github.com/mimblewimble/grin.git
synced 2025-01-20 19:11:08 +03:00
Move aggsig transaction building functions into separate lib (#1061)
* 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
This commit is contained in:
parent
24e21f013d
commit
4bbaa8d05f
17 changed files with 1413 additions and 659 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -799,6 +799,7 @@ dependencies = [
|
|||
"failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"grin_api 0.2.0",
|
||||
"grin_chain 0.2.0",
|
||||
"grin_core 0.2.0",
|
||||
"grin_keychain 0.2.0",
|
||||
"grin_util 0.2.0",
|
||||
|
@ -813,6 +814,7 @@ dependencies = [
|
|||
"serde_json 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"slog 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"term 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-core 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-retry 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"urlencoded 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
|
|
@ -38,6 +38,7 @@ use util::LOGGER;
|
|||
|
||||
bitflags! {
|
||||
/// Options for a kernel's structure or use
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct KernelFeatures: u8 {
|
||||
/// No flags
|
||||
const DEFAULT_KERNEL = 0b00000000;
|
||||
|
@ -114,7 +115,7 @@ impl From<keychain::Error> for Error {
|
|||
/// amount to zero.
|
||||
/// The signature signs the fee and the lock_height, which are retained for
|
||||
/// signature validation.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct TxKernel {
|
||||
/// Options for a kernel's structure or use
|
||||
pub features: KernelFeatures,
|
||||
|
@ -234,7 +235,7 @@ impl PMMRable for TxKernel {
|
|||
}
|
||||
|
||||
/// A transaction
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct Transaction {
|
||||
/// List of inputs spent by the transaction.
|
||||
pub inputs: Vec<Input>,
|
||||
|
@ -692,7 +693,7 @@ pub fn deaggregate(mk_tx: Transaction, txs: Vec<Transaction>) -> Result<Transact
|
|||
/// A transaction input.
|
||||
///
|
||||
/// Primarily a reference to an output being spent by the transaction.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct Input {
|
||||
/// The features of the output being spent.
|
||||
/// We will check maturity for coinbase output.
|
||||
|
|
|
@ -32,3 +32,7 @@ grin_api = { path = "../api" }
|
|||
grin_core = { path = "../core" }
|
||||
grin_keychain = { path = "../keychain" }
|
||||
grin_util = { path = "../util" }
|
||||
|
||||
[dev-dependencies]
|
||||
grin_chain = { path = "../chain" }
|
||||
time = "0.1"
|
||||
|
|
|
@ -57,19 +57,7 @@ pub fn refresh_outputs(config: &WalletConfig, keychain: &Keychain) -> Result<(),
|
|||
fn refresh_missing_block_hashes(config: &WalletConfig, keychain: &Keychain) -> Result<(), Error> {
|
||||
// build a local map of wallet outputs keyed by commit
|
||||
// and a list of outputs we want to query the node for
|
||||
let mut wallet_outputs: HashMap<pedersen::Commitment, Identifier> = HashMap::new();
|
||||
let _ = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
|
||||
for out in wallet_data.outputs.values().filter(|x| {
|
||||
x.root_key_id == keychain.root_key_id() && x.block.is_none()
|
||||
&& x.status == OutputStatus::Unspent
|
||||
}) {
|
||||
let commit = keychain
|
||||
.commit_with_key_index(out.value, out.n_child)
|
||||
.context(ErrorKind::Keychain)?;
|
||||
wallet_outputs.insert(commit, out.key_id.clone());
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
let wallet_outputs = map_wallet_outputs_missing_block(config, keychain)?;
|
||||
|
||||
// nothing to do so return (otherwise we hit the api with a monster query...)
|
||||
if wallet_outputs.is_empty() {
|
||||
|
@ -164,13 +152,12 @@ fn refresh_missing_block_hashes(config: &WalletConfig, keychain: &Keychain) -> R
|
|||
})
|
||||
}
|
||||
|
||||
/// Builds a single api query to retrieve the latest output data from the node.
|
||||
/// So we can refresh the local wallet outputs.
|
||||
fn refresh_output_state(config: &WalletConfig, keychain: &Keychain) -> Result<(), Error> {
|
||||
debug!(LOGGER, "Refreshing wallet outputs");
|
||||
|
||||
// build a local map of wallet outputs keyed by commit
|
||||
// and a list of outputs we want to query the node for
|
||||
/// build a local map of wallet outputs keyed by commit
|
||||
/// and a list of outputs we want to query the node for
|
||||
pub fn map_wallet_outputs(
|
||||
config: &WalletConfig,
|
||||
keychain: &Keychain,
|
||||
) -> Result<HashMap<pedersen::Commitment, Identifier>, Error> {
|
||||
let mut wallet_outputs: HashMap<pedersen::Commitment, Identifier> = HashMap::new();
|
||||
let _ =
|
||||
WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
|
||||
|
@ -184,7 +171,62 @@ fn refresh_output_state(config: &WalletConfig, keychain: &Keychain) -> Result<()
|
|||
}
|
||||
Ok(())
|
||||
});
|
||||
Ok(wallet_outputs)
|
||||
}
|
||||
|
||||
/// As above, but only return unspent outputs with missing block hashes
|
||||
/// and a list of outputs we want to query the node for
|
||||
pub fn map_wallet_outputs_missing_block(
|
||||
config: &WalletConfig,
|
||||
keychain: &Keychain,
|
||||
) -> Result<HashMap<pedersen::Commitment, Identifier>, Error> {
|
||||
let mut wallet_outputs: HashMap<pedersen::Commitment, Identifier> = HashMap::new();
|
||||
let _ = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
|
||||
for out in wallet_data.outputs.values().filter(|x| {
|
||||
x.root_key_id == keychain.root_key_id() && x.block.is_none()
|
||||
&& x.status == OutputStatus::Unspent
|
||||
}) {
|
||||
let commit = keychain
|
||||
.commit_with_key_index(out.value, out.n_child)
|
||||
.context(ErrorKind::Keychain)?;
|
||||
wallet_outputs.insert(commit, out.key_id.clone());
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
Ok(wallet_outputs)
|
||||
}
|
||||
|
||||
/// Apply refreshed API output data to the wallet
|
||||
pub fn apply_api_outputs(
|
||||
config: &WalletConfig,
|
||||
wallet_outputs: &HashMap<pedersen::Commitment, Identifier>,
|
||||
api_outputs: &HashMap<pedersen::Commitment, api::Output>,
|
||||
) -> Result<(), Error> {
|
||||
// now for each commit, find the output in the wallet and
|
||||
// the corresponding api output (if it exists)
|
||||
// and refresh it in-place in the wallet.
|
||||
// Note: minimizing the time we spend holding the wallet lock.
|
||||
WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
|
||||
for commit in wallet_outputs.keys() {
|
||||
let id = wallet_outputs.get(&commit).unwrap();
|
||||
if let Entry::Occupied(mut output) = wallet_data.outputs.entry(id.to_hex()) {
|
||||
match api_outputs.get(&commit) {
|
||||
Some(_) => mark_unspent_output(&mut output.get_mut()),
|
||||
None => mark_spent_output(&mut output.get_mut()),
|
||||
};
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Builds a single api query to retrieve the latest output data from the node.
|
||||
/// So we can refresh the local wallet outputs.
|
||||
fn refresh_output_state(config: &WalletConfig, keychain: &Keychain) -> Result<(), Error> {
|
||||
debug!(LOGGER, "Refreshing wallet outputs");
|
||||
|
||||
// build a local map of wallet outputs keyed by commit
|
||||
// and a list of outputs we want to query the node for
|
||||
let wallet_outputs = map_wallet_outputs(config, keychain)?;
|
||||
// build the necessary query params -
|
||||
// ?id=xxx&id=yyy&id=zzz
|
||||
let query_params: Vec<String> = wallet_outputs
|
||||
|
@ -238,21 +280,8 @@ fn refresh_output_state(config: &WalletConfig, keychain: &Keychain) -> Result<()
|
|||
}
|
||||
};
|
||||
}
|
||||
// now for each commit, find the output in the wallet and
|
||||
// the corresponding api output (if it exists)
|
||||
// and refresh it in-place in the wallet.
|
||||
// Note: minimizing the time we spend holding the wallet lock.
|
||||
WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
|
||||
for commit in wallet_outputs.keys() {
|
||||
let id = wallet_outputs.get(&commit).unwrap();
|
||||
if let Entry::Occupied(mut output) = wallet_data.outputs.entry(id.to_hex()) {
|
||||
match api_outputs.get(&commit) {
|
||||
Some(_) => mark_unspent_output(&mut output.get_mut()),
|
||||
None => mark_spent_output(&mut output.get_mut()),
|
||||
};
|
||||
}
|
||||
}
|
||||
})
|
||||
apply_api_outputs(config, &wallet_outputs, &api_outputs)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_tip_from_node(config: &WalletConfig) -> Result<api::Tip, Error> {
|
||||
|
|
|
@ -21,6 +21,7 @@ use tokio_core::reactor;
|
|||
use serde_json;
|
||||
|
||||
use types::*;
|
||||
use core::core::Transaction;
|
||||
use util::LOGGER;
|
||||
use std::io;
|
||||
|
||||
|
@ -40,14 +41,6 @@ pub fn create_coinbase(url: &str, block_fees: &BlockFees) -> Result<CbData, Erro
|
|||
}
|
||||
|
||||
pub fn send_partial_tx(url: &str, partial_tx: &PartialTx, fluff: bool) -> Result<PartialTx, Error> {
|
||||
single_send_partial_tx(url, partial_tx, fluff)
|
||||
}
|
||||
|
||||
fn single_send_partial_tx(
|
||||
url: &str,
|
||||
partial_tx: &PartialTx,
|
||||
fluff: bool,
|
||||
) -> Result<PartialTx, Error> {
|
||||
let mut core = reactor::Core::new().context(ErrorKind::Hyper)?;
|
||||
let client = hyper::Client::new(&core.handle());
|
||||
|
||||
|
@ -67,15 +60,49 @@ fn single_send_partial_tx(
|
|||
|
||||
let work = client.request(req).and_then(|res| {
|
||||
res.body().concat2().and_then(move |body| {
|
||||
let partial_tx: PartialTx =
|
||||
let tx: PartialTx =
|
||||
serde_json::from_slice(&body).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||
Ok(partial_tx)
|
||||
Ok(tx)
|
||||
})
|
||||
});
|
||||
let res = core.run(work).context(ErrorKind::Hyper)?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
///TODO: Factor this out with above later, this version just returns a Transaction instead
|
||||
///of a partial TX
|
||||
pub fn send_partial_tx_final(
|
||||
url: &str,
|
||||
partial_tx: &PartialTx,
|
||||
fluff: bool,
|
||||
) -> Result<Transaction, Error> {
|
||||
let mut core = reactor::Core::new().context(ErrorKind::Hyper)?;
|
||||
let client = hyper::Client::new(&core.handle());
|
||||
|
||||
// In case we want to do an express send
|
||||
let mut url_pool = url.to_owned();
|
||||
if fluff {
|
||||
url_pool = format!("{}{}", url, "?fluff");
|
||||
}
|
||||
|
||||
let mut req = Request::new(
|
||||
Method::Post,
|
||||
url_pool.parse::<hyper::Uri>().context(ErrorKind::Hyper)?,
|
||||
);
|
||||
req.headers_mut().set(ContentType::json());
|
||||
let json = serde_json::to_string(&partial_tx).context(ErrorKind::Hyper)?;
|
||||
req.set_body(json);
|
||||
|
||||
let work = client.request(req).and_then(|res| {
|
||||
res.body().concat2().and_then(move |body| {
|
||||
let tx: Transaction =
|
||||
serde_json::from_slice(&body).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||
Ok(tx)
|
||||
})
|
||||
});
|
||||
let res = core.run(work).context(ErrorKind::Hyper)?;
|
||||
Ok(res)
|
||||
}
|
||||
/// Makes a single request to the wallet API to create a new coinbase output.
|
||||
fn single_create_coinbase(url: &str, block_fees: &BlockFees) -> Result<CbData, Error> {
|
||||
let mut core =
|
||||
|
|
45
wallet/src/grinwallet/keys.rs
Normal file
45
wallet/src/grinwallet/keys.rs
Normal file
|
@ -0,0 +1,45 @@
|
|||
// 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.
|
||||
|
||||
//! Grin Wallet specific key management functions
|
||||
use rand::thread_rng;
|
||||
use uuid::Uuid;
|
||||
|
||||
use core::core::{amount_to_hr_string, Committed, Transaction};
|
||||
use libwallet::{aggsig, build};
|
||||
use keychain::{BlindSum, BlindingFactor, Identifier, Keychain};
|
||||
use types::*;
|
||||
use util::{secp, LOGGER};
|
||||
use util::secp::key::{PublicKey, SecretKey};
|
||||
use util::secp::Signature;
|
||||
use failure::ResultExt;
|
||||
|
||||
/// Get next available key in the wallet
|
||||
pub fn next_available_key(wallet_data: &WalletData, keychain: &Keychain) -> (Identifier, u32) {
|
||||
let root_key_id = keychain.root_key_id();
|
||||
let derivation = wallet_data.next_child(root_key_id.clone());
|
||||
let key_id = keychain.derive_key_id(derivation).unwrap();
|
||||
(key_id, derivation)
|
||||
}
|
||||
|
||||
/// Retrieve an existing key from a wallet
|
||||
pub fn retrieve_existing_key(wallet_data: &WalletData, key_id: Identifier) -> (Identifier, u32) {
|
||||
if let Some(existing) = wallet_data.get_output(&key_id) {
|
||||
let key_id = existing.key_id.clone();
|
||||
let derivation = existing.n_child;
|
||||
(key_id, derivation)
|
||||
} else {
|
||||
panic!("should never happen");
|
||||
}
|
||||
}
|
29
wallet/src/grinwallet/mod.rs
Normal file
29
wallet/src/grinwallet/mod.rs
Normal file
|
@ -0,0 +1,29 @@
|
|||
// 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.
|
||||
|
||||
//! Library specific to the Grin wallet implementation, as distinct from
|
||||
//! libwallet, which should build transactions without any knowledge of the
|
||||
//! wallet implementation.
|
||||
|
||||
// TODO: Once this is working, extract a set of traits that wallet
|
||||
// implementations would need to provide
|
||||
|
||||
#![deny(non_upper_case_globals)]
|
||||
#![deny(non_camel_case_types)]
|
||||
#![deny(non_snake_case)]
|
||||
#![deny(unused_mut)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
pub mod selection;
|
||||
pub mod keys;
|
204
wallet/src/grinwallet/selection.rs
Normal file
204
wallet/src/grinwallet/selection.rs
Normal file
|
@ -0,0 +1,204 @@
|
|||
// 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.
|
||||
|
||||
//! Selection of inputs for building transactions
|
||||
|
||||
use core::core::{amount_to_hr_string, Committed, Transaction};
|
||||
use libwallet::{aggsig, build};
|
||||
use keychain::{BlindSum, BlindingFactor, Identifier, Keychain};
|
||||
use types::*;
|
||||
use util::{secp, LOGGER};
|
||||
use util::secp::key::{PublicKey, SecretKey};
|
||||
use util::secp::Signature;
|
||||
use failure::ResultExt;
|
||||
|
||||
/// Builds a transaction to send to someone from the HD seed associated with the
|
||||
/// wallet and the amount to send. Handles reading through the wallet data file,
|
||||
/// selecting outputs to spend and building the change.
|
||||
pub fn build_send_tx(
|
||||
config: &WalletConfig,
|
||||
keychain: &Keychain,
|
||||
amount: u64,
|
||||
current_height: u64,
|
||||
minimum_confirmations: u64,
|
||||
lock_height: u64,
|
||||
max_outputs: usize,
|
||||
selection_strategy_is_use_all: bool,
|
||||
) -> Result<
|
||||
(
|
||||
Transaction,
|
||||
BlindingFactor,
|
||||
Vec<OutputData>,
|
||||
Option<Identifier>,
|
||||
u64,
|
||||
),
|
||||
Error,
|
||||
> {
|
||||
let key_id = keychain.clone().root_key_id();
|
||||
|
||||
// select some spendable coins from the wallet
|
||||
let mut coins = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
|
||||
Ok(wallet_data.select_coins(
|
||||
key_id.clone(),
|
||||
amount,
|
||||
current_height,
|
||||
minimum_confirmations,
|
||||
max_outputs,
|
||||
selection_strategy_is_use_all,
|
||||
))
|
||||
})?;
|
||||
|
||||
// Get the maximum number of outputs in the wallet
|
||||
let max_outputs = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
|
||||
Ok(wallet_data.select_coins(
|
||||
key_id.clone(),
|
||||
amount,
|
||||
current_height,
|
||||
minimum_confirmations,
|
||||
max_outputs,
|
||||
true,
|
||||
))
|
||||
})?.len();
|
||||
|
||||
// sender is responsible for setting the fee on the partial tx
|
||||
// recipient should double check the fee calculation and not blindly trust the
|
||||
// sender
|
||||
let mut fee;
|
||||
// First attempt to spend without change
|
||||
fee = tx_fee(coins.len(), 1, coins_proof_count(&coins), None);
|
||||
let mut total: u64 = coins.iter().map(|c| c.value).sum();
|
||||
let mut amount_with_fee = amount + fee;
|
||||
|
||||
if total == 0 {
|
||||
return Err(ErrorKind::NotEnoughFunds(total as u64))?;
|
||||
}
|
||||
|
||||
// Check if we need to use a change address
|
||||
if total > amount_with_fee {
|
||||
fee = tx_fee(coins.len(), 2, coins_proof_count(&coins), None);
|
||||
amount_with_fee = amount + fee;
|
||||
|
||||
// Here check if we have enough outputs for the amount including fee otherwise
|
||||
// look for other outputs and check again
|
||||
while total < amount_with_fee {
|
||||
// End the loop if we have selected all the outputs and still not enough funds
|
||||
if coins.len() == max_outputs {
|
||||
return Err(ErrorKind::NotEnoughFunds(total as u64))?;
|
||||
}
|
||||
|
||||
// select some spendable coins from the wallet
|
||||
coins = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
|
||||
Ok(wallet_data.select_coins(
|
||||
key_id.clone(),
|
||||
amount_with_fee,
|
||||
current_height,
|
||||
minimum_confirmations,
|
||||
max_outputs,
|
||||
selection_strategy_is_use_all,
|
||||
))
|
||||
})?;
|
||||
fee = tx_fee(coins.len(), 2, coins_proof_count(&coins), None);
|
||||
total = coins.iter().map(|c| c.value).sum();
|
||||
amount_with_fee = amount + fee;
|
||||
}
|
||||
}
|
||||
|
||||
// build transaction skeleton with inputs and change
|
||||
let (mut parts, change_key) = inputs_and_change(&coins, config, keychain, amount, fee)?;
|
||||
|
||||
// This is more proof of concept than anything but here we set lock_height
|
||||
// on tx being sent (based on current chain height via api).
|
||||
parts.push(build::with_lock_height(lock_height));
|
||||
|
||||
let (tx, blind) = build::partial_transaction(parts, &keychain).context(ErrorKind::Keychain)?;
|
||||
|
||||
Ok((tx, blind, coins, change_key, amount_with_fee))
|
||||
}
|
||||
|
||||
/// coins proof count
|
||||
pub fn coins_proof_count(coins: &Vec<OutputData>) -> usize {
|
||||
coins.iter().filter(|c| c.merkle_proof.is_some()).count()
|
||||
}
|
||||
|
||||
/// Selects inputs and change for a transaction
|
||||
pub fn inputs_and_change(
|
||||
coins: &Vec<OutputData>,
|
||||
config: &WalletConfig,
|
||||
keychain: &Keychain,
|
||||
amount: u64,
|
||||
fee: u64,
|
||||
) -> Result<(Vec<Box<build::Append>>, Option<Identifier>), Error> {
|
||||
let mut parts = vec![];
|
||||
|
||||
// calculate the total across all inputs, and how much is left
|
||||
let total: u64 = coins.iter().map(|c| c.value).sum();
|
||||
|
||||
parts.push(build::with_fee(fee));
|
||||
|
||||
// if we are spending 10,000 coins to send 1,000 then our change will be 9,000
|
||||
// if the fee is 80 then the recipient will receive 1000 and our change will be
|
||||
// 8,920
|
||||
let change = total - amount - fee;
|
||||
|
||||
// build inputs using the appropriate derived key_ids
|
||||
for coin in coins {
|
||||
let key_id = keychain
|
||||
.derive_key_id(coin.n_child)
|
||||
.context(ErrorKind::Keychain)?;
|
||||
if coin.is_coinbase {
|
||||
let block = coin.block.clone();
|
||||
let merkle_proof = coin.merkle_proof.clone();
|
||||
let merkle_proof = merkle_proof.unwrap().merkle_proof();
|
||||
|
||||
parts.push(build::coinbase_input(
|
||||
coin.value,
|
||||
block.unwrap().hash(),
|
||||
merkle_proof,
|
||||
key_id,
|
||||
));
|
||||
} else {
|
||||
parts.push(build::input(coin.value, key_id));
|
||||
}
|
||||
}
|
||||
let change_key;
|
||||
if change != 0 {
|
||||
// track the output representing our change
|
||||
change_key = WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
|
||||
let root_key_id = keychain.root_key_id();
|
||||
let change_derivation = wallet_data.next_child(root_key_id.clone());
|
||||
let change_key = keychain.derive_key_id(change_derivation).unwrap();
|
||||
|
||||
wallet_data.add_output(OutputData {
|
||||
root_key_id: root_key_id.clone(),
|
||||
key_id: change_key.clone(),
|
||||
n_child: change_derivation,
|
||||
value: change as u64,
|
||||
status: OutputStatus::Unconfirmed,
|
||||
height: 0,
|
||||
lock_height: 0,
|
||||
is_coinbase: false,
|
||||
block: None,
|
||||
merkle_proof: None,
|
||||
});
|
||||
|
||||
Some(change_key)
|
||||
})?;
|
||||
|
||||
parts.push(build::output(change, change_key.clone().unwrap()));
|
||||
} else {
|
||||
change_key = None
|
||||
}
|
||||
|
||||
Ok((parts, change_key))
|
||||
}
|
|
@ -49,17 +49,18 @@ extern crate grin_core as core;
|
|||
extern crate grin_keychain as keychain;
|
||||
extern crate grin_util as util;
|
||||
|
||||
mod checker;
|
||||
pub mod checker;
|
||||
mod handlers;
|
||||
mod outputs;
|
||||
mod info;
|
||||
mod receiver;
|
||||
pub mod receiver;
|
||||
mod sender;
|
||||
mod types;
|
||||
pub mod types;
|
||||
mod restore;
|
||||
pub mod client;
|
||||
pub mod server;
|
||||
pub mod libwallet;
|
||||
pub mod grinwallet;
|
||||
|
||||
pub use outputs::show_outputs;
|
||||
pub use info::{retrieve_info, show_info};
|
||||
|
|
|
@ -35,9 +35,12 @@ pub struct Context {
|
|||
/// Secret nonce (of which public is shared)
|
||||
/// (basically a SecretKey)
|
||||
pub sec_nonce: SecretKey,
|
||||
/// If I'm the recipient, store my outputs between invocations (that I need
|
||||
/// to sum)
|
||||
/// If I'm the sender, store change key
|
||||
pub change_key: Option<Identifier>,
|
||||
/// store my outputs between invocations
|
||||
pub output_ids: Vec<Identifier>,
|
||||
/// store the calculated fee
|
||||
pub fee: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -70,7 +73,9 @@ impl ContextManager {
|
|||
sec_key: sec_key,
|
||||
transaction_id: transaction_id.clone(),
|
||||
sec_nonce: aggsig::export_secnonce_single(secp).unwrap(),
|
||||
change_key: None,
|
||||
output_ids: vec![],
|
||||
fee: 0,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -27,3 +27,4 @@ pub mod blind;
|
|||
pub mod proof;
|
||||
pub mod reward;
|
||||
pub mod build;
|
||||
pub mod transaction;
|
||||
|
|
472
wallet/src/libwallet/transaction.rs
Normal file
472
wallet/src/libwallet/transaction.rs
Normal file
|
@ -0,0 +1,472 @@
|
|||
// 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.
|
||||
|
||||
//! Functions for building partial transactions to be passed
|
||||
//! around during an interactive wallet exchange
|
||||
use rand::thread_rng;
|
||||
use uuid::Uuid;
|
||||
|
||||
use core::core::{amount_to_hr_string, Committed, Transaction};
|
||||
use libwallet::{aggsig, build};
|
||||
use keychain::{BlindSum, BlindingFactor, Identifier, Keychain};
|
||||
use types::*; // TODO: Remove this?
|
||||
use util::{secp, LOGGER};
|
||||
use util::secp::key::{PublicKey, SecretKey};
|
||||
use util::secp::Signature;
|
||||
use failure::ResultExt;
|
||||
|
||||
// TODO: None of these functions should care about the wallet implementation,
|
||||
|
||||
/// Initiate a transaction for the aggsig exchange
|
||||
/// with the given transaction data
|
||||
pub fn sender_initiation(
|
||||
keychain: &Keychain,
|
||||
tx_id: &Uuid,
|
||||
context_manager: &mut aggsig::ContextManager,
|
||||
current_height: u64,
|
||||
//TODO: Make this nicer, remove wallet-specific OutputData type
|
||||
tx_data: (
|
||||
Transaction,
|
||||
BlindingFactor,
|
||||
Vec<OutputData>,
|
||||
Option<Identifier>,
|
||||
u64,
|
||||
),
|
||||
) -> Result<PartialTx, Error> {
|
||||
let lock_height = current_height;
|
||||
|
||||
let (tx, blind, coins, _change_key, amount_with_fee) = tx_data;
|
||||
|
||||
// TODO - wrap this up in build_send_tx or even the build() call?
|
||||
// Generate a random kernel offset here
|
||||
// and subtract it from the blind_sum so we create
|
||||
// the aggsig context with the "split" key
|
||||
let kernel_offset =
|
||||
BlindingFactor::from_secret_key(SecretKey::new(&keychain.secp(), &mut thread_rng()));
|
||||
|
||||
let blind_offset = keychain
|
||||
.blind_sum(&BlindSum::new()
|
||||
.add_blinding_factor(blind)
|
||||
.sub_blinding_factor(kernel_offset))
|
||||
.unwrap();
|
||||
|
||||
//
|
||||
// -Sender picks random blinding factors for all outputs it participates in,
|
||||
// computes total blinding excess xS -Sender picks random nonce kS
|
||||
// -Sender posts inputs, outputs, Message M=fee, xS * G and kS * G to Receiver
|
||||
//
|
||||
let skey = blind_offset
|
||||
.secret_key(&keychain.secp())
|
||||
.context(ErrorKind::Keychain)?;
|
||||
|
||||
// Create a new aggsig context
|
||||
let mut context = context_manager.create_context(keychain.secp(), &tx_id, skey);
|
||||
for coin in coins {
|
||||
context.add_output(&coin.key_id);
|
||||
}
|
||||
let partial_tx = build_partial_tx(
|
||||
&context,
|
||||
keychain,
|
||||
amount_with_fee,
|
||||
lock_height,
|
||||
kernel_offset,
|
||||
None,
|
||||
tx,
|
||||
);
|
||||
context_manager.save_context(context);
|
||||
Ok(partial_tx)
|
||||
}
|
||||
|
||||
/// Receive Part 1 of interactive transactions from sender, Sender Initiation
|
||||
/// Return result of part 2, Recipient Initation, to sender
|
||||
/// -Receiver receives inputs, outputs xS * G and kS * G
|
||||
/// -Receiver picks random blinding factors for all outputs being received,
|
||||
/// computes total blinding
|
||||
/// excess xR
|
||||
/// -Receiver picks random nonce kR
|
||||
/// -Receiver computes Schnorr challenge e = H(M | kR * G + kS * G)
|
||||
/// -Receiver computes their part of signature, sR = kR + e * xR
|
||||
/// -Receiver responds with sR, blinding excess xR * G, public nonce kR * G
|
||||
|
||||
pub fn recipient_initiation(
|
||||
keychain: &Keychain,
|
||||
context_manager: &mut aggsig::ContextManager,
|
||||
partial_tx: &PartialTx,
|
||||
output_key_id: &Identifier,
|
||||
) -> Result<PartialTx, Error> {
|
||||
let (amount, _lock_height, _sender_pub_blinding, sender_pub_nonce, kernel_offset, _sig, tx) =
|
||||
read_partial_tx(keychain, partial_tx)?;
|
||||
|
||||
// double check the fee amount included in the partial tx
|
||||
// we don't necessarily want to just trust the sender
|
||||
// we could just overwrite the fee here (but we won't) due to the sig
|
||||
let fee = tx_fee(
|
||||
tx.inputs.len(),
|
||||
tx.outputs.len() + 1,
|
||||
tx.input_proofs_count(),
|
||||
None,
|
||||
);
|
||||
if fee > tx.fee() {
|
||||
return Err(ErrorKind::FeeDispute {
|
||||
sender_fee: tx.fee(),
|
||||
recipient_fee: fee,
|
||||
})?;
|
||||
}
|
||||
|
||||
if fee > amount {
|
||||
info!(
|
||||
LOGGER,
|
||||
"Rejected the transfer because transaction fee ({}) exceeds received amount ({}).",
|
||||
amount_to_hr_string(fee),
|
||||
amount_to_hr_string(amount)
|
||||
);
|
||||
return Err(ErrorKind::FeeExceedsAmount {
|
||||
sender_amount: amount,
|
||||
recipient_fee: fee,
|
||||
})?;
|
||||
}
|
||||
|
||||
let out_amount = amount - tx.fee();
|
||||
|
||||
// First step is just to get the excess sum of the outputs we're participating
|
||||
// in Output and key needs to be stored until transaction finalisation time,
|
||||
// somehow
|
||||
// Still handy for getting the blinding sum
|
||||
let (_, blind_sum) = build::partial_transaction(
|
||||
vec![build::output(out_amount, output_key_id.clone())],
|
||||
keychain,
|
||||
).context(ErrorKind::Keychain)?;
|
||||
|
||||
// Create a new aggsig context
|
||||
// this will create a new blinding sum and nonce, and store them
|
||||
let blind = blind_sum
|
||||
.secret_key(&keychain.secp())
|
||||
.context(ErrorKind::Keychain)?;
|
||||
debug!(LOGGER, "Creating new aggsig context");
|
||||
let mut context = context_manager.create_context(keychain.secp(), &partial_tx.id, blind);
|
||||
context.add_output(output_key_id);
|
||||
context.fee = tx.fee();
|
||||
|
||||
let sig_part = context
|
||||
.calculate_partial_sig(
|
||||
keychain.secp(),
|
||||
&sender_pub_nonce,
|
||||
tx.fee(),
|
||||
tx.lock_height(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Build the response, which should contain sR, blinding excess xR * G, public
|
||||
// nonce kR * G
|
||||
let mut partial_tx = build_partial_tx(
|
||||
&context,
|
||||
keychain,
|
||||
amount,
|
||||
partial_tx.lock_height,
|
||||
kernel_offset,
|
||||
Some(sig_part),
|
||||
tx,
|
||||
);
|
||||
partial_tx.phase = PartialTxPhase::ReceiverInitiation;
|
||||
|
||||
context_manager.save_context(context);
|
||||
|
||||
Ok(partial_tx)
|
||||
}
|
||||
|
||||
/// -Sender receives xR * G, kR * G, sR
|
||||
/// -Sender computes Schnorr challenge e = H(M | kR * G + kS * G)
|
||||
/// -Sender verifies receivers sig, by verifying that kR * G + e * xR * G =
|
||||
/// sR * G·
|
||||
/// -Sender computes their part of signature, sS = kS + e * xS
|
||||
|
||||
pub fn sender_confirmation(
|
||||
keychain: &Keychain,
|
||||
context_manager: &mut aggsig::ContextManager,
|
||||
partial_tx: PartialTx,
|
||||
) -> Result<PartialTx, Error> {
|
||||
let context = context_manager.get_context(&partial_tx.id);
|
||||
|
||||
let (amount, lock_height, recp_pub_blinding, recp_pub_nonce, kernel_offset, sig, tx) =
|
||||
read_partial_tx(keychain, &partial_tx)?;
|
||||
|
||||
let res = context.verify_partial_sig(
|
||||
&keychain.secp(),
|
||||
&sig.unwrap(),
|
||||
&recp_pub_nonce,
|
||||
&recp_pub_blinding,
|
||||
tx.fee(),
|
||||
lock_height,
|
||||
);
|
||||
if !res {
|
||||
error!(LOGGER, "Partial Sig from recipient invalid.");
|
||||
return Err(ErrorKind::Signature("Partial Sig from recipient invalid."))?;
|
||||
}
|
||||
|
||||
let sig_part = context
|
||||
.calculate_partial_sig(
|
||||
&keychain.secp(),
|
||||
&recp_pub_nonce,
|
||||
tx.fee(),
|
||||
tx.lock_height(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Build the next stage, containing sS (and our pubkeys again, for the
|
||||
// recipient's convenience) offset has not been modified during tx building,
|
||||
// so pass it back in
|
||||
let mut partial_tx = build_partial_tx(
|
||||
&context,
|
||||
keychain,
|
||||
amount,
|
||||
lock_height,
|
||||
kernel_offset,
|
||||
Some(sig_part),
|
||||
tx,
|
||||
);
|
||||
partial_tx.phase = PartialTxPhase::SenderConfirmation;
|
||||
context_manager.save_context(context);
|
||||
Ok(partial_tx)
|
||||
}
|
||||
|
||||
/// Creates the final signature, callable by either the sender or recipient
|
||||
/// (after phase 3: sender confirmation)
|
||||
///
|
||||
/// TODO: takes a partial Tx that just contains the other party's public
|
||||
/// info at present, but this should be changed to something more appropriate
|
||||
pub fn finalize_transaction(
|
||||
keychain: &Keychain,
|
||||
context_manager: &mut aggsig::ContextManager,
|
||||
partial_tx: &PartialTx,
|
||||
other_partial_tx: &PartialTx,
|
||||
output_key_id: &Identifier,
|
||||
output_key_derivation: u32,
|
||||
) -> Result<Transaction, Error> {
|
||||
let (
|
||||
_amount,
|
||||
_lock_height,
|
||||
other_pub_blinding,
|
||||
other_pub_nonce,
|
||||
kernel_offset,
|
||||
other_sig_part,
|
||||
tx,
|
||||
) = read_partial_tx(keychain, other_partial_tx)?;
|
||||
let final_sig = create_final_signature(
|
||||
keychain,
|
||||
context_manager,
|
||||
partial_tx,
|
||||
&other_pub_blinding,
|
||||
&other_pub_nonce,
|
||||
&other_sig_part.unwrap(),
|
||||
)?;
|
||||
|
||||
build_final_transaction(
|
||||
keychain,
|
||||
partial_tx.amount,
|
||||
kernel_offset,
|
||||
&final_sig,
|
||||
tx.clone(),
|
||||
output_key_id,
|
||||
output_key_derivation,
|
||||
)
|
||||
}
|
||||
|
||||
/// This should be callable by either the sender or receiver
|
||||
/// once phase 3 is done
|
||||
///
|
||||
/// Receive Part 3 of interactive transactions from sender, Sender Confirmation
|
||||
/// Return Ok/Error
|
||||
/// -Receiver receives sS
|
||||
/// -Receiver verifies sender's sig, by verifying that
|
||||
/// kS * G + e *xS * G = sS* G
|
||||
/// -Receiver calculates final sig as s=(sS+sR, kS * G+kR * G)
|
||||
/// -Receiver puts into TX kernel:
|
||||
///
|
||||
/// Signature S
|
||||
/// pubkey xR * G+xS * G
|
||||
/// fee (= M)
|
||||
///
|
||||
/// Returns completed transaction ready for posting to the chain
|
||||
|
||||
fn create_final_signature(
|
||||
keychain: &Keychain,
|
||||
context_manager: &mut aggsig::ContextManager,
|
||||
partial_tx: &PartialTx,
|
||||
other_pub_blinding: &PublicKey,
|
||||
other_pub_nonce: &PublicKey,
|
||||
other_sig_part: &Signature,
|
||||
) -> Result<Signature, Error> {
|
||||
let (_amount, _lock_height, _, _, _kernel_offset, _, tx) =
|
||||
read_partial_tx(keychain, partial_tx)?;
|
||||
let context = context_manager.get_context(&partial_tx.id);
|
||||
let res = context.verify_partial_sig(
|
||||
&keychain.secp(),
|
||||
&other_sig_part,
|
||||
&other_pub_nonce,
|
||||
&other_pub_blinding,
|
||||
tx.fee(),
|
||||
tx.lock_height(),
|
||||
);
|
||||
|
||||
if !res {
|
||||
error!(LOGGER, "Partial Sig from other party invalid.");
|
||||
return Err(ErrorKind::Signature(
|
||||
"Partial Sig from other party invalid.",
|
||||
))?;
|
||||
}
|
||||
|
||||
// Just calculate our sig part again instead of storing
|
||||
let our_sig_part = context
|
||||
.calculate_partial_sig(
|
||||
&keychain.secp(),
|
||||
&other_pub_nonce,
|
||||
tx.fee(),
|
||||
tx.lock_height(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// And the final signature
|
||||
let final_sig = context
|
||||
.calculate_final_sig(
|
||||
&keychain.secp(),
|
||||
&other_sig_part,
|
||||
&our_sig_part,
|
||||
&other_pub_nonce,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Calculate the final public key (for our own sanity check)
|
||||
let final_pubkey = context
|
||||
.calculate_final_pubkey(&keychain.secp(), &other_pub_blinding)
|
||||
.unwrap();
|
||||
|
||||
// Check our final sig verifies
|
||||
let res = context.verify_final_sig_build_msg(
|
||||
&keychain.secp(),
|
||||
&final_sig,
|
||||
&final_pubkey,
|
||||
tx.fee(),
|
||||
tx.lock_height(),
|
||||
);
|
||||
|
||||
if !res {
|
||||
error!(LOGGER, "Final aggregated signature invalid.");
|
||||
return Err(ErrorKind::Signature("Final aggregated signature invalid."))?;
|
||||
}
|
||||
|
||||
Ok(final_sig)
|
||||
}
|
||||
|
||||
/// builds a final transaction after the aggregated sig exchange
|
||||
fn build_final_transaction(
|
||||
keychain: &Keychain,
|
||||
amount: u64,
|
||||
kernel_offset: BlindingFactor,
|
||||
excess_sig: &secp::Signature,
|
||||
tx: Transaction,
|
||||
output_key_id: &Identifier,
|
||||
output_key_derivation: u32,
|
||||
) -> Result<Transaction, Error> {
|
||||
let root_key_id = keychain.root_key_id();
|
||||
|
||||
// double check the fee amount included in the partial tx
|
||||
// we don't necessarily want to just trust the sender
|
||||
// we could just overwrite the fee here (but we won't) due to the ecdsa sig
|
||||
let fee = tx_fee(
|
||||
tx.inputs.len(),
|
||||
tx.outputs.len() + 1,
|
||||
tx.input_proofs_count(),
|
||||
None,
|
||||
);
|
||||
if fee > tx.fee() {
|
||||
return Err(ErrorKind::FeeDispute {
|
||||
sender_fee: tx.fee(),
|
||||
recipient_fee: fee,
|
||||
})?;
|
||||
}
|
||||
|
||||
if fee > amount {
|
||||
info!(
|
||||
LOGGER,
|
||||
"Rejected the transfer because transaction fee ({}) exceeds received amount ({}).",
|
||||
amount_to_hr_string(fee),
|
||||
amount_to_hr_string(amount)
|
||||
);
|
||||
return Err(ErrorKind::FeeExceedsAmount {
|
||||
sender_amount: amount,
|
||||
recipient_fee: fee,
|
||||
})?;
|
||||
}
|
||||
|
||||
let out_amount = amount - tx.fee();
|
||||
|
||||
// Build final transaction, the sum of which should
|
||||
// be the same as the exchanged excess values
|
||||
let mut final_tx = build::transaction(
|
||||
vec![
|
||||
build::initial_tx(tx),
|
||||
build::output(out_amount, output_key_id.clone()),
|
||||
build::with_offset(kernel_offset),
|
||||
],
|
||||
keychain,
|
||||
).context(ErrorKind::Keychain)?;
|
||||
|
||||
// build the final excess based on final tx and offset
|
||||
let final_excess = {
|
||||
// TODO - do we need to verify rangeproofs here?
|
||||
for x in &final_tx.outputs {
|
||||
x.verify_proof().context(ErrorKind::Transaction)?;
|
||||
}
|
||||
|
||||
// sum the input/output commitments on the final tx
|
||||
let overage = final_tx.fee() as i64;
|
||||
let tx_excess = final_tx
|
||||
.sum_commitments(overage, None)
|
||||
.context(ErrorKind::Transaction)?;
|
||||
|
||||
// subtract the kernel_excess (built from kernel_offset)
|
||||
let offset_excess = keychain
|
||||
.secp()
|
||||
.commit(0, kernel_offset.secret_key(&keychain.secp()).unwrap())
|
||||
.unwrap();
|
||||
keychain
|
||||
.secp()
|
||||
.commit_sum(vec![tx_excess], vec![offset_excess])
|
||||
.context(ErrorKind::Transaction)?
|
||||
};
|
||||
|
||||
// update the tx kernel to reflect the offset excess and sig
|
||||
assert_eq!(final_tx.kernels.len(), 1);
|
||||
final_tx.kernels[0].excess = final_excess.clone();
|
||||
final_tx.kernels[0].excess_sig = excess_sig.clone();
|
||||
|
||||
// confirm the kernel verifies successfully before proceeding
|
||||
debug!(LOGGER, "Validating final transaction");
|
||||
final_tx.kernels[0]
|
||||
.verify()
|
||||
.context(ErrorKind::Transaction)?;
|
||||
|
||||
// confirm the overall transaction is valid (including the updated kernel)
|
||||
let _ = final_tx.validate().context(ErrorKind::Transaction)?;
|
||||
|
||||
debug!(
|
||||
LOGGER,
|
||||
"Finalized transaction and built output - {:?}, {:?}, {}",
|
||||
root_key_id.clone(),
|
||||
output_key_id.clone(),
|
||||
output_key_derivation,
|
||||
);
|
||||
|
||||
Ok(final_tx)
|
||||
}
|
|
@ -25,14 +25,15 @@ use std::sync::{Arc, RwLock};
|
|||
|
||||
use api;
|
||||
use core::consensus::reward;
|
||||
use core::core::{amount_to_hr_string, Committed, Output, Transaction, TxKernel};
|
||||
use libwallet::{aggsig, build, reward};
|
||||
use core::core::{Output, Transaction, TxKernel};
|
||||
use libwallet::{aggsig, reward, transaction};
|
||||
use grinwallet::keys;
|
||||
use core::{global, ser};
|
||||
use failure::{Fail, ResultExt};
|
||||
use keychain::{BlindingFactor, Identifier, Keychain};
|
||||
use keychain::Keychain;
|
||||
use types::*;
|
||||
use urlencoded::UrlEncodedQuery;
|
||||
use util::{secp, to_hex, LOGGER};
|
||||
use util::{to_hex, LOGGER};
|
||||
|
||||
/// Dummy wrapper for the hex-encoded serialized transaction.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
|
@ -46,71 +47,64 @@ lazy_static! {
|
|||
= Arc::new(RwLock::new(aggsig::ContextManager::new()));
|
||||
}
|
||||
|
||||
/// Receive Part 1 of interactive transactions from sender, Sender Initiation
|
||||
/// Return result of part 2, Recipient Initation, to sender
|
||||
/// -Receiver receives inputs, outputs xS * G and kS * G
|
||||
/// -Receiver picks random blinding factors for all outputs being received,
|
||||
/// computes total blinding
|
||||
/// excess xR
|
||||
/// -Receiver picks random nonce kR
|
||||
/// -Receiver computes Schnorr challenge e = H(M | kR * G + kS * G)
|
||||
/// -Receiver computes their part of signature, sR = kR + e * xR
|
||||
/// -Receiver responds with sR, blinding excess xR * G, public nonce kR * G
|
||||
|
||||
fn handle_sender_initiation(
|
||||
config: &WalletConfig,
|
||||
context_manager: &mut aggsig::ContextManager,
|
||||
keychain: &Keychain,
|
||||
partial_tx: &PartialTx,
|
||||
) -> Result<PartialTx, Error> {
|
||||
let (amount, _sender_pub_blinding, sender_pub_nonce, kernel_offset, _sig, tx) =
|
||||
read_partial_tx(keychain, partial_tx)?;
|
||||
// Create a potential output for this transaction
|
||||
let (key_id, derivation) = WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
|
||||
keys::next_available_key(&wallet_data, keychain)
|
||||
})?;
|
||||
|
||||
let partial_tx =
|
||||
transaction::recipient_initiation(keychain, context_manager, partial_tx, &key_id)?;
|
||||
let mut context = context_manager.get_context(&partial_tx.id);
|
||||
context.add_output(&key_id);
|
||||
|
||||
// Add the output to our wallet
|
||||
let _ = WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
|
||||
wallet_data.add_output(OutputData {
|
||||
root_key_id: keychain.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,
|
||||
});
|
||||
})?;
|
||||
|
||||
context_manager.save_context(context);
|
||||
Ok(partial_tx)
|
||||
}
|
||||
|
||||
fn handle_sender_confirmation(
|
||||
config: &WalletConfig,
|
||||
context_manager: &mut aggsig::ContextManager,
|
||||
keychain: &Keychain,
|
||||
partial_tx: &PartialTx,
|
||||
fluff: bool,
|
||||
) -> Result<Transaction, Error> {
|
||||
let context = context_manager.get_context(&partial_tx.id);
|
||||
// Get output we created in earlier step
|
||||
// TODO: will just be one for now, support multiple later
|
||||
let output_vec = context.get_outputs();
|
||||
|
||||
let root_key_id = keychain.root_key_id();
|
||||
|
||||
// double check the fee amount included in the partial tx
|
||||
// we don't necessarily want to just trust the sender
|
||||
// we could just overwrite the fee here (but we won't) due to the ecdsa sig
|
||||
let fee = tx_fee(
|
||||
tx.inputs.len(),
|
||||
tx.outputs.len() + 1,
|
||||
tx.input_proofs_count(),
|
||||
None,
|
||||
);
|
||||
if fee > tx.fee() {
|
||||
return Err(ErrorKind::FeeDispute {
|
||||
sender_fee: tx.fee(),
|
||||
recipient_fee: fee,
|
||||
})?;
|
||||
}
|
||||
|
||||
if fee > amount {
|
||||
info!(
|
||||
LOGGER,
|
||||
"Rejected the transfer because transaction fee ({}) exceeds received amount ({}).",
|
||||
amount_to_hr_string(fee),
|
||||
amount_to_hr_string(amount)
|
||||
);
|
||||
return Err(ErrorKind::FeeExceedsAmount {
|
||||
sender_amount: amount,
|
||||
recipient_fee: fee,
|
||||
})?;
|
||||
}
|
||||
|
||||
let out_amount = amount - tx.fee();
|
||||
|
||||
// First step is just to get the excess sum of the outputs we're participating
|
||||
// in Output and key needs to be stored until transaction finalisation time,
|
||||
// somehow
|
||||
|
||||
let key_id = WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
|
||||
let (key_id, derivation) = next_available_key(&wallet_data, keychain);
|
||||
// operate within a lock on wallet data
|
||||
let (key_id, derivation) = WalletData::with_wallet(&config.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: out_amount,
|
||||
value: partial_tx.amount - context.fee,
|
||||
status: OutputStatus::Unconfirmed,
|
||||
height: 0,
|
||||
lock_height: 0,
|
||||
|
@ -119,135 +113,17 @@ fn handle_sender_initiation(
|
|||
merkle_proof: None,
|
||||
});
|
||||
|
||||
key_id
|
||||
(key_id, derivation)
|
||||
})?;
|
||||
|
||||
// Still handy for getting the blinding sum
|
||||
let (_, blind_sum) =
|
||||
build::partial_transaction(vec![build::output(out_amount, key_id.clone())], keychain)
|
||||
.context(ErrorKind::Keychain)?;
|
||||
|
||||
warn!(LOGGER, "Creating new aggsig context");
|
||||
// Create a new aggsig context
|
||||
// this will create a new blinding sum and nonce, and store them
|
||||
let blind = blind_sum
|
||||
.secret_key(&keychain.secp())
|
||||
.context(ErrorKind::Keychain)?;
|
||||
let mut context = context_manager.create_context(keychain.secp(), &partial_tx.id, blind);
|
||||
|
||||
context.add_output(&key_id);
|
||||
|
||||
let sig_part = context
|
||||
.calculate_partial_sig(
|
||||
keychain.secp(),
|
||||
&sender_pub_nonce,
|
||||
tx.fee(),
|
||||
tx.lock_height(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Build the response, which should contain sR, blinding excess xR * G, public
|
||||
// nonce kR * G
|
||||
let mut partial_tx = build_partial_tx(
|
||||
&context,
|
||||
// In this case partial_tx contains other party's pubkey info
|
||||
let final_tx = transaction::finalize_transaction(
|
||||
keychain,
|
||||
amount,
|
||||
kernel_offset,
|
||||
Some(sig_part),
|
||||
tx,
|
||||
);
|
||||
partial_tx.phase = PartialTxPhase::ReceiverInitiation;
|
||||
|
||||
context_manager.save_context(context);
|
||||
|
||||
Ok(partial_tx)
|
||||
}
|
||||
|
||||
/// Receive Part 3 of interactive transactions from sender, Sender Confirmation
|
||||
/// Return Ok/Error
|
||||
/// -Receiver receives sS
|
||||
/// -Receiver verifies sender's sig, by verifying that
|
||||
/// kS * G + e *xS * G = sS* G
|
||||
/// -Receiver calculates final sig as s=(sS+sR, kS * G+kR * G)
|
||||
/// -Receiver puts into TX kernel:
|
||||
///
|
||||
/// Signature S
|
||||
/// pubkey xR * G+xS * G
|
||||
/// fee (= M)
|
||||
/// -Receiver sends completed TX to mempool. responds OK to sender
|
||||
|
||||
fn handle_sender_confirmation(
|
||||
config: &WalletConfig,
|
||||
context_manager: &mut aggsig::ContextManager,
|
||||
keychain: &Keychain,
|
||||
partial_tx: &PartialTx,
|
||||
fluff: bool,
|
||||
) -> Result<PartialTx, Error> {
|
||||
let (amount, sender_pub_blinding, sender_pub_nonce, kernel_offset, sender_sig_part, tx) =
|
||||
read_partial_tx(keychain, partial_tx)?;
|
||||
let mut context = context_manager.get_context(&partial_tx.id);
|
||||
let sender_sig_part = sender_sig_part.unwrap();
|
||||
let res = context.verify_partial_sig(
|
||||
&keychain.secp(),
|
||||
&sender_sig_part,
|
||||
&sender_pub_nonce,
|
||||
&sender_pub_blinding,
|
||||
tx.fee(),
|
||||
tx.lock_height(),
|
||||
);
|
||||
|
||||
if !res {
|
||||
error!(LOGGER, "Partial Sig from sender invalid.");
|
||||
return Err(ErrorKind::Signature("Partial Sig from sender invalid."))?;
|
||||
}
|
||||
|
||||
// Just calculate our sig part again instead of storing
|
||||
let our_sig_part = context
|
||||
.calculate_partial_sig(
|
||||
&keychain.secp(),
|
||||
&sender_pub_nonce,
|
||||
tx.fee(),
|
||||
tx.lock_height(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// And the final signature
|
||||
let final_sig = context
|
||||
.calculate_final_sig(
|
||||
&keychain.secp(),
|
||||
&sender_sig_part,
|
||||
&our_sig_part,
|
||||
&sender_pub_nonce,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Calculate the final public key (for our own sanity check)
|
||||
let final_pubkey = context
|
||||
.calculate_final_pubkey(&keychain.secp(), &sender_pub_blinding)
|
||||
.unwrap();
|
||||
|
||||
// Check our final sig verifies
|
||||
let res = context.verify_final_sig_build_msg(
|
||||
&keychain.secp(),
|
||||
&final_sig,
|
||||
&final_pubkey,
|
||||
tx.fee(),
|
||||
tx.lock_height(),
|
||||
);
|
||||
|
||||
if !res {
|
||||
error!(LOGGER, "Final aggregated signature invalid.");
|
||||
return Err(ErrorKind::Signature("Final aggregated signature invalid."))?;
|
||||
}
|
||||
|
||||
let final_tx = build_final_transaction(
|
||||
&mut context,
|
||||
config,
|
||||
keychain,
|
||||
amount,
|
||||
kernel_offset,
|
||||
&final_sig,
|
||||
tx.clone(),
|
||||
context_manager,
|
||||
partial_tx,
|
||||
partial_tx,
|
||||
&key_id,
|
||||
derivation,
|
||||
)?;
|
||||
|
||||
let tx_hex = to_hex(ser::ser_vec(&final_tx).unwrap());
|
||||
|
@ -263,20 +139,7 @@ fn handle_sender_confirmation(
|
|||
}
|
||||
api::client::post(url.as_str(), &TxWrapper { tx_hex: tx_hex }).context(ErrorKind::Node)?;
|
||||
|
||||
// Return what we've actually posted
|
||||
// TODO - why build_partial_tx here? Just a naming issue?
|
||||
let mut partial_tx = build_partial_tx(
|
||||
&context,
|
||||
keychain,
|
||||
amount,
|
||||
kernel_offset,
|
||||
Some(final_sig),
|
||||
tx,
|
||||
);
|
||||
|
||||
context_manager.save_context(context);
|
||||
partial_tx.phase = PartialTxPhase::ReceiverConfirmation;
|
||||
Ok(partial_tx)
|
||||
Ok(final_tx)
|
||||
}
|
||||
|
||||
/// Component used to receive coins, implements all the receiving end of the
|
||||
|
@ -345,23 +208,7 @@ impl Handler for WalletReceiver {
|
|||
}
|
||||
}
|
||||
|
||||
fn retrieve_existing_key(wallet_data: &WalletData, key_id: Identifier) -> (Identifier, u32) {
|
||||
if let Some(existing) = wallet_data.get_output(&key_id) {
|
||||
let key_id = existing.key_id.clone();
|
||||
let derivation = existing.n_child;
|
||||
(key_id, derivation)
|
||||
} else {
|
||||
panic!("should never happen");
|
||||
}
|
||||
}
|
||||
|
||||
fn next_available_key(wallet_data: &WalletData, keychain: &Keychain) -> (Identifier, u32) {
|
||||
let root_key_id = keychain.root_key_id();
|
||||
let derivation = wallet_data.next_child(root_key_id.clone());
|
||||
let key_id = keychain.derive_key_id(derivation).unwrap();
|
||||
(key_id, derivation)
|
||||
}
|
||||
|
||||
//TODO: Split up the output creation and the wallet insertion
|
||||
/// Build a coinbase output and the corresponding kernel
|
||||
pub fn receive_coinbase(
|
||||
config: &WalletConfig,
|
||||
|
@ -377,8 +224,8 @@ pub fn receive_coinbase(
|
|||
let (key_id, derivation) = WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
|
||||
let key_id = block_fees.key_id();
|
||||
let (key_id, derivation) = match key_id {
|
||||
Some(key_id) => retrieve_existing_key(&wallet_data, key_id),
|
||||
None => next_available_key(&wallet_data, keychain),
|
||||
Some(key_id) => keys::retrieve_existing_key(&wallet_data, key_id),
|
||||
None => keys::next_available_key(&wallet_data, keychain),
|
||||
};
|
||||
|
||||
// track the new output and return the stuff needed for reward
|
||||
|
@ -415,129 +262,3 @@ pub fn receive_coinbase(
|
|||
/* .context(ErrorKind::Keychain)?; */
|
||||
Ok((out, kern, block_fees))
|
||||
}
|
||||
|
||||
/// builds a final transaction after the aggregated sig exchange
|
||||
fn build_final_transaction(
|
||||
context: &mut aggsig::Context,
|
||||
config: &WalletConfig,
|
||||
keychain: &Keychain,
|
||||
amount: u64,
|
||||
kernel_offset: BlindingFactor,
|
||||
excess_sig: &secp::Signature,
|
||||
tx: Transaction,
|
||||
) -> Result<Transaction, Error> {
|
||||
let root_key_id = keychain.root_key_id();
|
||||
|
||||
// double check the fee amount included in the partial tx
|
||||
// we don't necessarily want to just trust the sender
|
||||
// we could just overwrite the fee here (but we won't) due to the ecdsa sig
|
||||
let fee = tx_fee(
|
||||
tx.inputs.len(),
|
||||
tx.outputs.len() + 1,
|
||||
tx.input_proofs_count(),
|
||||
None,
|
||||
);
|
||||
if fee > tx.fee() {
|
||||
return Err(ErrorKind::FeeDispute {
|
||||
sender_fee: tx.fee(),
|
||||
recipient_fee: fee,
|
||||
})?;
|
||||
}
|
||||
|
||||
if fee > amount {
|
||||
info!(
|
||||
LOGGER,
|
||||
"Rejected the transfer because transaction fee ({}) exceeds received amount ({}).",
|
||||
amount_to_hr_string(fee),
|
||||
amount_to_hr_string(amount)
|
||||
);
|
||||
return Err(ErrorKind::FeeExceedsAmount {
|
||||
sender_amount: amount,
|
||||
recipient_fee: fee,
|
||||
})?;
|
||||
}
|
||||
|
||||
let out_amount = amount - tx.fee();
|
||||
|
||||
// Get output we created in earlier step
|
||||
// TODO: will just be one for now, support multiple later
|
||||
let output_vec = context.get_outputs();
|
||||
|
||||
// operate within a lock on wallet data
|
||||
let (key_id, derivation) = WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
|
||||
let (key_id, derivation) = 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: out_amount,
|
||||
status: OutputStatus::Unconfirmed,
|
||||
height: 0,
|
||||
lock_height: 0,
|
||||
is_coinbase: false,
|
||||
block: None,
|
||||
merkle_proof: None,
|
||||
});
|
||||
|
||||
(key_id, derivation)
|
||||
})?;
|
||||
|
||||
// Build final transaction, the sum of which should
|
||||
// be the same as the exchanged excess values
|
||||
let mut final_tx = build::transaction(
|
||||
vec![
|
||||
build::initial_tx(tx),
|
||||
build::output(out_amount, key_id.clone()),
|
||||
build::with_offset(kernel_offset),
|
||||
],
|
||||
keychain,
|
||||
).context(ErrorKind::Keychain)?;
|
||||
|
||||
// build the final excess based on final tx and offset
|
||||
let final_excess = {
|
||||
// TODO - do we need to verify rangeproofs here?
|
||||
for x in &final_tx.outputs {
|
||||
x.verify_proof().context(ErrorKind::Transaction)?;
|
||||
}
|
||||
|
||||
// sum the input/output commitments on the final tx
|
||||
let overage = final_tx.fee() as i64;
|
||||
let tx_excess = final_tx
|
||||
.sum_commitments(overage, None)
|
||||
.context(ErrorKind::Transaction)?;
|
||||
|
||||
// subtract the kernel_excess (built from kernel_offset)
|
||||
let offset_excess = keychain
|
||||
.secp()
|
||||
.commit(0, kernel_offset.secret_key(&keychain.secp()).unwrap())
|
||||
.unwrap();
|
||||
keychain
|
||||
.secp()
|
||||
.commit_sum(vec![tx_excess], vec![offset_excess])
|
||||
.context(ErrorKind::Transaction)?
|
||||
};
|
||||
|
||||
// update the tx kernel to reflect the offset excess and sig
|
||||
assert_eq!(final_tx.kernels.len(), 1);
|
||||
final_tx.kernels[0].excess = final_excess.clone();
|
||||
final_tx.kernels[0].excess_sig = excess_sig.clone();
|
||||
|
||||
// confirm the kernel verifies successfully before proceeding
|
||||
final_tx.kernels[0]
|
||||
.verify()
|
||||
.context(ErrorKind::Transaction)?;
|
||||
|
||||
// confirm the overall transaction is valid (including the updated kernel)
|
||||
let _ = final_tx.validate().context(ErrorKind::Transaction)?;
|
||||
|
||||
debug!(
|
||||
LOGGER,
|
||||
"Finalized transaction and built output - {:?}, {:?}, {}",
|
||||
root_key_id.clone(),
|
||||
key_id.clone(),
|
||||
derivation,
|
||||
);
|
||||
|
||||
Ok(final_tx)
|
||||
}
|
||||
|
|
|
@ -12,20 +12,19 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use rand::thread_rng;
|
||||
use uuid::Uuid;
|
||||
|
||||
use api;
|
||||
use client;
|
||||
use checker;
|
||||
use core::core::{amount_to_hr_string, Transaction};
|
||||
use libwallet::{aggsig, build};
|
||||
use core::core::amount_to_hr_string;
|
||||
use libwallet::{aggsig, build, transaction};
|
||||
use grinwallet::selection;
|
||||
use core::ser;
|
||||
use keychain::{BlindSum, BlindingFactor, Identifier, Keychain};
|
||||
use keychain::{Identifier, Keychain};
|
||||
use receiver::TxWrapper;
|
||||
use types::*;
|
||||
use util::LOGGER;
|
||||
use util::secp::key::SecretKey;
|
||||
use util;
|
||||
use failure::ResultExt;
|
||||
|
||||
|
@ -45,13 +44,19 @@ pub fn issue_send_tx(
|
|||
) -> Result<(), Error> {
|
||||
checker::refresh_outputs(config, keychain)?;
|
||||
|
||||
// Create a new aggsig context
|
||||
let mut context_manager = aggsig::ContextManager::new();
|
||||
let tx_id = Uuid::new_v4();
|
||||
|
||||
// Get lock height
|
||||
let chain_tip = checker::get_tip_from_node(config)?;
|
||||
let current_height = chain_tip.height;
|
||||
// ensure outputs we're selecting are up to date
|
||||
checker::refresh_outputs(config, keychain)?;
|
||||
|
||||
// proof of concept - set lock_height on the tx
|
||||
let lock_height = chain_tip.height;
|
||||
let lock_height = current_height;
|
||||
|
||||
let (tx, blind, coins, change_key, amount_with_fee) = build_send_tx(
|
||||
let tx_data = selection::build_send_tx(
|
||||
config,
|
||||
keychain,
|
||||
amount,
|
||||
|
@ -62,40 +67,22 @@ pub fn issue_send_tx(
|
|||
selection_strategy_is_use_all,
|
||||
)?;
|
||||
|
||||
// TODO - wrap this up in build_send_tx or even the build() call?
|
||||
// Generate a random kernel offset here
|
||||
// and subtract it from the blind_sum so we create
|
||||
// the aggsig context with the "split" key
|
||||
let kernel_offset =
|
||||
BlindingFactor::from_secret_key(SecretKey::new(&keychain.secp(), &mut thread_rng()));
|
||||
let partial_tx = transaction::sender_initiation(
|
||||
keychain,
|
||||
&tx_id,
|
||||
&mut context_manager,
|
||||
current_height,
|
||||
tx_data,
|
||||
)?;
|
||||
|
||||
let blind_offset = keychain
|
||||
.blind_sum(&BlindSum::new()
|
||||
.add_blinding_factor(blind)
|
||||
.sub_blinding_factor(kernel_offset))
|
||||
.unwrap();
|
||||
|
||||
//
|
||||
// -Sender picks random blinding factors for all outputs it participates in,
|
||||
// computes total blinding excess xS -Sender picks random nonce kS
|
||||
// -Sender posts inputs, outputs, Message M=fee, xS * G and kS * G to Receiver
|
||||
//
|
||||
let tx_id = Uuid::new_v4();
|
||||
let skey = blind_offset
|
||||
.secret_key(&keychain.secp())
|
||||
.context(ErrorKind::Keychain)?;
|
||||
|
||||
// Create a new aggsig context
|
||||
let mut context_manager = aggsig::ContextManager::new();
|
||||
let context = context_manager.create_context(keychain.secp(), &tx_id, skey);
|
||||
|
||||
let partial_tx = build_partial_tx(&context, keychain, amount_with_fee, kernel_offset, None, tx);
|
||||
let context = context_manager.get_context(&tx_id);
|
||||
|
||||
// Closure to acquire wallet lock and lock the coins being spent
|
||||
// so we avoid accidental double spend attempt.
|
||||
let update_wallet = || {
|
||||
WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
|
||||
for coin in coins {
|
||||
for id in context.get_outputs().clone() {
|
||||
let coin = wallet_data.get_output(&id).unwrap().clone();
|
||||
wallet_data.lock_output(&coin);
|
||||
}
|
||||
})
|
||||
|
@ -105,7 +92,7 @@ pub fn issue_send_tx(
|
|||
// failure.
|
||||
let rollback_wallet = || {
|
||||
WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
|
||||
match change_key.clone() {
|
||||
match context.change_key.clone() {
|
||||
Some(change) => {
|
||||
info!(LOGGER, "cleaning up unused change output from wallet");
|
||||
wallet_data.delete_output(&change);
|
||||
|
@ -126,7 +113,7 @@ pub fn issue_send_tx(
|
|||
|
||||
if &dest[..4] != "http" {
|
||||
WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
|
||||
match change_key.clone() {
|
||||
match context.change_key.clone() {
|
||||
Some(change) => {
|
||||
info!(LOGGER, "cleaning up unused change output from wallet");
|
||||
wallet_data.delete_output(&change);
|
||||
|
@ -163,51 +150,11 @@ pub fn issue_send_tx(
|
|||
return Err(e);
|
||||
}
|
||||
|
||||
/* -Sender receives xR * G, kR * G, sR
|
||||
* -Sender computes Schnorr challenge e = H(M | kR * G + kS * G)
|
||||
* -Sender verifies receivers sig, by verifying that kR * G + e * xR * G =
|
||||
* sR * G· -Sender computes their part of signature, sS = kS + e * xS
|
||||
* -Sender posts sS to receiver
|
||||
*/
|
||||
let (_amount, recp_pub_blinding, recp_pub_nonce, kernel_offset, sig, tx) =
|
||||
read_partial_tx(keychain, &res.unwrap())?;
|
||||
let res = context.verify_partial_sig(
|
||||
&keychain.secp(),
|
||||
&sig.unwrap(),
|
||||
&recp_pub_nonce,
|
||||
&recp_pub_blinding,
|
||||
tx.fee(),
|
||||
lock_height,
|
||||
);
|
||||
if !res {
|
||||
error!(LOGGER, "Partial Sig from recipient invalid.");
|
||||
return Err(ErrorKind::Signature("Partial Sig from recipient invalid."))?;
|
||||
}
|
||||
let partial_tx =
|
||||
transaction::sender_confirmation(keychain, &mut context_manager, res.unwrap())?;
|
||||
|
||||
let sig_part = context
|
||||
.calculate_partial_sig(
|
||||
&keychain.secp(),
|
||||
&recp_pub_nonce,
|
||||
tx.fee(),
|
||||
tx.lock_height(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Build the next stage, containing sS (and our pubkeys again, for the
|
||||
// recipient's convenience) offset has not been modified during tx building,
|
||||
// so pass it back in
|
||||
let mut partial_tx = build_partial_tx(
|
||||
&context,
|
||||
keychain,
|
||||
amount_with_fee,
|
||||
kernel_offset,
|
||||
Some(sig_part),
|
||||
tx,
|
||||
);
|
||||
partial_tx.phase = PartialTxPhase::SenderConfirmation;
|
||||
|
||||
// And send again
|
||||
let res = client::send_partial_tx(&url, &partial_tx, fluff);
|
||||
// And send again, expecting completed transaction as result this time
|
||||
let res = client::send_partial_tx_final(&url, &partial_tx, fluff);
|
||||
if let Err(e) = res {
|
||||
match e.kind() {
|
||||
ErrorKind::FeeExceedsAmount {sender_amount, recipient_fee} =>
|
||||
|
@ -224,120 +171,13 @@ pub fn issue_send_tx(
|
|||
}
|
||||
|
||||
// Not really necessary here
|
||||
context_manager.save_context(context);
|
||||
context_manager.save_context(context.clone());
|
||||
|
||||
// All good so
|
||||
update_wallet()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Builds a transaction to send to someone from the HD seed associated with the
|
||||
/// wallet and the amount to send. Handles reading through the wallet data file,
|
||||
/// selecting outputs to spend and building the change.
|
||||
fn build_send_tx(
|
||||
config: &WalletConfig,
|
||||
keychain: &Keychain,
|
||||
amount: u64,
|
||||
current_height: u64,
|
||||
minimum_confirmations: u64,
|
||||
lock_height: u64,
|
||||
max_outputs: usize,
|
||||
selection_strategy_is_use_all: bool,
|
||||
) -> Result<
|
||||
(
|
||||
Transaction,
|
||||
BlindingFactor,
|
||||
Vec<OutputData>,
|
||||
Option<Identifier>,
|
||||
u64,
|
||||
),
|
||||
Error,
|
||||
> {
|
||||
let key_id = keychain.clone().root_key_id();
|
||||
|
||||
// select some spendable coins from the wallet
|
||||
let mut coins = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
|
||||
Ok(wallet_data.select_coins(
|
||||
key_id.clone(),
|
||||
amount,
|
||||
current_height,
|
||||
minimum_confirmations,
|
||||
max_outputs,
|
||||
selection_strategy_is_use_all,
|
||||
))
|
||||
})?;
|
||||
|
||||
// Get the maximum number of outputs in the wallet
|
||||
let max_outputs = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
|
||||
Ok(wallet_data.select_coins(
|
||||
key_id.clone(),
|
||||
amount,
|
||||
current_height,
|
||||
minimum_confirmations,
|
||||
max_outputs,
|
||||
true,
|
||||
))
|
||||
})?.len();
|
||||
|
||||
// sender is responsible for setting the fee on the partial tx
|
||||
// recipient should double check the fee calculation and not blindly trust the
|
||||
// sender
|
||||
let mut fee;
|
||||
// First attempt to spend without change
|
||||
fee = tx_fee(coins.len(), 1, coins_proof_count(&coins), None);
|
||||
let mut total: u64 = coins.iter().map(|c| c.value).sum();
|
||||
let mut amount_with_fee = amount + fee;
|
||||
|
||||
if total == 0 {
|
||||
return Err(ErrorKind::NotEnoughFunds(total as u64))?;
|
||||
}
|
||||
|
||||
// Check if we need to use a change address
|
||||
if total > amount_with_fee {
|
||||
fee = tx_fee(coins.len(), 2, coins_proof_count(&coins), None);
|
||||
amount_with_fee = amount + fee;
|
||||
|
||||
// Here check if we have enough outputs for the amount including fee otherwise
|
||||
// look for other outputs and check again
|
||||
while total < amount_with_fee {
|
||||
// End the loop if we have selected all the outputs and still not enough funds
|
||||
if coins.len() == max_outputs {
|
||||
return Err(ErrorKind::NotEnoughFunds(total as u64))?;
|
||||
}
|
||||
|
||||
// select some spendable coins from the wallet
|
||||
coins = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
|
||||
Ok(wallet_data.select_coins(
|
||||
key_id.clone(),
|
||||
amount_with_fee,
|
||||
current_height,
|
||||
minimum_confirmations,
|
||||
max_outputs,
|
||||
selection_strategy_is_use_all,
|
||||
))
|
||||
})?;
|
||||
fee = tx_fee(coins.len(), 2, coins_proof_count(&coins), None);
|
||||
total = coins.iter().map(|c| c.value).sum();
|
||||
amount_with_fee = amount + fee;
|
||||
}
|
||||
}
|
||||
|
||||
// build transaction skeleton with inputs and change
|
||||
let (mut parts, change_key) = inputs_and_change(&coins, config, keychain, amount, fee)?;
|
||||
|
||||
// This is more proof of concept than anything but here we set lock_height
|
||||
// on tx being sent (based on current chain height via api).
|
||||
parts.push(build::with_lock_height(lock_height));
|
||||
|
||||
let (tx, blind) = build::partial_transaction(parts, &keychain).context(ErrorKind::Keychain)?;
|
||||
|
||||
Ok((tx, blind, coins, change_key, amount_with_fee))
|
||||
}
|
||||
|
||||
fn coins_proof_count(coins: &Vec<OutputData>) -> usize {
|
||||
coins.iter().filter(|c| c.merkle_proof.is_some()).count()
|
||||
}
|
||||
|
||||
pub fn issue_burn_tx(
|
||||
config: &WalletConfig,
|
||||
keychain: &Keychain,
|
||||
|
@ -368,8 +208,8 @@ pub fn issue_burn_tx(
|
|||
|
||||
debug!(LOGGER, "selected some coins - {}", coins.len());
|
||||
|
||||
let fee = tx_fee(coins.len(), 2, coins_proof_count(&coins), None);
|
||||
let (mut parts, _) = inputs_and_change(&coins, config, keychain, amount, fee)?;
|
||||
let fee = tx_fee(coins.len(), 2, selection::coins_proof_count(&coins), None);
|
||||
let (mut parts, _) = selection::inputs_and_change(&coins, config, keychain, amount, fee)?;
|
||||
|
||||
// add burn output and fees
|
||||
parts.push(build::output(amount - fee, Identifier::zero()));
|
||||
|
@ -385,77 +225,6 @@ pub fn issue_burn_tx(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn inputs_and_change(
|
||||
coins: &Vec<OutputData>,
|
||||
config: &WalletConfig,
|
||||
keychain: &Keychain,
|
||||
amount: u64,
|
||||
fee: u64,
|
||||
) -> Result<(Vec<Box<build::Append>>, Option<Identifier>), Error> {
|
||||
let mut parts = vec![];
|
||||
|
||||
// calculate the total across all inputs, and how much is left
|
||||
let total: u64 = coins.iter().map(|c| c.value).sum();
|
||||
|
||||
parts.push(build::with_fee(fee));
|
||||
|
||||
// if we are spending 10,000 coins to send 1,000 then our change will be 9,000
|
||||
// if the fee is 80 then the recipient will receive 1000 and our change will be
|
||||
// 8,920
|
||||
let change = total - amount - fee;
|
||||
|
||||
// build inputs using the appropriate derived key_ids
|
||||
for coin in coins {
|
||||
let key_id = keychain
|
||||
.derive_key_id(coin.n_child)
|
||||
.context(ErrorKind::Keychain)?;
|
||||
if coin.is_coinbase {
|
||||
let block = coin.block.clone();
|
||||
let merkle_proof = coin.merkle_proof.clone();
|
||||
let merkle_proof = merkle_proof.unwrap().merkle_proof();
|
||||
|
||||
parts.push(build::coinbase_input(
|
||||
coin.value,
|
||||
block.unwrap().hash(),
|
||||
merkle_proof,
|
||||
key_id,
|
||||
));
|
||||
} else {
|
||||
parts.push(build::input(coin.value, key_id));
|
||||
}
|
||||
}
|
||||
let change_key;
|
||||
if change != 0 {
|
||||
// track the output representing our change
|
||||
change_key = WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
|
||||
let root_key_id = keychain.root_key_id();
|
||||
let change_derivation = wallet_data.next_child(root_key_id.clone());
|
||||
let change_key = keychain.derive_key_id(change_derivation).unwrap();
|
||||
|
||||
wallet_data.add_output(OutputData {
|
||||
root_key_id: root_key_id.clone(),
|
||||
key_id: change_key.clone(),
|
||||
n_child: change_derivation,
|
||||
value: change as u64,
|
||||
status: OutputStatus::Unconfirmed,
|
||||
height: 0,
|
||||
lock_height: 0,
|
||||
is_coinbase: false,
|
||||
block: None,
|
||||
merkle_proof: None,
|
||||
});
|
||||
|
||||
Some(change_key)
|
||||
})?;
|
||||
|
||||
parts.push(build::output(change, change_key.clone().unwrap()));
|
||||
} else {
|
||||
change_key = None
|
||||
}
|
||||
|
||||
Ok((parts, change_key))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use libwallet::build;
|
||||
|
|
|
@ -276,7 +276,7 @@ impl<'de> serde::de::Visitor<'de> for MerkleProofWrapperVisitor {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
|
||||
pub struct BlockIdentifier(Hash);
|
||||
pub struct BlockIdentifier(pub Hash);
|
||||
|
||||
impl BlockIdentifier {
|
||||
pub fn hash(&self) -> Hash {
|
||||
|
@ -767,6 +767,7 @@ pub struct PartialTx {
|
|||
pub phase: PartialTxPhase,
|
||||
pub id: Uuid,
|
||||
pub amount: u64,
|
||||
pub lock_height: u64,
|
||||
pub public_blind_excess: String,
|
||||
pub public_nonce: String,
|
||||
pub kernel_offset: String,
|
||||
|
@ -781,6 +782,7 @@ pub fn build_partial_tx(
|
|||
context: &aggsig::Context,
|
||||
keychain: &keychain::Keychain,
|
||||
receive_amount: u64,
|
||||
lock_height: u64,
|
||||
kernel_offset: BlindingFactor,
|
||||
part_sig: Option<secp::Signature>,
|
||||
tx: Transaction,
|
||||
|
@ -798,6 +800,7 @@ pub fn build_partial_tx(
|
|||
phase: PartialTxPhase::SenderInitiation,
|
||||
id: context.transaction_id,
|
||||
amount: receive_amount,
|
||||
lock_height: lock_height,
|
||||
public_blind_excess: util::to_hex(pub_excess),
|
||||
public_nonce: util::to_hex(pub_nonce),
|
||||
kernel_offset: kernel_offset.to_hex(),
|
||||
|
@ -816,6 +819,7 @@ pub fn read_partial_tx(
|
|||
partial_tx: &PartialTx,
|
||||
) -> Result<
|
||||
(
|
||||
u64,
|
||||
u64,
|
||||
PublicKey,
|
||||
PublicKey,
|
||||
|
@ -850,7 +854,15 @@ pub fn read_partial_tx(
|
|||
let tx = ser::deserialize(&mut &tx_bin[..]).context(ErrorKind::GenericError(
|
||||
"Could not deserialize transaction, invalid format.",
|
||||
))?;
|
||||
Ok((partial_tx.amount, blinding, nonce, kernel_offset, sig, tx))
|
||||
Ok((
|
||||
partial_tx.amount,
|
||||
partial_tx.lock_height,
|
||||
blinding,
|
||||
nonce,
|
||||
kernel_offset,
|
||||
sig,
|
||||
tx,
|
||||
))
|
||||
}
|
||||
|
||||
/// Amount in request to build a coinbase output.
|
||||
|
|
197
wallet/tests/common/mod.rs
Normal file
197
wallet/tests/common/mod.rs
Normal file
|
@ -0,0 +1,197 @@
|
|||
// 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.
|
||||
|
||||
//! Common functions to facilitate wallet, walletlib and transaction testing
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::collections::HashMap;
|
||||
|
||||
extern crate grin_api as api;
|
||||
extern crate grin_chain as chain;
|
||||
extern crate grin_core as core;
|
||||
extern crate grin_keychain as keychain;
|
||||
extern crate grin_wallet as wallet;
|
||||
extern crate time;
|
||||
|
||||
use chain::Chain;
|
||||
use core::core::{Output, OutputFeatures, OutputIdentifier, Transaction, TxKernel};
|
||||
use core::core::hash::Hashed;
|
||||
use core::{consensus, global, pow};
|
||||
use wallet::types::{BlockIdentifier, Error, ErrorKind, MerkleProofWrapper, OutputStatus,
|
||||
WalletConfig, WalletData};
|
||||
use wallet::{checker, BlockFees};
|
||||
use keychain::Keychain;
|
||||
|
||||
use util::secp::pedersen;
|
||||
|
||||
/// Mostly for testing, refreshes output state against a local chain instance instead of
|
||||
/// via an http API call
|
||||
pub fn refresh_output_state_local(
|
||||
config: &WalletConfig,
|
||||
keychain: &Keychain,
|
||||
chain: &chain::Chain,
|
||||
) -> Result<(), Error> {
|
||||
let wallet_outputs = checker::map_wallet_outputs(config, keychain)?;
|
||||
let chain_outputs: Vec<api::Output> = wallet_outputs
|
||||
.keys()
|
||||
.map(|k| match get_output_local(chain, &k) {
|
||||
Err(e) => panic!(e),
|
||||
Ok(k) => k,
|
||||
})
|
||||
.collect();
|
||||
let mut api_outputs: HashMap<pedersen::Commitment, api::Output> = HashMap::new();
|
||||
for out in chain_outputs {
|
||||
api_outputs.insert(out.commit.commit(), out);
|
||||
}
|
||||
checker::apply_api_outputs(config, &wallet_outputs, &api_outputs)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return the spendable wallet balance from the local chain
|
||||
/// (0:total, 1:amount_awaiting_confirmation, 2:confirmed but locked, 3:currently_spendable,
|
||||
/// 4:locked total) TODO: Should be a wallet lib function with nicer return values
|
||||
pub fn get_wallet_balances(
|
||||
config: &WalletConfig,
|
||||
keychain: &Keychain,
|
||||
height: u64,
|
||||
) -> Result<(u64, u64, u64, u64, u64), Error> {
|
||||
let ret_val = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
|
||||
let mut unspent_total = 0;
|
||||
let mut unspent_but_locked_total = 0;
|
||||
let mut unconfirmed_total = 0;
|
||||
let mut locked_total = 0;
|
||||
for out in wallet_data
|
||||
.outputs
|
||||
.values()
|
||||
.filter(|out| out.root_key_id == keychain.root_key_id())
|
||||
{
|
||||
if out.status == OutputStatus::Unspent {
|
||||
unspent_total += out.value;
|
||||
if out.lock_height > height {
|
||||
unspent_but_locked_total += out.value;
|
||||
}
|
||||
}
|
||||
if out.status == OutputStatus::Unconfirmed && !out.is_coinbase {
|
||||
unconfirmed_total += out.value;
|
||||
}
|
||||
if out.status == OutputStatus::Locked {
|
||||
locked_total += out.value;
|
||||
}
|
||||
}
|
||||
|
||||
Ok((
|
||||
unspent_total + unconfirmed_total, //total
|
||||
unconfirmed_total, //amount_awaiting_confirmation
|
||||
unspent_but_locked_total, // confirmed but locked
|
||||
unspent_total - unspent_but_locked_total, // currently spendable
|
||||
locked_total, // locked total
|
||||
))
|
||||
});
|
||||
ret_val
|
||||
}
|
||||
|
||||
/// Get an output from the chain locally and present it back as an API output
|
||||
fn get_output_local(
|
||||
chain: &chain::Chain,
|
||||
commit: &pedersen::Commitment,
|
||||
) -> Result<api::Output, Error> {
|
||||
let outputs = [
|
||||
OutputIdentifier::new(OutputFeatures::DEFAULT_OUTPUT, commit),
|
||||
OutputIdentifier::new(OutputFeatures::COINBASE_OUTPUT, commit),
|
||||
];
|
||||
|
||||
for x in outputs.iter() {
|
||||
if let Ok(_) = chain.is_unspent(&x) {
|
||||
return Ok(api::Output::new(&commit));
|
||||
}
|
||||
}
|
||||
Err(ErrorKind::Transaction)?
|
||||
}
|
||||
|
||||
/// Adds a block with a given reward to the chain and mines it
|
||||
pub fn add_block_with_reward(chain: &Chain, txs: Vec<&Transaction>, reward: (Output, TxKernel)) {
|
||||
let prev = chain.head_header().unwrap();
|
||||
let difficulty = consensus::next_difficulty(chain.difficulty_iter()).unwrap();
|
||||
let mut b = core::core::Block::new(&prev, txs, difficulty.clone(), reward).unwrap();
|
||||
b.header.timestamp = prev.timestamp + time::Duration::seconds(60);
|
||||
chain.set_txhashset_roots(&mut b, false).unwrap();
|
||||
pow::pow_size(
|
||||
&mut b.header,
|
||||
difficulty,
|
||||
global::proofsize(),
|
||||
global::sizeshift(),
|
||||
).unwrap();
|
||||
chain.process_block(b, chain::Options::MINE).unwrap();
|
||||
chain.validate(false).unwrap();
|
||||
}
|
||||
|
||||
/// adds a reward output to a wallet, includes that reward in a block, mines the block
|
||||
/// and adds it to the chain, with option transactions included.
|
||||
/// Helpful for building up precise wallet balances for testing.
|
||||
pub fn award_block_to_wallet(
|
||||
chain: &Chain,
|
||||
txs: Vec<&Transaction>,
|
||||
wallet: &(WalletConfig, Keychain),
|
||||
) {
|
||||
let prev = chain.head_header().unwrap();
|
||||
let fee_amt = txs.iter().map(|tx| tx.fee()).sum();
|
||||
let fees = BlockFees {
|
||||
fees: fee_amt,
|
||||
key_id: None,
|
||||
height: prev.height + 1,
|
||||
};
|
||||
let coinbase_tx = wallet::receiver::receive_coinbase(&wallet.0, &wallet.1, &fees);
|
||||
let (coinbase_tx, fees) = match coinbase_tx {
|
||||
Ok(t) => ((t.0, t.1), t.2),
|
||||
Err(e) => {
|
||||
panic!("Unable to create block reward: {:?}", e);
|
||||
}
|
||||
};
|
||||
add_block_with_reward(chain, txs, coinbase_tx.clone());
|
||||
// build merkle proof and block identifier and save in wallet
|
||||
let output_id = OutputIdentifier::from_output(&coinbase_tx.0.clone());
|
||||
let m_proof = chain.get_merkle_proof(&output_id, &chain.head_header().unwrap());
|
||||
let block_id = Some(BlockIdentifier(chain.head_header().unwrap().hash()));
|
||||
let _ = WalletData::with_wallet(&wallet.0.data_file_dir, |wallet_data| {
|
||||
if let Entry::Occupied(mut output) = wallet_data
|
||||
.outputs
|
||||
.entry(fees.key_id.as_ref().unwrap().to_hex())
|
||||
{
|
||||
let output = output.get_mut();
|
||||
output.block = block_id;
|
||||
output.merkle_proof = Some(MerkleProofWrapper(m_proof.unwrap()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// adds many block rewards to a wallet, no transactions
|
||||
pub fn award_blocks_to_wallet(
|
||||
chain: &Chain,
|
||||
wallet: &(WalletConfig, Keychain),
|
||||
num_rewards: usize,
|
||||
) {
|
||||
for _ in 0..num_rewards {
|
||||
award_block_to_wallet(chain, vec![], wallet);
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new wallet in a particular directory
|
||||
pub fn create_wallet(dir: &str) -> (WalletConfig, Keychain) {
|
||||
let mut wallet_config = WalletConfig::default();
|
||||
wallet_config.data_file_dir = String::from(dir);
|
||||
let wallet_seed = wallet::WalletSeed::init_file(&wallet_config).unwrap();
|
||||
let keychain = wallet_seed
|
||||
.derive_keychain("")
|
||||
.expect("Failed to derive keychain from seed file and passphrase.");
|
||||
(wallet_config, keychain)
|
||||
}
|
235
wallet/tests/transaction.rs
Normal file
235
wallet/tests/transaction.rs
Normal file
|
@ -0,0 +1,235 @@
|
|||
// 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);
|
||||
}
|
Loading…
Reference in a new issue