grin/wallet/src/libwallet/internal/tx.rs

199 lines
5.5 KiB
Rust
Raw Normal View History

// Copyright 2018 The Grin Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Transaction building functions
use core::core::Transaction;
use keychain::{Identifier, Keychain};
use libtx::slate::Slate;
use libtx::{build, tx_fee};
use libwallet::internal::{selection, sigcontext, updater};
use libwallet::types::{WalletBackend, WalletClient};
use libwallet::{Error, ErrorKind};
use util::LOGGER;
/// Receive a transaction, modifying the slate accordingly (which can then be
/// sent back to sender for posting)
pub fn receive_tx<T, K>(wallet: &mut T, slate: &mut Slate) -> Result<(), Error>
where
T: WalletBackend<K>,
K: Keychain,
{
// create an output using the amount in the slate
let (_, mut context, receiver_create_fn) =
selection::build_recipient_output_with_slate(wallet, slate).unwrap();
// fill public keys
let _ = slate.fill_round_1(
wallet.keychain(),
&mut context.sec_key,
&context.sec_nonce,
1,
)?;
// perform partial sig
let _ = slate.fill_round_2(wallet.keychain(), &context.sec_key, &context.sec_nonce, 1)?;
// Save output in wallet
let _ = receiver_create_fn(wallet);
Ok(())
}
/// Issue a new transaction to the provided sender by spending some of our
/// wallet
pub fn create_send_tx<T, K>(
wallet: &mut T,
2017-09-29 21:44:25 +03:00
amount: u64,
minimum_confirmations: u64,
max_outputs: usize,
selection_strategy_is_use_all: bool,
) -> Result<
(
Slate,
sigcontext::Context,
impl FnOnce(&mut T) -> Result<(), Error>,
),
Error,
>
where
T: WalletBackend<K> + WalletClient,
K: Keychain,
{
// Get lock height
let current_height = wallet.get_chain_height(wallet.node_url())?;
// ensure outputs we're selecting are up to date
updater::refresh_outputs(wallet)?;
let lock_height = current_height;
// Sender selects outputs into a new slate and save our corresponding keys in
// a transaction context. The secret key in our transaction context will be
// randomly selected. This returns the public slate, and a closure that locks
// our inputs and outputs once we're convinced the transaction exchange went
// according to plan
// This function is just a big helper to do all of that, in theory
// this process can be split up in any way
let (mut slate, mut context, sender_lock_fn) = selection::build_send_tx_slate(
wallet,
2,
amount,
current_height,
minimum_confirmations,
lock_height,
max_outputs,
selection_strategy_is_use_all,
)?;
2018-03-04 03:19:54 +03:00
// Generate a kernel offset and subtract from our context's secret key. Store
// the offset in the slate's transaction kernel, and adds our public key
// information to the slate
let _ = slate.fill_round_1(
wallet.keychain(),
&mut context.sec_key,
&context.sec_nonce,
0,
)?;
Ok((slate, context, sender_lock_fn))
}
/// Complete a transaction as the sender
pub fn complete_tx<T, K>(
wallet: &mut T,
slate: &mut Slate,
context: &sigcontext::Context,
) -> Result<(), Error>
where
T: WalletBackend<K>,
K: Keychain,
{
let _ = slate.fill_round_2(wallet.keychain(), &context.sec_key, &context.sec_nonce, 0)?;
// Final transaction can be built by anyone at this stage
let res = slate.finalize(wallet.keychain());
if let Err(e) = res {
Err(ErrorKind::LibTX(e.kind()))?
}
Ok(())
}
/// Issue a burn tx
pub fn issue_burn_tx<T, K>(
wallet: &mut T,
amount: u64,
minimum_confirmations: u64,
max_outputs: usize,
) -> Result<Transaction, Error>
where
T: WalletBackend<K> + WalletClient,
K: Keychain,
{
// TODO
// let keychain = &Keychain::burn_enabled(wallet.keychain(),
// &Identifier::zero());
let keychain = wallet.keychain().clone();
let current_height = wallet.get_chain_height(wallet.node_url())?;
let _ = updater::refresh_outputs(wallet);
let key_id = keychain.root_key_id();
// select some spendable coins from the wallet
Wallet LMDB backend (#1151) * Migrate main node store to LMDB In preparation to using LMDB as a wallet database, migrate the node db. There's no point in having 2 key-value stores. In addition LMDB provides a few advantages as a node db, namely a much faster build (compared to RocksDb), lesser dependencies and transactions. * Migrated p2p store to lmdb, stuff compiles * More fixes, chain tests starting to pass * Fixed txhashset rollback messing with block save and general batch delimitation. Chain tests passing. * rustfmt * LMDB max map size of 10MB isn't really workable. Half TB seems reasonable. * Fix wallet tests * Rather crucial commit was missing * rustfmt * Fixing new merged tests following lmdb changes * rustfmt * * Make txhashset validation read-only on fast sync to avoid having a really long open transaction. * Fix deadlock in new block processing, batch should always be created within a txhashset lock (when they interact). * Comment about batch and txhashset interlacing * Fix store tests to use batch * Externalize wallet config and seed * Converted direct read access to file outputs map to an iterator * Cleaned up and simplified wallet Backend trait: * No more direct mutable access to internal structures (HashMap) * Batch interface for all writes * Remove unneeded read wrapper (read_wallet) * rustfmt * First (incomplete) pass at wallet LMDB backend * Progressing on lmdb backent iml * Added batch impl for LMDB wallet backend. Pretty much done with it, but not sure how to deal with commit (owned). * rustfmt * Wrapping LMDB batch around a refcell to work around borrow rules * Compilation up to grin chain
2018-06-22 11:08:06 +03:00
let coins = wallet.select_coins(
key_id.clone(),
amount,
current_height,
minimum_confirmations,
max_outputs,
false,
);
debug!(LOGGER, "selected some coins - {}", coins.len());
let fee = tx_fee(coins.len(), 2, selection::coins_proof_count(&coins), None);
let (mut parts, _, _) =
Wallet LMDB backend (#1151) * Migrate main node store to LMDB In preparation to using LMDB as a wallet database, migrate the node db. There's no point in having 2 key-value stores. In addition LMDB provides a few advantages as a node db, namely a much faster build (compared to RocksDb), lesser dependencies and transactions. * Migrated p2p store to lmdb, stuff compiles * More fixes, chain tests starting to pass * Fixed txhashset rollback messing with block save and general batch delimitation. Chain tests passing. * rustfmt * LMDB max map size of 10MB isn't really workable. Half TB seems reasonable. * Fix wallet tests * Rather crucial commit was missing * rustfmt * Fixing new merged tests following lmdb changes * rustfmt * * Make txhashset validation read-only on fast sync to avoid having a really long open transaction. * Fix deadlock in new block processing, batch should always be created within a txhashset lock (when they interact). * Comment about batch and txhashset interlacing * Fix store tests to use batch * Externalize wallet config and seed * Converted direct read access to file outputs map to an iterator * Cleaned up and simplified wallet Backend trait: * No more direct mutable access to internal structures (HashMap) * Batch interface for all writes * Remove unneeded read wrapper (read_wallet) * rustfmt * First (incomplete) pass at wallet LMDB backend * Progressing on lmdb backent iml * Added batch impl for LMDB wallet backend. Pretty much done with it, but not sure how to deal with commit (owned). * rustfmt * Wrapping LMDB batch around a refcell to work around borrow rules * Compilation up to grin chain
2018-06-22 11:08:06 +03:00
selection::inputs_and_change(&coins, wallet, amount, fee)?;
//TODO: If we end up using this, create change output here
// add burn output and fees
parts.push(build::output(amount - fee, Identifier::zero()));
// finalize the burn transaction and send
let tx_burn = build::transaction(parts, &keychain)?;
tx_burn.validate()?;
Ok(tx_burn)
}
#[cfg(test)]
mod test {
use keychain::{ExtKeychain, Keychain};
use libtx::build;
#[test]
// demonstrate that input.commitment == referenced output.commitment
2017-11-01 21:32:34 +03:00
// based on the public key and amount begin spent
fn output_commitment_equals_input_commitment_on_spend() {
let keychain = ExtKeychain::from_random_seed().unwrap();
let key_id1 = keychain.derive_key_id(1).unwrap();
let tx1 = build::transaction(vec![build::output(105, key_id1.clone())], &keychain).unwrap();
let tx2 = build::transaction(vec![build::input(105, key_id1.clone())], &keychain).unwrap();
hash (features|commitment) in output mmr (#615) * experiment with lock_heights on outputs * playing around with lock_height as part of the switch commitment hash * cleanup * include features in the switch commit hash key * commit * rebase off master * commit * cleanup * missing docs * rework coinbase maturity test to build valid tx * pool and chain tests passing (inputs have switch commitments) * commit * cleanup * check inputs spending coinbase outputs have valid lock_heights * wip - got it building (tests still failing) * use zero key for non coinbase switch commit hash * fees and height wrong order... * send output lock_height over to wallet via api * no more header by height index workaround this for wallet refresh and wallet restore * refresh heights for unspent wallet outputs where missing * TODO - might be slow? * simplify - do not pass around lock_height for non coinbase outputs * commit * fix tests after merge * build input vs coinbase_input switch commit hash key encodes lock_height cleanup output by commit index (currently broken...) * is_unspent and get_unspent cleanup - we have no outputs, only switch_commit_hashes * separate concept of utxo vs output in the api utxos come from the sumtrees (and only the sumtrees, limited info) outputs come from blocks (and we need to look them up via block height) * cleanup * better api support for block outputs with range proofs * basic wallet operations appear to work restore is not working fully refresh refreshes heights correctly (at least appears to) * wallet refresh and wallet restore appear to be working now * fix core tests * fix some mine_simple_chain tests * fixup chain tests * rework so pool tests pass * wallet restore now safely habndles duplicate commitments (reused wallet keys) for coinbase outputs where lock_height is _very_ important * wip * validate_coinbase_maturity got things building tests are failing * lite vs full versions of is_unspent * builds and working locally zero-conf - what to do here? * handle zero-conf edge case (use latest block) * introduce OutputIdentifier, avoid leaking SumCommit everywhere * fix the bad merge * pool verifies coinbase maturity via is_matured this uses sumtree in a consistent way * cleanup * add docs, cleanup build warnings * fix core tests * fix chain tests * fix pool tests * cleanup debug logging that we no longer need * make out_block optional on an input (only care about it for spending coinbase outputs) * cleanup * bump the build
2018-01-17 06:03:40 +03:00
assert_eq!(tx1.outputs[0].features, tx2.inputs[0].features);
assert_eq!(tx1.outputs[0].commitment(), tx2.inputs[0].commitment());
}
}