mirror of
https://github.com/jwinterm/grin-wallet.git
synced 2026-10-17 15:21:39 +02:00
initial commit
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
[package]
|
||||
name = "grin_libwallet"
|
||||
version = "1.1.0"
|
||||
authors = ["Grin Developers <mimblewimble@lists.launchpad.net>"]
|
||||
description = "Simple, private and scalable cryptocurrency implementation based on the MimbleWimble chain format."
|
||||
license = "Apache-2.0"
|
||||
repository = "https://github.com/mimblewimble/grin"
|
||||
keywords = [ "crypto", "grin", "mimblewimble" ]
|
||||
readme = "README.md"
|
||||
exclude = ["**/*.grin", "**/*.grin2"]
|
||||
#build = "src/build/build.rs"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
blake2-rfc = "0.2"
|
||||
failure = "0.1"
|
||||
failure_derive = "0.1"
|
||||
rand = "0.5"
|
||||
serde = "1"
|
||||
serde_derive = "1"
|
||||
serde_json = "1"
|
||||
log = "0.4"
|
||||
uuid = { version = "0.6", features = ["serde", "v4"] }
|
||||
chrono = { version = "0.4.4", features = ["serde"] }
|
||||
|
||||
grin_core = { path = "../../grin/core", version = "1.1.0" }
|
||||
grin_keychain = { path = "../../grin/keychain", version = "1.1.0" }
|
||||
grin_util = { path = "../../grin/util", version = "1.1.0" }
|
||||
@@ -0,0 +1,968 @@
|
||||
// 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.
|
||||
|
||||
//! Main interface into all wallet API functions.
|
||||
//! Wallet APIs are split into two seperate blocks of functionality
|
||||
//! called the 'Owner' and 'Foreign' APIs:
|
||||
//! * The 'Owner' API is intended to expose methods that are to be
|
||||
//! used by the wallet owner only. It is vital that this API is not
|
||||
//! exposed to anyone other than the owner of the wallet (i.e. the
|
||||
//! person with access to the seed and password.
|
||||
//! * The 'Foreign' API contains methods that other wallets will
|
||||
//! use to interact with the owner's wallet. This API can be exposed
|
||||
//! to the outside world, with the consideration as to how that can
|
||||
//! be done securely up to the implementor.
|
||||
//!
|
||||
//! Methods in both APIs are intended to be 'single use', that is to say each
|
||||
//! method will 'open' the wallet (load the keychain with its master seed), perform
|
||||
//! its operation, then 'close' the wallet (unloading references to the keychain and master
|
||||
//! seed).
|
||||
|
||||
use crate::util::Mutex;
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::core::core::hash::Hashed;
|
||||
use crate::core::core::Transaction;
|
||||
use crate::core::ser;
|
||||
use crate::keychain::{Identifier, Keychain};
|
||||
use crate::internal::{keys, tx, updater};
|
||||
use crate::slate::Slate;
|
||||
use crate::types::{
|
||||
AcctPathMapping, BlockFees, CbData, NodeClient, OutputData, OutputLockFn, TxLogEntry,
|
||||
TxLogEntryType, TxWrapper, WalletBackend, WalletInfo,
|
||||
};
|
||||
use crate::{Error, ErrorKind};
|
||||
use crate::util;
|
||||
use crate::util::secp::{pedersen, ContextFlag, Secp256k1};
|
||||
|
||||
const USER_MESSAGE_MAX_LEN: usize = 256;
|
||||
|
||||
/// Functions intended for use by the owner (e.g. master seed holder) of the wallet.
|
||||
pub struct APIOwner<W: ?Sized, C, K>
|
||||
where
|
||||
W: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
/// A reference-counted mutex to an implementation of the
|
||||
/// [`WalletBackend`](../types/trait.WalletBackend.html) trait.
|
||||
pub wallet: Arc<Mutex<W>>,
|
||||
phantom: PhantomData<K>,
|
||||
phantom_c: PhantomData<C>,
|
||||
}
|
||||
|
||||
impl<W: ?Sized, C, K> APIOwner<W, C, K>
|
||||
where
|
||||
W: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
/// Create a new API instance with the given wallet instance. All subsequent
|
||||
/// API calls will operate on this instance of the wallet.
|
||||
///
|
||||
/// Each method will call the [`WalletBackend`](../types/trait.WalletBackend.html)'s
|
||||
/// [`open_with_credentials`](../types/trait.WalletBackend.html#tymethod.open_with_credentials)
|
||||
/// (initialising a keychain with the master seed,) perform its operation, then close the keychain
|
||||
/// with a call to [`close`](../types/trait.WalletBackend.html#tymethod.close)
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `wallet_in` - A reference-counted mutex containing an implementation of the
|
||||
/// [`WalletBackend`](../types/trait.WalletBackend.html) trait.
|
||||
///
|
||||
/// # Returns
|
||||
/// * An instance of the OwnerAPI holding a reference to the provided wallet
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # extern crate grin_wallet as wallet;
|
||||
/// # extern crate grin_keychain as keychain;
|
||||
/// # extern crate grin_util as util;
|
||||
///
|
||||
/// use std::sync::Arc;
|
||||
/// use util::Mutex;
|
||||
///
|
||||
/// use keychain::ExtKeychain;
|
||||
/// use wallet::libwallet::api::APIOwner;
|
||||
///
|
||||
/// // These contain sample implementations of each part needed for a wallet
|
||||
/// use wallet::{LMDBBackend, HTTPNodeClient, WalletBackend, WalletConfig};
|
||||
///
|
||||
/// let mut wallet_config = WalletConfig::default();
|
||||
/// # wallet_config.data_file_dir = "test_output/doc/wallet1".to_owned();
|
||||
///
|
||||
/// // A NodeClient must first be created to handle communication between
|
||||
/// // the wallet and the node.
|
||||
///
|
||||
/// let node_client = HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, None);
|
||||
/// let mut wallet:Arc<Mutex<WalletBackend<HTTPNodeClient, ExtKeychain>>> =
|
||||
/// Arc::new(Mutex::new(
|
||||
/// LMDBBackend::new(wallet_config.clone(), "", node_client).unwrap()
|
||||
/// ));
|
||||
///
|
||||
/// let api_owner = APIOwner::new(wallet.clone());
|
||||
/// // .. perform wallet operations
|
||||
///
|
||||
/// ```
|
||||
|
||||
pub fn new(wallet_in: Arc<Mutex<W>>) -> Self {
|
||||
APIOwner {
|
||||
wallet: wallet_in,
|
||||
phantom: PhantomData,
|
||||
phantom_c: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a list of accounts stored in the wallet (i.e. mappings between
|
||||
/// user-specified labels and BIP32 derivation paths.
|
||||
///
|
||||
/// # Returns
|
||||
/// * Result Containing:
|
||||
/// * A Vector of [`AcctPathMapping`](../types/struct.AcctPathMapping.html) data
|
||||
/// * or [`libwallet::Error`](../struct.Error.html) if an error is encountered.
|
||||
///
|
||||
/// # Remarks
|
||||
///
|
||||
/// * A wallet should always have the path with the label 'default' path defined,
|
||||
/// with path m/0/0
|
||||
/// * This method does not need to use the wallet seed or keychain.
|
||||
///
|
||||
/// # Example
|
||||
/// Set up as in [`new`](struct.APIOwner.html#method.new) method above.
|
||||
/// ```
|
||||
/// # extern crate grin_wallet as wallet;
|
||||
/// # extern crate grin_keychain as keychain;
|
||||
/// # extern crate grin_util as util;
|
||||
/// # use std::sync::Arc;
|
||||
/// # use util::Mutex;
|
||||
/// # use keychain::ExtKeychain;
|
||||
/// # use wallet::libwallet::api::APIOwner;
|
||||
/// # use wallet::{LMDBBackend, HTTPNodeClient, WalletBackend, WalletConfig};
|
||||
/// # let mut wallet_config = WalletConfig::default();
|
||||
/// # wallet_config.data_file_dir = "test_output/doc/wallet1".to_owned();
|
||||
/// # let node_client = HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, None);
|
||||
/// # let mut wallet:Arc<Mutex<WalletBackend<HTTPNodeClient, ExtKeychain>>> =
|
||||
/// # Arc::new(Mutex::new(
|
||||
/// # LMDBBackend::new(wallet_config.clone(), "", node_client).unwrap()
|
||||
/// # ));
|
||||
///
|
||||
/// let api_owner = APIOwner::new(wallet.clone());
|
||||
///
|
||||
/// let result = api_owner.accounts();
|
||||
///
|
||||
/// if let Ok(accts) = result {
|
||||
/// //...
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
pub fn accounts(&self) -> Result<Vec<AcctPathMapping>, Error> {
|
||||
let mut w = self.wallet.lock();
|
||||
keys::accounts(&mut *w)
|
||||
}
|
||||
|
||||
/// Creates a new 'account', which is a mapping of a user-specified
|
||||
/// label to a BIP32 path
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `label` - A human readable label to which to map the new BIP32 Path
|
||||
///
|
||||
/// # Returns
|
||||
/// * Result Containing:
|
||||
/// * A [Keychain Identifier](#) for the new path
|
||||
/// * or [`libwallet::Error`](../struct.Error.html) if an error is encountered.
|
||||
///
|
||||
/// # Remarks
|
||||
///
|
||||
/// * Wallets should be initialised with the 'default' path mapped to `m/0/0`
|
||||
/// * Each call to this function will increment the first element of the path
|
||||
/// so the first call will create an account at `m/1/0` and the second at
|
||||
/// `m/2/0` etc. . .
|
||||
/// * The account path is used throughout as the parent key for most key-derivation
|
||||
/// operations. See [`set_active_account`](struct.APIOwner.html#method.set_active_account) for
|
||||
/// further details.
|
||||
///
|
||||
/// * This function does not need to use the root wallet seed or keychain.
|
||||
///
|
||||
/// # Example
|
||||
/// Set up as in [`new`](struct.APIOwner.html#method.new) method above.
|
||||
/// ```
|
||||
/// # extern crate grin_wallet as wallet;
|
||||
/// # extern crate grin_keychain as keychain;
|
||||
/// # extern crate grin_util as util;
|
||||
/// # use std::sync::Arc;
|
||||
/// # use util::Mutex;
|
||||
/// # use keychain::ExtKeychain;
|
||||
/// # use wallet::libwallet::api::APIOwner;
|
||||
/// # use wallet::{LMDBBackend, HTTPNodeClient, WalletBackend, WalletConfig};
|
||||
/// # let mut wallet_config = WalletConfig::default();
|
||||
/// # wallet_config.data_file_dir = "test_output/doc/wallet1".to_owned();
|
||||
/// # let node_client = HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, None);
|
||||
/// # let mut wallet:Arc<Mutex<WalletBackend<HTTPNodeClient, ExtKeychain>>> =
|
||||
/// # Arc::new(Mutex::new(
|
||||
/// # LMDBBackend::new(wallet_config.clone(), "", node_client).unwrap()
|
||||
/// # ));
|
||||
///
|
||||
/// let api_owner = APIOwner::new(wallet.clone());
|
||||
///
|
||||
/// let result = api_owner.create_account_path("account1");
|
||||
///
|
||||
/// if let Ok(identifier) = result {
|
||||
/// //...
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
pub fn create_account_path(&self, label: &str) -> Result<Identifier, Error> {
|
||||
let mut w = self.wallet.lock();
|
||||
keys::new_acct_path(&mut *w, label)
|
||||
}
|
||||
|
||||
/// Sets the wallet's currently active account. This sets the
|
||||
/// BIP32 parent path used for most key-derivation operations.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `label` - The human readable label for the account. Accounts can be retrieved via
|
||||
/// the [`account`](struct.APIOwner.html#method.accounts) method
|
||||
///
|
||||
/// # Returns
|
||||
/// * Result Containing:
|
||||
/// * `Ok(())` if the path was correctly set
|
||||
/// * or [`libwallet::Error`](../struct.Error.html) if an error is encountered.
|
||||
///
|
||||
/// # Remarks
|
||||
///
|
||||
/// * Wallet parent paths are 2 path elements long, e.g. `m/0/0` is the path
|
||||
/// labelled 'default'. Keys derived from this parent path are 3 elements long,
|
||||
/// e.g. the secret keys derived from the `m/0/0` path will be at paths `m/0/0/0`,
|
||||
/// `m/0/0/1` etc...
|
||||
///
|
||||
/// * This function does not need to use the root wallet seed or keychain.
|
||||
///
|
||||
/// # Example
|
||||
/// Set up as in [`new`](struct.APIOwner.html#method.new) method above.
|
||||
/// ```
|
||||
/// # extern crate grin_wallet as wallet;
|
||||
/// # extern crate grin_keychain as keychain;
|
||||
/// # extern crate grin_util as util;
|
||||
/// # use std::sync::Arc;
|
||||
/// # use util::Mutex;
|
||||
/// # use keychain::ExtKeychain;
|
||||
/// # use wallet::libwallet::api::APIOwner;
|
||||
/// # use wallet::{LMDBBackend, HTTPNodeClient, WalletBackend, WalletConfig};
|
||||
/// # let mut wallet_config = WalletConfig::default();
|
||||
/// # wallet_config.data_file_dir = "test_output/doc/wallet1".to_owned();
|
||||
/// # let node_client = HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, None);
|
||||
/// # let mut wallet:Arc<Mutex<WalletBackend<HTTPNodeClient, ExtKeychain>>> =
|
||||
/// # Arc::new(Mutex::new(
|
||||
/// # LMDBBackend::new(wallet_config.clone(), "", node_client).unwrap()
|
||||
/// # ));
|
||||
///
|
||||
/// let api_owner = APIOwner::new(wallet.clone());
|
||||
///
|
||||
/// let result = api_owner.create_account_path("account1");
|
||||
///
|
||||
/// if let Ok(identifier) = result {
|
||||
/// // set the account active
|
||||
/// let result2 = api_owner.set_active_account("account1");
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
pub fn set_active_account(&self, label: &str) -> Result<(), Error> {
|
||||
let mut w = self.wallet.lock();
|
||||
w.set_parent_key_id_by_name(label)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns a list of outputs from the active account in the wallet.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `include_spent` - If `true`, outputs that have been marked as 'spent'
|
||||
/// in the wallet will be returned. If `false`, spent outputs will omitted
|
||||
/// from the results.
|
||||
/// * `refresh_from_node` - If true, the wallet will attempt to contact
|
||||
/// a node (via the [`NodeClient`](../types/trait.NodeClient.html)
|
||||
/// provided during wallet instantiation). If `false`, the results will
|
||||
/// contain output information that may be out-of-date (from the last time
|
||||
/// the wallet's output set was refreshed against the node).
|
||||
/// * `tx_id` - If `Some(i)`, only return the outputs associated with
|
||||
/// the transaction log entry of id `i`.
|
||||
///
|
||||
/// # Returns
|
||||
/// * (`bool`, `Vec<OutputData, Commitment>`) - A tuple:
|
||||
/// * The first `bool` element indicates whether the data was successfully
|
||||
/// refreshed from the node (note this may be false even if the `refresh_from_node`
|
||||
/// argument was set to `true`.
|
||||
/// * The second element contains the result set, of which each element is
|
||||
/// a mapping between the wallet's internal [OutputData](../types/struct.OutputData.html)
|
||||
/// and the Output commitment as identified in the chain's UTXO set
|
||||
///
|
||||
/// # Example
|
||||
/// Set up as in [`new`](struct.APIOwner.html#method.new) method above.
|
||||
/// ```
|
||||
/// # extern crate grin_wallet as wallet;
|
||||
/// # extern crate grin_keychain as keychain;
|
||||
/// # extern crate grin_util as util;
|
||||
/// # use std::sync::Arc;
|
||||
/// # use util::Mutex;
|
||||
/// # use keychain::ExtKeychain;
|
||||
/// # use wallet::libwallet::api::APIOwner;
|
||||
/// # use wallet::{LMDBBackend, HTTPNodeClient, WalletBackend, WalletConfig};
|
||||
/// # let mut wallet_config = WalletConfig::default();
|
||||
/// # wallet_config.data_file_dir = "test_output/doc/wallet1".to_owned();
|
||||
/// # let node_client = HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, None);
|
||||
/// # let mut wallet:Arc<Mutex<WalletBackend<HTTPNodeClient, ExtKeychain>>> =
|
||||
/// # Arc::new(Mutex::new(
|
||||
/// # LMDBBackend::new(wallet_config.clone(), "", node_client).unwrap()
|
||||
/// # ));
|
||||
///
|
||||
/// let api_owner = APIOwner::new(wallet.clone());
|
||||
/// let show_spent = false;
|
||||
/// let update_from_node = true;
|
||||
/// let tx_id = None;
|
||||
///
|
||||
/// let result = api_owner.retrieve_outputs(show_spent, update_from_node, tx_id);
|
||||
///
|
||||
/// if let Ok((was_updated, output_mapping)) = result {
|
||||
/// //...
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
pub fn retrieve_outputs(
|
||||
&self,
|
||||
include_spent: bool,
|
||||
refresh_from_node: bool,
|
||||
tx_id: Option<u32>,
|
||||
) -> Result<(bool, Vec<(OutputData, pedersen::Commitment)>), Error> {
|
||||
let mut w = self.wallet.lock();
|
||||
w.open_with_credentials()?;
|
||||
let parent_key_id = w.parent_key_id();
|
||||
|
||||
let mut validated = false;
|
||||
if refresh_from_node {
|
||||
validated = self.update_outputs(&mut w, false);
|
||||
}
|
||||
|
||||
let res = Ok((
|
||||
validated,
|
||||
updater::retrieve_outputs(&mut *w, include_spent, tx_id, Some(&parent_key_id))?,
|
||||
));
|
||||
|
||||
w.close()?;
|
||||
res
|
||||
}
|
||||
|
||||
/// Returns a list of [Transaction Log Entries](../types/struct.TxLogEntry.html)
|
||||
/// from the active account in the wallet.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `refresh_from_node` - If true, the wallet will attempt to contact
|
||||
/// a node (via the [`NodeClient`](../types/trait.NodeClient.html)
|
||||
/// provided during wallet instantiation). If `false`, the results will
|
||||
/// contain transaction information that may be out-of-date (from the last time
|
||||
/// the wallet's output set was refreshed against the node).
|
||||
/// * `tx_id` - If `Some(i)`, only return the transactions associated with
|
||||
/// the transaction log entry of id `i`.
|
||||
/// * `tx_slate_id` - If `Some(uuid)`, only return transactions associated with
|
||||
/// the given [`Slate`](../../libtx/slate/struct.Slate.html) uuid.
|
||||
///
|
||||
/// # Returns
|
||||
/// * (`bool`, `Vec<[TxLogEntry](../types/struct.TxLogEntry.html)>`) - A tuple:
|
||||
/// * The first `bool` element indicates whether the data was successfully
|
||||
/// refreshed from the node (note this may be false even if the `refresh_from_node`
|
||||
/// argument was set to `true`.
|
||||
/// * The second element contains the set of retrieved
|
||||
/// [TxLogEntries](../types/struct/TxLogEntry.html)
|
||||
///
|
||||
/// # Example
|
||||
/// Set up as in [`new`](struct.APIOwner.html#method.new) method above.
|
||||
/// ```
|
||||
/// # extern crate grin_wallet as wallet;
|
||||
/// # extern crate grin_keychain as keychain;
|
||||
/// # extern crate grin_util as util;
|
||||
/// # use std::sync::Arc;
|
||||
/// # use util::Mutex;
|
||||
/// # use keychain::ExtKeychain;
|
||||
/// # use wallet::libwallet::api::APIOwner;
|
||||
/// # use wallet::{LMDBBackend, HTTPNodeClient, WalletBackend, WalletConfig};
|
||||
/// # let mut wallet_config = WalletConfig::default();
|
||||
/// # wallet_config.data_file_dir = "test_output/doc/wallet1".to_owned();
|
||||
/// # let node_client = HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, None);
|
||||
/// # let mut wallet:Arc<Mutex<WalletBackend<HTTPNodeClient, ExtKeychain>>> =
|
||||
/// # Arc::new(Mutex::new(
|
||||
/// # LMDBBackend::new(wallet_config.clone(), "", node_client).unwrap()
|
||||
/// # ));
|
||||
///
|
||||
/// let api_owner = APIOwner::new(wallet.clone());
|
||||
/// let update_from_node = true;
|
||||
/// let tx_id = None;
|
||||
/// let tx_slate_id = None;
|
||||
///
|
||||
/// // Return all TxLogEntries
|
||||
/// let result = api_owner.retrieve_txs(update_from_node, tx_id, tx_slate_id);
|
||||
///
|
||||
/// if let Ok((was_updated, tx_log_entries)) = result {
|
||||
/// //...
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
pub fn retrieve_txs(
|
||||
&self,
|
||||
refresh_from_node: bool,
|
||||
tx_id: Option<u32>,
|
||||
tx_slate_id: Option<Uuid>,
|
||||
) -> Result<(bool, Vec<TxLogEntry>), Error> {
|
||||
let mut w = self.wallet.lock();
|
||||
w.open_with_credentials()?;
|
||||
let parent_key_id = w.parent_key_id();
|
||||
|
||||
let mut validated = false;
|
||||
if refresh_from_node {
|
||||
validated = self.update_outputs(&mut w, false);
|
||||
}
|
||||
|
||||
let res = Ok((
|
||||
validated,
|
||||
updater::retrieve_txs(&mut *w, tx_id, tx_slate_id, Some(&parent_key_id), false)?,
|
||||
));
|
||||
|
||||
w.close()?;
|
||||
res
|
||||
}
|
||||
|
||||
/// Returns summary information from the active account in the wallet.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `refresh_from_node` - If true, the wallet will attempt to contact
|
||||
/// a node (via the [`NodeClient`](../types/trait.NodeClient.html)
|
||||
/// provided during wallet instantiation). If `false`, the results will
|
||||
/// contain transaction information that may be out-of-date (from the last time
|
||||
/// the wallet's output set was refreshed against the node).
|
||||
/// * `minimum_confirmations` - The minimum number of confirmations an output
|
||||
/// should have before it's included in the 'amount_currently_spendable' total
|
||||
///
|
||||
/// # Returns
|
||||
/// * (`bool`, [`WalletInfo`](../types/struct.WalletInfo.html)) - A tuple:
|
||||
/// * The first `bool` element indicates whether the data was successfully
|
||||
/// refreshed from the node (note this may be false even if the `refresh_from_node`
|
||||
/// argument was set to `true`.
|
||||
/// * The second element contains the Summary [`WalletInfo`](../types/struct.WalletInfo.html)
|
||||
///
|
||||
/// # Example
|
||||
/// Set up as in [`new`](struct.APIOwner.html#method.new) method above.
|
||||
/// ```
|
||||
/// # extern crate grin_wallet as wallet;
|
||||
/// # extern crate grin_keychain as keychain;
|
||||
/// # extern crate grin_util as util;
|
||||
/// # use std::sync::Arc;
|
||||
/// # use util::Mutex;
|
||||
/// # use keychain::ExtKeychain;
|
||||
/// # use wallet::libwallet::api::APIOwner;
|
||||
/// # use wallet::{LMDBBackend, HTTPNodeClient, WalletBackend, WalletConfig};
|
||||
/// # let mut wallet_config = WalletConfig::default();
|
||||
/// # wallet_config.data_file_dir = "test_output/doc/wallet1".to_owned();
|
||||
/// # let node_client = HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, None);
|
||||
/// # let mut wallet:Arc<Mutex<WalletBackend<HTTPNodeClient, ExtKeychain>>> =
|
||||
/// # Arc::new(Mutex::new(
|
||||
/// # LMDBBackend::new(wallet_config.clone(), "", node_client).unwrap()
|
||||
/// # ));
|
||||
///
|
||||
/// let mut api_owner = APIOwner::new(wallet.clone());
|
||||
/// let update_from_node = true;
|
||||
/// let minimum_confirmations=10;
|
||||
///
|
||||
/// // Return summary info for active account
|
||||
/// let result = api_owner.retrieve_summary_info(update_from_node, minimum_confirmations);
|
||||
///
|
||||
/// if let Ok((was_updated, summary_info)) = result {
|
||||
/// //...
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
pub fn retrieve_summary_info(
|
||||
&mut self,
|
||||
refresh_from_node: bool,
|
||||
minimum_confirmations: u64,
|
||||
) -> Result<(bool, WalletInfo), Error> {
|
||||
let mut w = self.wallet.lock();
|
||||
w.open_with_credentials()?;
|
||||
let parent_key_id = w.parent_key_id();
|
||||
|
||||
let mut validated = false;
|
||||
if refresh_from_node {
|
||||
validated = self.update_outputs(&mut w, false);
|
||||
}
|
||||
|
||||
let wallet_info = updater::retrieve_info(&mut *w, &parent_key_id, minimum_confirmations)?;
|
||||
let res = Ok((validated, wallet_info));
|
||||
|
||||
w.close()?;
|
||||
res
|
||||
}
|
||||
|
||||
/// Initiates a new transaction as the sender, creating a new
|
||||
/// [`Slate`](../../libtx/slate/struct.Slate.html) object containing
|
||||
/// the sender's inputs, change outputs, and public signature data. This slate can
|
||||
/// then be sent to the recipient to continue the transaction via the
|
||||
/// [Foreign API's `receive_tx`](struct.APIForeign.html#method.receive_tx) method.
|
||||
///
|
||||
/// When a transaction is created, the wallet must also lock inputs (and create unconfirmed
|
||||
/// outputs) corresponding to the transaction created in the slate, so that the wallet doesn't
|
||||
/// attempt to re-spend outputs that are already included in a transaction before the transaction
|
||||
/// is confirmed. This method also returns a function that will perform that locking, and it is
|
||||
/// up to the caller to decide the best time to call the lock function
|
||||
/// (via the [`tx_lock_outputs`](struct.APIOwner.html#method.tx_lock_outputs) method).
|
||||
/// If the exchange method is intended to be synchronous (such as via a direct http call,)
|
||||
/// then the lock call can wait until the response is confirmed. If it is asynchronous, (such
|
||||
/// as via file transfer,) the lock call should happen immediately (before the file is sent
|
||||
/// to the recipient).
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `src_acct_name` - The human readable account name from which to draw outputs
|
||||
/// for the transaction, overriding whatever the active account is as set via the
|
||||
/// [`set_active_account`](struct.APIOwner.html#method.set_active_account) method.
|
||||
/// If None, the transaction will use the active account.
|
||||
/// * `amount` - The amount to send, in nanogrins. (`1 G = 1_000_000_000nG`)
|
||||
/// * `minimum_confirmations` - The minimum number of confirmations an output
|
||||
/// should have in order to be included in the transaction.
|
||||
/// * `max_outputs` - By default, the wallet selects as many inputs as possible in a
|
||||
/// transaction, to reduce the Output set and the fees. The wallet will attempt to spend
|
||||
/// include up to `max_outputs` in a transaction, however if this is not enough to cover
|
||||
/// the whole amount, the wallet will include more outputs. This parameter should be considered
|
||||
/// a soft limit.
|
||||
/// * `num_change_outputs` - The target number of change outputs to create in the transaction.
|
||||
/// The actual number created will be `num_change_outputs` + whatever remainder is needed.
|
||||
/// * `selection_strategy_is_use_all` - If `true`, attempt to use up as many outputs as
|
||||
/// possible to create the transaction, up the 'soft limit' of `max_outputs`. This helps
|
||||
/// to reduce the size of the UTXO set and the amount of data stored in the wallet, and
|
||||
/// minimizes fees. This will generally result in many inputs and a large change output(s),
|
||||
/// usually much larger than the amount being sent. If `false`, the transaction will include
|
||||
/// as many outputs as are needed to meet the amount, (and no more) starting with the smallest
|
||||
/// value outputs.
|
||||
/// * `message` - An optional participant message to include alongside the sender's public
|
||||
/// ParticipantData within the slate. This message will include a signature created with the
|
||||
/// sender's private keys, and will be publically verifiable. Note this message is for
|
||||
/// the convenience of the participants during the exchange; it is not included in the final
|
||||
/// transaction sent to the chain. The message will be truncated to 256 characters.
|
||||
/// Validation of this message is optional.
|
||||
///
|
||||
/// # Returns
|
||||
/// * a result containing:
|
||||
/// * ([`Slate`](../../libtx/slate/struct.Slate.html), lock_function) - A tuple:
|
||||
/// * The transaction Slate, which can be forwarded to the recieving party by any means.
|
||||
/// * A lock function, which should be called when the caller deems it appropriate to lock
|
||||
/// the transaction outputs (i.e. there is relative certaintly that the slate will be
|
||||
/// transmitted to the receiving party). Must be called before calling
|
||||
/// [`finalize_tx`](struct.APIOwner.html#method.finalize_tx).
|
||||
/// * or [`libwallet::Error`](../struct.Error.html) if an error is encountered.
|
||||
///
|
||||
/// # Remarks
|
||||
///
|
||||
/// * This method requires an active connection to a node, and will fail with error if a node
|
||||
/// cannot be contacted to refresh output statuses.
|
||||
/// * This method will store a partially completed transaction in the wallet's transaction log,
|
||||
/// which will be updated on the corresponding call to [`finalize_tx`](struct.APIOwner.html#method.finalize_tx).
|
||||
///
|
||||
/// # Example
|
||||
/// Set up as in [new](struct.APIOwner.html#method.new) method above.
|
||||
/// ```
|
||||
/// # extern crate grin_wallet as wallet;
|
||||
/// # extern crate grin_keychain as keychain;
|
||||
/// # extern crate grin_util as util;
|
||||
/// # use std::sync::Arc;
|
||||
/// # use util::Mutex;
|
||||
/// # use keychain::ExtKeychain;
|
||||
/// # use wallet::libwallet::api::APIOwner;
|
||||
/// # use wallet::{LMDBBackend, HTTPNodeClient, WalletBackend, WalletConfig};
|
||||
/// # let mut wallet_config = WalletConfig::default();
|
||||
/// # wallet_config.data_file_dir = "test_output/doc/wallet1".to_owned();
|
||||
/// # let node_client = HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, None);
|
||||
/// # let mut wallet:Arc<Mutex<WalletBackend<HTTPNodeClient, ExtKeychain>>> =
|
||||
/// # Arc::new(Mutex::new(
|
||||
/// # LMDBBackend::new(wallet_config.clone(), "", node_client).unwrap()
|
||||
/// # ));
|
||||
///
|
||||
/// let mut api_owner = APIOwner::new(wallet.clone());
|
||||
/// let amount = 2_000_000_000;
|
||||
///
|
||||
/// // Attempt to create a transaction using the 'default' account
|
||||
/// let result = api_owner.initiate_tx(
|
||||
/// None,
|
||||
/// amount, // amount
|
||||
/// 10, // minimum confirmations
|
||||
/// 500, // max outputs
|
||||
/// 1, // num change outputs
|
||||
/// true, // select all outputs
|
||||
/// Some("Have some Grins. Love, Yeastplume".to_owned()),
|
||||
/// );
|
||||
///
|
||||
/// if let Ok((slate, lock_fn)) = result {
|
||||
/// // Send slate somehow
|
||||
/// // ...
|
||||
/// // Lock our outputs if we're happy the slate was (or is being) sent
|
||||
/// api_owner.tx_lock_outputs(&slate, lock_fn);
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
pub fn initiate_tx(
|
||||
&mut self,
|
||||
src_acct_name: Option<&str>,
|
||||
amount: u64,
|
||||
minimum_confirmations: u64,
|
||||
max_outputs: usize,
|
||||
num_change_outputs: usize,
|
||||
selection_strategy_is_use_all: bool,
|
||||
message: Option<String>,
|
||||
) -> Result<(Slate, OutputLockFn<W, C, K>), Error> {
|
||||
let mut w = self.wallet.lock();
|
||||
w.open_with_credentials()?;
|
||||
let parent_key_id = match src_acct_name {
|
||||
Some(d) => {
|
||||
let pm = w.get_acct_path(d.to_owned())?;
|
||||
match pm {
|
||||
Some(p) => p.path,
|
||||
None => w.parent_key_id(),
|
||||
}
|
||||
}
|
||||
None => w.parent_key_id(),
|
||||
};
|
||||
|
||||
let message = match message {
|
||||
Some(mut m) => {
|
||||
m.truncate(USER_MESSAGE_MAX_LEN);
|
||||
Some(m)
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
let mut slate = tx::new_tx_slate(&mut *w, amount, 2)?;
|
||||
|
||||
let (context, lock_fn) = tx::add_inputs_to_slate(
|
||||
&mut *w,
|
||||
&mut slate,
|
||||
minimum_confirmations,
|
||||
max_outputs,
|
||||
num_change_outputs,
|
||||
selection_strategy_is_use_all,
|
||||
&parent_key_id,
|
||||
0,
|
||||
message,
|
||||
)?;
|
||||
|
||||
// Save the aggsig context in our DB for when we
|
||||
// recieve the transaction back
|
||||
{
|
||||
let mut batch = w.batch()?;
|
||||
batch.save_private_context(slate.id.as_bytes(), &context)?;
|
||||
batch.commit()?;
|
||||
}
|
||||
|
||||
w.close()?;
|
||||
Ok((slate, lock_fn))
|
||||
}
|
||||
|
||||
/// Estimates the amount to be locked and fee for the transaction without creating one
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `src_acct_name` - The human readable account name from which to draw outputs
|
||||
/// for the transaction, overriding whatever the active account is as set via the
|
||||
/// [`set_active_account`](struct.APIOwner.html#method.set_active_account) method.
|
||||
/// If None, the transaction will use the active account.
|
||||
/// * `amount` - The amount to send, in nanogrins. (`1 G = 1_000_000_000nG`)
|
||||
/// * `minimum_confirmations` - The minimum number of confirmations an output
|
||||
/// should have in order to be included in the transaction.
|
||||
/// * `max_outputs` - By default, the wallet selects as many inputs as possible in a
|
||||
/// transaction, to reduce the Output set and the fees. The wallet will attempt to spend
|
||||
/// include up to `max_outputs` in a transaction, however if this is not enough to cover
|
||||
/// the whole amount, the wallet will include more outputs. This parameter should be considered
|
||||
/// a soft limit.
|
||||
/// * `num_change_outputs` - The target number of change outputs to create in the transaction.
|
||||
/// The actual number created will be `num_change_outputs` + whatever remainder is needed.
|
||||
/// * `selection_strategy_is_use_all` - If `true`, attempt to use up as many outputs as
|
||||
/// possible to create the transaction, up the 'soft limit' of `max_outputs`. This helps
|
||||
/// to reduce the size of the UTXO set and the amount of data stored in the wallet, and
|
||||
/// minimizes fees. This will generally result in many inputs and a large change output(s),
|
||||
/// usually much larger than the amount being sent. If `false`, the transaction will include
|
||||
/// as many outputs as are needed to meet the amount, (and no more) starting with the smallest
|
||||
/// value outputs.
|
||||
///
|
||||
/// # Returns
|
||||
/// * a result containing:
|
||||
/// * (total, fee) - A tuple:
|
||||
/// * Total amount to be locked.
|
||||
/// * Transaction fee
|
||||
pub fn estimate_initiate_tx(
|
||||
&mut self,
|
||||
src_acct_name: Option<&str>,
|
||||
amount: u64,
|
||||
minimum_confirmations: u64,
|
||||
max_outputs: usize,
|
||||
num_change_outputs: usize,
|
||||
selection_strategy_is_use_all: bool,
|
||||
) -> Result<
|
||||
(
|
||||
u64, // total
|
||||
u64, // fee
|
||||
),
|
||||
Error,
|
||||
> {
|
||||
let mut w = self.wallet.lock();
|
||||
w.open_with_credentials()?;
|
||||
let parent_key_id = match src_acct_name {
|
||||
Some(d) => {
|
||||
let pm = w.get_acct_path(d.to_owned())?;
|
||||
match pm {
|
||||
Some(p) => p.path,
|
||||
None => w.parent_key_id(),
|
||||
}
|
||||
}
|
||||
None => w.parent_key_id(),
|
||||
};
|
||||
tx::estimate_send_tx(
|
||||
&mut *w,
|
||||
amount,
|
||||
minimum_confirmations,
|
||||
max_outputs,
|
||||
num_change_outputs,
|
||||
selection_strategy_is_use_all,
|
||||
&parent_key_id,
|
||||
)
|
||||
}
|
||||
|
||||
/// Lock outputs associated with a given slate/transaction
|
||||
pub fn tx_lock_outputs(
|
||||
&mut self,
|
||||
slate: &Slate,
|
||||
mut lock_fn: OutputLockFn<W, C, K>,
|
||||
) -> Result<(), Error> {
|
||||
let mut w = self.wallet.lock();
|
||||
w.open_with_credentials()?;
|
||||
lock_fn(&mut *w, &slate.tx, PhantomData, PhantomData)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sender finalization of the transaction. Takes the file returned by the
|
||||
/// sender as well as the private file generate on the first send step.
|
||||
/// Builds the complete transaction and sends it to a grin node for
|
||||
/// propagation.
|
||||
pub fn finalize_tx(&mut self, slate: &mut Slate) -> Result<(), Error> {
|
||||
let mut w = self.wallet.lock();
|
||||
w.open_with_credentials()?;
|
||||
let context = w.get_private_context(slate.id.as_bytes())?;
|
||||
tx::complete_tx(&mut *w, slate, 0, &context)?;
|
||||
tx::update_stored_tx(&mut *w, slate)?;
|
||||
tx::update_message(&mut *w, slate)?;
|
||||
{
|
||||
let mut batch = w.batch()?;
|
||||
batch.delete_private_context(slate.id.as_bytes())?;
|
||||
batch.commit()?;
|
||||
}
|
||||
w.close()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Roll back a transaction and all associated outputs with a given
|
||||
/// transaction id This means delete all change outputs, (or recipient
|
||||
/// output if you're recipient), and unlock all locked outputs associated
|
||||
/// with the transaction used when a transaction is created but never
|
||||
/// posted
|
||||
pub fn cancel_tx(
|
||||
&mut self,
|
||||
tx_id: Option<u32>,
|
||||
tx_slate_id: Option<Uuid>,
|
||||
) -> Result<(), Error> {
|
||||
let mut w = self.wallet.lock();
|
||||
w.open_with_credentials()?;
|
||||
let parent_key_id = w.parent_key_id();
|
||||
if !self.update_outputs(&mut w, false) {
|
||||
return Err(ErrorKind::TransactionCancellationError(
|
||||
"Can't contact running Grin node. Not Cancelling.",
|
||||
))?;
|
||||
}
|
||||
tx::cancel_tx(&mut *w, &parent_key_id, tx_id, tx_slate_id)?;
|
||||
w.close()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Retrieves a stored transaction from a TxLogEntry
|
||||
pub fn get_stored_tx(&self, entry: &TxLogEntry) -> Result<Option<Transaction>, Error> {
|
||||
let w = self.wallet.lock();
|
||||
w.get_stored_tx(entry)
|
||||
}
|
||||
|
||||
/// Posts a transaction to the chain
|
||||
pub fn post_tx(&self, tx: &Transaction, fluff: bool) -> Result<(), Error> {
|
||||
let tx_hex = util::to_hex(ser::ser_vec(tx).unwrap());
|
||||
let client = {
|
||||
let mut w = self.wallet.lock();
|
||||
w.w2n_client().clone()
|
||||
};
|
||||
let res = client.post_tx(&TxWrapper { tx_hex: tx_hex }, fluff);
|
||||
if let Err(e) = res {
|
||||
error!("api: post_tx: failed with error: {}", e);
|
||||
Err(e)
|
||||
} else {
|
||||
debug!(
|
||||
"api: post_tx: successfully posted tx: {}, fluff? {}",
|
||||
tx.hash(),
|
||||
fluff
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Verifies all messages in the slate match their public keys
|
||||
pub fn verify_slate_messages(&mut self, slate: &Slate) -> Result<(), Error> {
|
||||
let secp = Secp256k1::with_caps(ContextFlag::VerifyOnly);
|
||||
slate.verify_messages(&secp)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Attempt to restore contents of wallet
|
||||
pub fn restore(&mut self) -> Result<(), Error> {
|
||||
let mut w = self.wallet.lock();
|
||||
w.open_with_credentials()?;
|
||||
w.restore()?;
|
||||
w.close()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Attempt to check and fix the contents of the wallet
|
||||
pub fn check_repair(&mut self) -> Result<(), Error> {
|
||||
let mut w = self.wallet.lock();
|
||||
w.open_with_credentials()?;
|
||||
self.update_outputs(&mut w, true);
|
||||
w.check_repair()?;
|
||||
w.close()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Retrieve current height from node
|
||||
pub fn node_height(&mut self) -> Result<(u64, bool), Error> {
|
||||
let res = {
|
||||
let mut w = self.wallet.lock();
|
||||
w.open_with_credentials()?;
|
||||
w.w2n_client().get_chain_height()
|
||||
};
|
||||
match res {
|
||||
Ok(height) => Ok((height, true)),
|
||||
Err(_) => {
|
||||
let outputs = self.retrieve_outputs(true, false, None)?;
|
||||
let height = match outputs.1.iter().map(|(out, _)| out.height).max() {
|
||||
Some(height) => height,
|
||||
None => 0,
|
||||
};
|
||||
Ok((height, false))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to update outputs in wallet, return whether it was successful
|
||||
fn update_outputs(&self, w: &mut W, update_all: bool) -> bool {
|
||||
let parent_key_id = w.parent_key_id();
|
||||
match updater::refresh_outputs(&mut *w, &parent_key_id, update_all) {
|
||||
Ok(_) => true,
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper around external API functions, intended to communicate
|
||||
/// with other parties
|
||||
pub struct APIForeign<W: ?Sized, C, K>
|
||||
where
|
||||
W: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
/// Wallet, contains its keychain (TODO: Split these up into 2 traits
|
||||
/// perhaps)
|
||||
pub wallet: Arc<Mutex<W>>,
|
||||
phantom: PhantomData<K>,
|
||||
phantom_c: PhantomData<C>,
|
||||
}
|
||||
|
||||
impl<'a, W: ?Sized, C, K> APIForeign<W, C, K>
|
||||
where
|
||||
W: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
/// Create new API instance
|
||||
pub fn new(wallet_in: Arc<Mutex<W>>) -> Box<Self> {
|
||||
Box::new(APIForeign {
|
||||
wallet: wallet_in,
|
||||
phantom: PhantomData,
|
||||
phantom_c: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
/// Build a new (potential) coinbase transaction in the wallet
|
||||
pub fn build_coinbase(&mut self, block_fees: &BlockFees) -> Result<CbData, Error> {
|
||||
let mut w = self.wallet.lock();
|
||||
w.open_with_credentials()?;
|
||||
let res = updater::build_coinbase(&mut *w, block_fees);
|
||||
w.close()?;
|
||||
res
|
||||
}
|
||||
|
||||
/// Verifies all messages in the slate match their public keys
|
||||
pub fn verify_slate_messages(&mut self, slate: &Slate) -> Result<(), Error> {
|
||||
let secp = Secp256k1::with_caps(ContextFlag::VerifyOnly);
|
||||
slate.verify_messages(&secp)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Receive a transaction from a sender
|
||||
pub fn receive_tx(
|
||||
&mut self,
|
||||
slate: &mut Slate,
|
||||
dest_acct_name: Option<&str>,
|
||||
message: Option<String>,
|
||||
) -> Result<(), Error> {
|
||||
let mut w = self.wallet.lock();
|
||||
w.open_with_credentials()?;
|
||||
let parent_key_id = match dest_acct_name {
|
||||
Some(d) => {
|
||||
let pm = w.get_acct_path(d.to_owned())?;
|
||||
match pm {
|
||||
Some(p) => p.path,
|
||||
None => w.parent_key_id(),
|
||||
}
|
||||
}
|
||||
None => w.parent_key_id(),
|
||||
};
|
||||
// Don't do this multiple times
|
||||
let tx = updater::retrieve_txs(&mut *w, None, Some(slate.id), Some(&parent_key_id), false)?;
|
||||
for t in &tx {
|
||||
if t.tx_type == TxLogEntryType::TxReceived {
|
||||
return Err(ErrorKind::TransactionAlreadyReceived(slate.id.to_string()).into());
|
||||
}
|
||||
}
|
||||
|
||||
let message = match message {
|
||||
Some(mut m) => {
|
||||
m.truncate(USER_MESSAGE_MAX_LEN);
|
||||
Some(m)
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
let (_, mut create_fn) =
|
||||
tx::add_output_to_slate(&mut *w, slate, &parent_key_id, 1, message)?;
|
||||
create_fn(&mut *w, &slate.tx, PhantomData, PhantomData)?;
|
||||
tx::update_message(&mut *w, slate)?;
|
||||
w.close()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,300 @@
|
||||
// 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.
|
||||
|
||||
//! Error types for libwallet
|
||||
|
||||
use crate::core::core::{committed, transaction};
|
||||
use crate::core::libtx;
|
||||
use crate::keychain;
|
||||
use crate::util::secp;
|
||||
use failure::{Backtrace, Context, Fail};
|
||||
use std::env;
|
||||
use std::fmt::{self, Display};
|
||||
use std::io;
|
||||
|
||||
/// Error definition
|
||||
#[derive(Debug, Fail)]
|
||||
pub struct Error {
|
||||
inner: Context<ErrorKind>,
|
||||
}
|
||||
|
||||
/// Wallet errors, mostly wrappers around underlying crypto or I/O errors.
|
||||
#[derive(Clone, Eq, PartialEq, Debug, Fail)]
|
||||
pub enum ErrorKind {
|
||||
/// Not enough funds
|
||||
#[fail(
|
||||
display = "Not enough funds. Required: {}, Available: {}",
|
||||
needed_disp, available_disp
|
||||
)]
|
||||
NotEnoughFunds {
|
||||
/// available funds
|
||||
available: u64,
|
||||
/// Display friendly
|
||||
available_disp: String,
|
||||
/// Needed funds
|
||||
needed: u64,
|
||||
/// Display friendly
|
||||
needed_disp: String,
|
||||
},
|
||||
|
||||
/// Fee error
|
||||
#[fail(display = "Fee Error: {}", _0)]
|
||||
Fee(String),
|
||||
|
||||
/// LibTX Error
|
||||
#[fail(display = "LibTx Error")]
|
||||
LibTX(libtx::ErrorKind),
|
||||
|
||||
/// Keychain error
|
||||
#[fail(display = "Keychain error")]
|
||||
Keychain(keychain::Error),
|
||||
|
||||
/// Transaction Error
|
||||
#[fail(display = "Transaction error")]
|
||||
Transaction(transaction::Error),
|
||||
|
||||
/// API Error
|
||||
#[fail(display = "Client Callback Error: {}", _0)]
|
||||
ClientCallback(String),
|
||||
|
||||
/// Secp Error
|
||||
#[fail(display = "Secp error")]
|
||||
Secp(secp::Error),
|
||||
|
||||
/// Callback implementation error conversion
|
||||
#[fail(display = "Trait Implementation error")]
|
||||
CallbackImpl(&'static str),
|
||||
|
||||
/// Wallet backend error
|
||||
#[fail(display = "Wallet store error: {}", _0)]
|
||||
Backend(String),
|
||||
|
||||
/// Callback implementation error conversion
|
||||
#[fail(display = "Restore Error")]
|
||||
Restore,
|
||||
|
||||
/// An error in the format of the JSON structures exchanged by the wallet
|
||||
#[fail(display = "JSON format error: {}", _0)]
|
||||
Format(String),
|
||||
|
||||
/// Other serialization errors
|
||||
#[fail(display = "Ser/Deserialization error")]
|
||||
Deser(crate::core::ser::Error),
|
||||
|
||||
/// IO Error
|
||||
#[fail(display = "I/O error")]
|
||||
IO,
|
||||
|
||||
/// Error when contacting a node through its API
|
||||
#[fail(display = "Node API error")]
|
||||
Node,
|
||||
|
||||
/// Error contacting wallet API
|
||||
#[fail(display = "Wallet Communication Error: {}", _0)]
|
||||
WalletComms(String),
|
||||
|
||||
/// Error originating from hyper.
|
||||
#[fail(display = "Hyper error")]
|
||||
Hyper,
|
||||
|
||||
/// Error originating from hyper uri parsing.
|
||||
#[fail(display = "Uri parsing error")]
|
||||
Uri,
|
||||
|
||||
/// Signature error
|
||||
#[fail(display = "Signature error: {}", _0)]
|
||||
Signature(String),
|
||||
|
||||
/// Attempt to use duplicate transaction id in separate transactions
|
||||
#[fail(display = "Duplicate transaction ID error")]
|
||||
DuplicateTransactionId,
|
||||
|
||||
/// Wallet seed already exists
|
||||
#[fail(display = "Wallet seed exists error")]
|
||||
WalletSeedExists,
|
||||
|
||||
/// Wallet seed doesn't exist
|
||||
#[fail(display = "Wallet seed doesn't exist error")]
|
||||
WalletSeedDoesntExist,
|
||||
|
||||
/// Wallet seed doesn't exist
|
||||
#[fail(display = "Wallet seed decryption error")]
|
||||
WalletSeedDecryption,
|
||||
|
||||
/// Transaction doesn't exist
|
||||
#[fail(display = "Transaction {} doesn't exist", _0)]
|
||||
TransactionDoesntExist(String),
|
||||
|
||||
/// Transaction already rolled back
|
||||
#[fail(display = "Transaction {} cannot be cancelled", _0)]
|
||||
TransactionNotCancellable(String),
|
||||
|
||||
/// Cancellation error
|
||||
#[fail(display = "Cancellation Error: {}", _0)]
|
||||
TransactionCancellationError(&'static str),
|
||||
|
||||
/// Cancellation error
|
||||
#[fail(display = "Tx dump Error: {}", _0)]
|
||||
TransactionDumpError(&'static str),
|
||||
|
||||
/// Attempt to repost a transaction that's already confirmed
|
||||
#[fail(display = "Transaction already confirmed error")]
|
||||
TransactionAlreadyConfirmed,
|
||||
|
||||
/// Transaction has already been received
|
||||
#[fail(display = "Transaction {} has already been received", _0)]
|
||||
TransactionAlreadyReceived(String),
|
||||
|
||||
/// Attempt to repost a transaction that's not completed and stored
|
||||
#[fail(display = "Transaction building not completed: {}", _0)]
|
||||
TransactionBuildingNotCompleted(u32),
|
||||
|
||||
/// Invalid BIP-32 Depth
|
||||
#[fail(display = "Invalid BIP32 Depth (must be 1 or greater)")]
|
||||
InvalidBIP32Depth,
|
||||
|
||||
/// Attempt to add an account that exists
|
||||
#[fail(display = "Account Label '{}' already exists", _0)]
|
||||
AccountLabelAlreadyExists(String),
|
||||
|
||||
/// Reference unknown account label
|
||||
#[fail(display = "Unknown Account Label '{}'", _0)]
|
||||
UnknownAccountLabel(String),
|
||||
|
||||
/// Error from summing commitments via committed trait.
|
||||
#[fail(display = "Committed Error")]
|
||||
Committed(committed::Error),
|
||||
|
||||
/// Other
|
||||
#[fail(display = "Generic error: {}", _0)]
|
||||
GenericError(String),
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let show_bt = match env::var("RUST_BACKTRACE") {
|
||||
Ok(r) => {
|
||||
if r == "1" {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
Err(_) => false,
|
||||
};
|
||||
let backtrace = match self.backtrace() {
|
||||
Some(b) => format!("{}", b),
|
||||
None => String::from("Unknown"),
|
||||
};
|
||||
let inner_output = format!("{}", self.inner,);
|
||||
let backtrace_output = format!("\n Backtrace: {}", backtrace);
|
||||
let mut output = inner_output.clone();
|
||||
if show_bt {
|
||||
output.push_str(&backtrace_output);
|
||||
}
|
||||
Display::fmt(&output, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// get kind
|
||||
pub fn kind(&self) -> ErrorKind {
|
||||
self.inner.get_context().clone()
|
||||
}
|
||||
/// get cause string
|
||||
pub fn cause_string(&self) -> String {
|
||||
match self.cause() {
|
||||
Some(k) => format!("{}", k),
|
||||
None => format!("Unknown"),
|
||||
}
|
||||
}
|
||||
/// get cause
|
||||
pub fn cause(&self) -> Option<&dyn Fail> {
|
||||
self.inner.cause()
|
||||
}
|
||||
/// get backtrace
|
||||
pub fn backtrace(&self) -> Option<&Backtrace> {
|
||||
self.inner.backtrace()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ErrorKind> for Error {
|
||||
fn from(kind: ErrorKind) -> Error {
|
||||
Error {
|
||||
inner: Context::new(kind),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Context<ErrorKind>> for Error {
|
||||
fn from(inner: Context<ErrorKind>) -> Error {
|
||||
Error { inner: inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(_error: io::Error) -> Error {
|
||||
Error {
|
||||
inner: Context::new(ErrorKind::IO),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<keychain::Error> for Error {
|
||||
fn from(error: keychain::Error) -> Error {
|
||||
Error {
|
||||
inner: Context::new(ErrorKind::Keychain(error)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<libtx::Error> for Error {
|
||||
fn from(error: crate::core::libtx::Error) -> Error {
|
||||
Error {
|
||||
inner: Context::new(ErrorKind::LibTX(error.kind())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<transaction::Error> for Error {
|
||||
fn from(error: transaction::Error) -> Error {
|
||||
Error {
|
||||
inner: Context::new(ErrorKind::Transaction(error)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::core::ser::Error> for Error {
|
||||
fn from(error: crate::core::ser::Error) -> Error {
|
||||
Error {
|
||||
inner: Context::new(ErrorKind::Deser(error)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<secp::Error> for Error {
|
||||
fn from(error: secp::Error) -> Error {
|
||||
Error {
|
||||
inner: Context::new(ErrorKind::Secp(error)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<committed::Error> for Error {
|
||||
fn from(error: committed::Error) -> Error {
|
||||
Error {
|
||||
inner: Context::new(ErrorKind::Committed(error)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// 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.
|
||||
|
||||
//! lower-level wallet functions which build upon core::libtx to perform wallet
|
||||
//! operations
|
||||
|
||||
#![deny(non_upper_case_globals)]
|
||||
#![deny(non_camel_case_types)]
|
||||
#![deny(non_snake_case)]
|
||||
#![deny(unused_mut)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
pub mod keys;
|
||||
pub mod restore;
|
||||
pub mod selection;
|
||||
pub mod tx;
|
||||
pub mod updater;
|
||||
@@ -0,0 +1,120 @@
|
||||
// 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.
|
||||
|
||||
//! Wallet key management functions
|
||||
use crate::keychain::{ChildNumber, ExtKeychain, Identifier, Keychain};
|
||||
use crate::error::{Error, ErrorKind};
|
||||
use crate::types::{AcctPathMapping, NodeClient, WalletBackend};
|
||||
|
||||
/// Get next available key in the wallet for a given parent
|
||||
pub fn next_available_key<T: ?Sized, C, K>(wallet: &mut T) -> Result<Identifier, Error>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
let child = wallet.next_child()?;
|
||||
Ok(child)
|
||||
}
|
||||
|
||||
/// Retrieve an existing key from a wallet
|
||||
pub fn retrieve_existing_key<T: ?Sized, C, K>(
|
||||
wallet: &T,
|
||||
key_id: Identifier,
|
||||
mmr_index: Option<u64>,
|
||||
) -> Result<(Identifier, u32), Error>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
let existing = wallet.get(&key_id, &mmr_index)?;
|
||||
let key_id = existing.key_id.clone();
|
||||
let derivation = existing.n_child;
|
||||
Ok((key_id, derivation))
|
||||
}
|
||||
|
||||
/// Returns a list of account to BIP32 path mappings
|
||||
pub fn accounts<T: ?Sized, C, K>(wallet: &mut T) -> Result<Vec<AcctPathMapping>, Error>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
Ok(wallet.acct_path_iter().collect())
|
||||
}
|
||||
|
||||
/// Adds an new parent account path with a given label
|
||||
pub fn new_acct_path<T: ?Sized, C, K>(wallet: &mut T, label: &str) -> Result<Identifier, Error>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
let label = label.to_owned();
|
||||
if let Some(_) = wallet.acct_path_iter().find(|l| l.label == label) {
|
||||
return Err(ErrorKind::AccountLabelAlreadyExists(label.clone()).into());
|
||||
}
|
||||
|
||||
// We're always using paths at m/k/0 for parent keys for output derivations
|
||||
// so find the highest of those, then increment (to conform with external/internal
|
||||
// derivation chains in BIP32 spec)
|
||||
|
||||
let highest_entry = wallet.acct_path_iter().max_by(|a, b| {
|
||||
<u32>::from(a.path.to_path().path[0]).cmp(&<u32>::from(b.path.to_path().path[0]))
|
||||
});
|
||||
|
||||
let return_id = {
|
||||
if let Some(e) = highest_entry {
|
||||
let mut p = e.path.to_path();
|
||||
p.path[0] = ChildNumber::from(<u32>::from(p.path[0]) + 1);
|
||||
p.to_identifier()
|
||||
} else {
|
||||
ExtKeychain::derive_key_id(2, 0, 0, 0, 0)
|
||||
}
|
||||
};
|
||||
|
||||
let save_path = AcctPathMapping {
|
||||
label: label.to_owned(),
|
||||
path: return_id.clone(),
|
||||
};
|
||||
|
||||
let mut batch = wallet.batch()?;
|
||||
batch.save_acct_path(save_path)?;
|
||||
batch.commit()?;
|
||||
Ok(return_id)
|
||||
}
|
||||
|
||||
/// Adds/sets a particular account path with a given label
|
||||
pub fn set_acct_path<T: ?Sized, C, K>(
|
||||
wallet: &mut T,
|
||||
label: &str,
|
||||
path: &Identifier,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
let label = label.to_owned();
|
||||
let save_path = AcctPathMapping {
|
||||
label: label.to_owned(),
|
||||
path: path.clone(),
|
||||
};
|
||||
|
||||
let mut batch = wallet.batch()?;
|
||||
batch.save_acct_path(save_path)?;
|
||||
batch.commit()?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,452 @@
|
||||
// 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 to restore a wallet's outputs from just the master seed
|
||||
|
||||
use crate::core::global;
|
||||
use crate::core::libtx::proof;
|
||||
use crate::keychain::{ExtKeychain, Identifier, Keychain};
|
||||
use crate::internal::{keys, updater};
|
||||
use crate::types::*;
|
||||
use crate::Error;
|
||||
use crate::util::secp::{key::SecretKey, pedersen};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Utility struct for return values from below
|
||||
#[derive(Clone)]
|
||||
struct OutputResult {
|
||||
///
|
||||
pub commit: pedersen::Commitment,
|
||||
///
|
||||
pub key_id: Identifier,
|
||||
///
|
||||
pub n_child: u32,
|
||||
///
|
||||
pub mmr_index: u64,
|
||||
///
|
||||
pub value: u64,
|
||||
///
|
||||
pub height: u64,
|
||||
///
|
||||
pub lock_height: u64,
|
||||
///
|
||||
pub is_coinbase: bool,
|
||||
///
|
||||
pub blinding: SecretKey,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
/// Collect stats in case we want to just output a single tx log entry
|
||||
/// for restored non-coinbase outputs
|
||||
struct RestoredTxStats {
|
||||
///
|
||||
pub log_id: u32,
|
||||
///
|
||||
pub amount_credited: u64,
|
||||
///
|
||||
pub num_outputs: usize,
|
||||
}
|
||||
|
||||
fn identify_utxo_outputs<T, C, K>(
|
||||
wallet: &mut T,
|
||||
outputs: Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64, u64)>,
|
||||
) -> Result<Vec<OutputResult>, Error>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
let mut wallet_outputs: Vec<OutputResult> = Vec::new();
|
||||
|
||||
warn!(
|
||||
"Scanning {} outputs in the current Grin utxo set",
|
||||
outputs.len(),
|
||||
);
|
||||
|
||||
for output in outputs.iter() {
|
||||
let (commit, proof, is_coinbase, height, mmr_index) = output;
|
||||
// attempt to unwind message from the RP and get a value
|
||||
// will fail if it's not ours
|
||||
let info = proof::rewind(wallet.keychain(), *commit, None, *proof)?;
|
||||
|
||||
if !info.success {
|
||||
continue;
|
||||
}
|
||||
|
||||
let lock_height = if *is_coinbase {
|
||||
*height + global::coinbase_maturity()
|
||||
} else {
|
||||
*height
|
||||
};
|
||||
|
||||
// TODO: Output paths are always going to be length 3 for now, but easy enough to grind
|
||||
// through to find the right path if required later
|
||||
let key_id = Identifier::from_serialized_path(3u8, &info.message.as_bytes());
|
||||
|
||||
info!(
|
||||
"Output found: {:?}, amount: {:?}, key_id: {:?}, mmr_index: {},",
|
||||
commit, info.value, key_id, mmr_index,
|
||||
);
|
||||
|
||||
wallet_outputs.push(OutputResult {
|
||||
commit: *commit,
|
||||
key_id: key_id.clone(),
|
||||
n_child: key_id.to_path().last_path_index(),
|
||||
value: info.value,
|
||||
height: *height,
|
||||
lock_height: lock_height,
|
||||
is_coinbase: *is_coinbase,
|
||||
blinding: info.blinding,
|
||||
mmr_index: *mmr_index,
|
||||
});
|
||||
}
|
||||
Ok(wallet_outputs)
|
||||
}
|
||||
|
||||
fn collect_chain_outputs<T, C, K>(wallet: &mut T) -> Result<Vec<OutputResult>, Error>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
let batch_size = 1000;
|
||||
let mut start_index = 1;
|
||||
let mut result_vec: Vec<OutputResult> = vec![];
|
||||
loop {
|
||||
let (highest_index, last_retrieved_index, outputs) = wallet
|
||||
.w2n_client()
|
||||
.get_outputs_by_pmmr_index(start_index, batch_size)?;
|
||||
warn!(
|
||||
"Checking {} outputs, up to index {}. (Highest index: {})",
|
||||
outputs.len(),
|
||||
highest_index,
|
||||
last_retrieved_index,
|
||||
);
|
||||
|
||||
result_vec.append(&mut identify_utxo_outputs(wallet, outputs.clone())?);
|
||||
|
||||
if highest_index == last_retrieved_index {
|
||||
break;
|
||||
}
|
||||
start_index = last_retrieved_index + 1;
|
||||
}
|
||||
Ok(result_vec)
|
||||
}
|
||||
|
||||
///
|
||||
fn restore_missing_output<T, C, K>(
|
||||
wallet: &mut T,
|
||||
output: OutputResult,
|
||||
found_parents: &mut HashMap<Identifier, u32>,
|
||||
tx_stats: &mut Option<&mut HashMap<Identifier, RestoredTxStats>>,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
let commit = wallet.calc_commit_for_cache(output.value, &output.key_id)?;
|
||||
let mut batch = wallet.batch()?;
|
||||
|
||||
let parent_key_id = output.key_id.parent_path();
|
||||
if !found_parents.contains_key(&parent_key_id) {
|
||||
found_parents.insert(parent_key_id.clone(), 0);
|
||||
if let Some(ref mut s) = tx_stats {
|
||||
s.insert(
|
||||
parent_key_id.clone(),
|
||||
RestoredTxStats {
|
||||
log_id: batch.next_tx_log_id(&parent_key_id)?,
|
||||
amount_credited: 0,
|
||||
num_outputs: 0,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let log_id = if tx_stats.is_none() || output.is_coinbase {
|
||||
let log_id = batch.next_tx_log_id(&parent_key_id)?;
|
||||
let entry_type = match output.is_coinbase {
|
||||
true => TxLogEntryType::ConfirmedCoinbase,
|
||||
false => TxLogEntryType::TxReceived,
|
||||
};
|
||||
let mut t = TxLogEntry::new(parent_key_id.clone(), entry_type, log_id);
|
||||
t.confirmed = true;
|
||||
t.amount_credited = output.value;
|
||||
t.num_outputs = 1;
|
||||
t.update_confirmation_ts();
|
||||
batch.save_tx_log_entry(t, &parent_key_id)?;
|
||||
log_id
|
||||
} else {
|
||||
if let Some(ref mut s) = tx_stats {
|
||||
let ts = s.get(&parent_key_id).unwrap().clone();
|
||||
s.insert(
|
||||
parent_key_id.clone(),
|
||||
RestoredTxStats {
|
||||
log_id: ts.log_id,
|
||||
amount_credited: ts.amount_credited + output.value,
|
||||
num_outputs: ts.num_outputs + 1,
|
||||
},
|
||||
);
|
||||
ts.log_id
|
||||
} else {
|
||||
0
|
||||
}
|
||||
};
|
||||
|
||||
let _ = batch.save(OutputData {
|
||||
root_key_id: parent_key_id.clone(),
|
||||
key_id: output.key_id,
|
||||
n_child: output.n_child,
|
||||
mmr_index: Some(output.mmr_index),
|
||||
commit: commit,
|
||||
value: output.value,
|
||||
status: OutputStatus::Unspent,
|
||||
height: output.height,
|
||||
lock_height: output.lock_height,
|
||||
is_coinbase: output.is_coinbase,
|
||||
tx_log_entry: Some(log_id),
|
||||
});
|
||||
|
||||
let max_child_index = found_parents.get(&parent_key_id).unwrap().clone();
|
||||
if output.n_child >= max_child_index {
|
||||
found_parents.insert(parent_key_id.clone(), output.n_child);
|
||||
}
|
||||
|
||||
batch.commit()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
fn cancel_tx_log_entry<T, C, K>(wallet: &mut T, output: &OutputData) -> Result<(), Error>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
let parent_key_id = output.key_id.parent_path();
|
||||
let updated_tx_entry = if output.tx_log_entry.is_some() {
|
||||
let entries = updater::retrieve_txs(
|
||||
wallet,
|
||||
output.tx_log_entry.clone(),
|
||||
None,
|
||||
Some(&parent_key_id),
|
||||
false,
|
||||
)?;
|
||||
if entries.len() > 0 {
|
||||
let mut entry = entries[0].clone();
|
||||
match entry.tx_type {
|
||||
TxLogEntryType::TxSent => entry.tx_type = TxLogEntryType::TxSentCancelled,
|
||||
TxLogEntryType::TxReceived => entry.tx_type = TxLogEntryType::TxReceivedCancelled,
|
||||
_ => {}
|
||||
}
|
||||
Some(entry)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut batch = wallet.batch()?;
|
||||
if let Some(t) = updated_tx_entry {
|
||||
batch.save_tx_log_entry(t, &parent_key_id)?;
|
||||
}
|
||||
batch.commit()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check / repair wallet contents
|
||||
/// assume wallet contents have been freshly updated with contents
|
||||
/// of latest block
|
||||
pub fn check_repair<T, C, K>(wallet: &mut T) -> Result<(), Error>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
// First, get a definitive list of outputs we own from the chain
|
||||
warn!("Starting wallet check.");
|
||||
let chain_outs = collect_chain_outputs(wallet)?;
|
||||
warn!(
|
||||
"Identified {} wallet_outputs as belonging to this wallet",
|
||||
chain_outs.len(),
|
||||
);
|
||||
|
||||
// Now, get all outputs owned by this wallet (regardless of account)
|
||||
let wallet_outputs = {
|
||||
let res = updater::retrieve_outputs(&mut *wallet, true, None, None)?;
|
||||
res
|
||||
};
|
||||
|
||||
let mut missing_outs = vec![];
|
||||
let mut accidental_spend_outs = vec![];
|
||||
let mut locked_outs = vec![];
|
||||
|
||||
// check all definitive outputs exist in the wallet outputs
|
||||
for deffo in chain_outs.into_iter() {
|
||||
let matched_out = wallet_outputs.iter().find(|wo| wo.1 == deffo.commit);
|
||||
match matched_out {
|
||||
Some(s) => {
|
||||
if s.0.status == OutputStatus::Spent {
|
||||
accidental_spend_outs.push((s.0.clone(), deffo.clone()));
|
||||
}
|
||||
if s.0.status == OutputStatus::Locked {
|
||||
locked_outs.push((s.0.clone(), deffo.clone()));
|
||||
}
|
||||
}
|
||||
None => missing_outs.push(deffo),
|
||||
}
|
||||
}
|
||||
|
||||
// mark problem spent outputs as unspent (confirmed against a short-lived fork, for example)
|
||||
for m in accidental_spend_outs.into_iter() {
|
||||
let mut o = m.0;
|
||||
warn!(
|
||||
"Output for {} with ID {} ({:?}) marked as spent but exists in UTXO set. \
|
||||
Marking unspent and cancelling any associated transaction log entries.",
|
||||
o.value, o.key_id, m.1.commit,
|
||||
);
|
||||
o.status = OutputStatus::Unspent;
|
||||
// any transactions associated with this should be cancelled
|
||||
cancel_tx_log_entry(wallet, &o)?;
|
||||
let mut batch = wallet.batch()?;
|
||||
batch.save(o)?;
|
||||
batch.commit()?;
|
||||
}
|
||||
|
||||
let mut found_parents: HashMap<Identifier, u32> = HashMap::new();
|
||||
|
||||
// Restore missing outputs, adding transaction for it back to the log
|
||||
for m in missing_outs.into_iter() {
|
||||
warn!(
|
||||
"Confirmed output for {} with ID {} ({:?}) exists in UTXO set but not in wallet. \
|
||||
Restoring.",
|
||||
m.value, m.key_id, m.commit,
|
||||
);
|
||||
restore_missing_output(wallet, m, &mut found_parents, &mut None)?;
|
||||
}
|
||||
|
||||
// Unlock locked outputs
|
||||
for m in locked_outs.into_iter() {
|
||||
let mut o = m.0;
|
||||
warn!(
|
||||
"Confirmed output for {} with ID {} ({:?}) exists in UTXO set and is locked. \
|
||||
Unlocking and cancelling associated transaction log entries.",
|
||||
o.value, o.key_id, m.1.commit,
|
||||
);
|
||||
o.status = OutputStatus::Unspent;
|
||||
cancel_tx_log_entry(wallet, &o)?;
|
||||
let mut batch = wallet.batch()?;
|
||||
batch.save(o)?;
|
||||
batch.commit()?;
|
||||
}
|
||||
|
||||
let unconfirmed_outs: Vec<&(OutputData, pedersen::Commitment)> = wallet_outputs
|
||||
.iter()
|
||||
.filter(|o| o.0.status == OutputStatus::Unconfirmed)
|
||||
.collect();
|
||||
// Delete unconfirmed outputs
|
||||
for m in unconfirmed_outs.into_iter() {
|
||||
let o = m.0.clone();
|
||||
warn!(
|
||||
"Unconfirmed output for {} with ID {} ({:?}) not in UTXO set. \
|
||||
Deleting and cancelling associated transaction log entries.",
|
||||
o.value, o.key_id, m.1,
|
||||
);
|
||||
cancel_tx_log_entry(wallet, &o)?;
|
||||
let mut batch = wallet.batch()?;
|
||||
batch.delete(&o.key_id, &o.mmr_index)?;
|
||||
batch.commit()?;
|
||||
}
|
||||
|
||||
// restore labels, account paths and child derivation indices
|
||||
let label_base = "account";
|
||||
let mut acct_index = 1;
|
||||
for (path, max_child_index) in found_parents.iter() {
|
||||
// default path already exists
|
||||
if *path != ExtKeychain::derive_key_id(2, 0, 0, 0, 0) {
|
||||
let label = format!("{}_{}", label_base, acct_index);
|
||||
keys::set_acct_path(wallet, &label, path)?;
|
||||
acct_index += 1;
|
||||
}
|
||||
let mut batch = wallet.batch()?;
|
||||
debug!("Next child for account {} is {}", path, max_child_index + 1);
|
||||
batch.save_child_index(path, max_child_index + 1)?;
|
||||
batch.commit()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Restore a wallet
|
||||
pub fn restore<T, C, K>(wallet: &mut T) -> Result<(), Error>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
// Don't proceed if wallet_data has anything in it
|
||||
let is_empty = wallet.iter().next().is_none();
|
||||
if !is_empty {
|
||||
error!("Not restoring. Please back up and remove existing db directory first.");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
warn!("Starting restore.");
|
||||
|
||||
let result_vec = collect_chain_outputs(wallet)?;
|
||||
|
||||
warn!(
|
||||
"Identified {} wallet_outputs as belonging to this wallet",
|
||||
result_vec.len(),
|
||||
);
|
||||
|
||||
let mut found_parents: HashMap<Identifier, u32> = HashMap::new();
|
||||
let mut restore_stats = HashMap::new();
|
||||
|
||||
// Now save what we have
|
||||
for output in result_vec {
|
||||
restore_missing_output(
|
||||
wallet,
|
||||
output,
|
||||
&mut found_parents,
|
||||
&mut Some(&mut restore_stats),
|
||||
)?;
|
||||
}
|
||||
|
||||
// restore labels, account paths and child derivation indices
|
||||
let label_base = "account";
|
||||
let mut acct_index = 1;
|
||||
for (path, max_child_index) in found_parents.iter() {
|
||||
// default path already exists
|
||||
if *path != ExtKeychain::derive_key_id(2, 0, 0, 0, 0) {
|
||||
let label = format!("{}_{}", label_base, acct_index);
|
||||
keys::set_acct_path(wallet, &label, path)?;
|
||||
acct_index += 1;
|
||||
}
|
||||
// restore tx log entry for non-coinbase outputs
|
||||
if let Some(s) = restore_stats.get(path) {
|
||||
let mut batch = wallet.batch()?;
|
||||
let mut t = TxLogEntry::new(path.clone(), TxLogEntryType::TxReceived, s.log_id);
|
||||
t.confirmed = true;
|
||||
t.amount_credited = s.amount_credited;
|
||||
t.num_outputs = s.num_outputs;
|
||||
t.update_confirmation_ts();
|
||||
batch.save_tx_log_entry(t, &path)?;
|
||||
batch.commit()?;
|
||||
}
|
||||
let mut batch = wallet.batch()?;
|
||||
batch.save_child_index(path, max_child_index + 1)?;
|
||||
debug!("Next child for account {} is {}", path, max_child_index + 1);
|
||||
batch.commit()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,547 @@
|
||||
// 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 crate::core::core::{amount_to_hr_string, Transaction};
|
||||
use crate::core::libtx::{build, tx_fee};
|
||||
use crate::keychain::{Identifier, Keychain};
|
||||
use crate::error::{Error, ErrorKind};
|
||||
use crate::internal::keys;
|
||||
use crate::slate::Slate;
|
||||
use crate::types::*;
|
||||
use std::collections::HashMap;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// Initialize a transaction on the sender side, returns a corresponding
|
||||
/// libwallet transaction slate with the appropriate inputs selected,
|
||||
/// and saves the private wallet identifiers of our selected outputs
|
||||
/// into our transaction context
|
||||
|
||||
pub fn build_send_tx<T: ?Sized, C, K>(
|
||||
wallet: &mut T,
|
||||
slate: &mut Slate,
|
||||
minimum_confirmations: u64,
|
||||
max_outputs: usize,
|
||||
change_outputs: usize,
|
||||
selection_strategy_is_use_all: bool,
|
||||
parent_key_id: Identifier,
|
||||
) -> Result<(Context, OutputLockFn<T, C, K>), Error>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
let (elems, inputs, change_amounts_derivations, fee) = select_send_tx(
|
||||
wallet,
|
||||
slate.amount,
|
||||
slate.height,
|
||||
minimum_confirmations,
|
||||
slate.lock_height,
|
||||
max_outputs,
|
||||
change_outputs,
|
||||
selection_strategy_is_use_all,
|
||||
&parent_key_id,
|
||||
)?;
|
||||
|
||||
slate.fee = fee;
|
||||
|
||||
let keychain = wallet.keychain().clone();
|
||||
let blinding = slate.add_transaction_elements(&keychain, elems)?;
|
||||
|
||||
// Create our own private context
|
||||
let mut context = Context::new(
|
||||
wallet.keychain().secp(),
|
||||
blinding.secret_key(&keychain.secp()).unwrap(),
|
||||
);
|
||||
|
||||
// Store our private identifiers for each input
|
||||
for input in inputs {
|
||||
context.add_input(&input.key_id, &input.mmr_index);
|
||||
}
|
||||
|
||||
let mut commits: HashMap<Identifier, Option<String>> = HashMap::new();
|
||||
|
||||
// Store change output(s) and cached commits
|
||||
for (change_amount, id, mmr_index) in &change_amounts_derivations {
|
||||
context.add_output(&id, &mmr_index);
|
||||
commits.insert(
|
||||
id.clone(),
|
||||
wallet.calc_commit_for_cache(*change_amount, &id)?,
|
||||
);
|
||||
}
|
||||
|
||||
let lock_inputs_in = context.get_inputs().clone();
|
||||
let _lock_outputs = context.get_outputs().clone();
|
||||
let messages_in = Some(slate.participant_messages());
|
||||
let slate_id_in = slate.id.clone();
|
||||
let height_in = slate.height;
|
||||
|
||||
// Return a closure to acquire wallet lock and lock the coins being spent
|
||||
// so we avoid accidental double spend attempt.
|
||||
let update_sender_wallet_fn =
|
||||
move |wallet: &mut T, tx: &Transaction, _: PhantomData<C>, _: PhantomData<K>| {
|
||||
let tx_entry = {
|
||||
// These ensure the closure remains FnMut
|
||||
let lock_inputs = lock_inputs_in.clone();
|
||||
let messages = messages_in.clone();
|
||||
let slate_id = slate_id_in.clone();
|
||||
let height = height_in.clone();
|
||||
let mut batch = wallet.batch()?;
|
||||
let log_id = batch.next_tx_log_id(&parent_key_id)?;
|
||||
let mut t = TxLogEntry::new(parent_key_id.clone(), TxLogEntryType::TxSent, log_id);
|
||||
t.tx_slate_id = Some(slate_id.clone());
|
||||
let filename = format!("{}.grintx", slate_id);
|
||||
t.stored_tx = Some(filename);
|
||||
t.fee = Some(fee);
|
||||
let mut amount_debited = 0;
|
||||
t.num_inputs = lock_inputs.len();
|
||||
for id in lock_inputs {
|
||||
let mut coin = batch.get(&id.0, &id.1).unwrap();
|
||||
coin.tx_log_entry = Some(log_id);
|
||||
amount_debited = amount_debited + coin.value;
|
||||
batch.lock_output(&mut coin)?;
|
||||
}
|
||||
|
||||
t.amount_debited = amount_debited;
|
||||
t.messages = messages;
|
||||
|
||||
// write the output representing our change
|
||||
for (change_amount, id, _) in &change_amounts_derivations {
|
||||
t.num_outputs += 1;
|
||||
t.amount_credited += change_amount;
|
||||
let commit = commits.get(&id).unwrap().clone();
|
||||
batch.save(OutputData {
|
||||
root_key_id: parent_key_id.clone(),
|
||||
key_id: id.clone(),
|
||||
n_child: id.to_path().last_path_index(),
|
||||
commit: commit,
|
||||
mmr_index: None,
|
||||
value: change_amount.clone(),
|
||||
status: OutputStatus::Unconfirmed,
|
||||
height: height,
|
||||
lock_height: 0,
|
||||
is_coinbase: false,
|
||||
tx_log_entry: Some(log_id),
|
||||
})?;
|
||||
}
|
||||
batch.save_tx_log_entry(t.clone(), &parent_key_id)?;
|
||||
batch.commit()?;
|
||||
t
|
||||
};
|
||||
wallet.store_tx(&format!("{}", tx_entry.tx_slate_id.unwrap()), tx)?;
|
||||
Ok(())
|
||||
};
|
||||
|
||||
Ok((context, Box::new(update_sender_wallet_fn)))
|
||||
}
|
||||
|
||||
/// Creates a new output in the wallet for the recipient,
|
||||
/// returning the key of the fresh output and a closure
|
||||
/// that actually performs the addition of the output to the
|
||||
/// wallet
|
||||
pub fn build_recipient_output<T: ?Sized, C, K>(
|
||||
wallet: &mut T,
|
||||
slate: &mut Slate,
|
||||
parent_key_id: Identifier,
|
||||
) -> Result<(Identifier, Context, OutputLockFn<T, C, K>), Error>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
// Create a potential output for this transaction
|
||||
let key_id = keys::next_available_key(wallet).unwrap();
|
||||
|
||||
let keychain = wallet.keychain().clone();
|
||||
let key_id_inner = key_id.clone();
|
||||
let amount = slate.amount;
|
||||
let height = slate.height;
|
||||
|
||||
let slate_id = slate.id.clone();
|
||||
let blinding =
|
||||
slate.add_transaction_elements(&keychain, vec![build::output(amount, key_id.clone())])?;
|
||||
|
||||
// Add blinding sum to our context
|
||||
let mut context = Context::new(
|
||||
keychain.secp(),
|
||||
blinding
|
||||
.secret_key(wallet.keychain().clone().secp())
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
context.add_output(&key_id, &None);
|
||||
let messages_in = Some(slate.participant_messages());
|
||||
|
||||
// Create closure that adds the output to recipient's wallet
|
||||
// (up to the caller to decide when to do)
|
||||
let wallet_add_fn =
|
||||
move |wallet: &mut T, _tx: &Transaction, _: PhantomData<C>, _: PhantomData<K>| {
|
||||
// Ensure closure remains FnMut
|
||||
let messages = messages_in.clone();
|
||||
let commit = wallet.calc_commit_for_cache(amount, &key_id_inner)?;
|
||||
let mut batch = wallet.batch()?;
|
||||
let log_id = batch.next_tx_log_id(&parent_key_id)?;
|
||||
let mut t = TxLogEntry::new(parent_key_id.clone(), TxLogEntryType::TxReceived, log_id);
|
||||
t.tx_slate_id = Some(slate_id);
|
||||
t.amount_credited = amount;
|
||||
t.num_outputs = 1;
|
||||
t.messages = messages;
|
||||
batch.save(OutputData {
|
||||
root_key_id: parent_key_id.clone(),
|
||||
key_id: key_id_inner.clone(),
|
||||
mmr_index: None,
|
||||
n_child: key_id_inner.to_path().last_path_index(),
|
||||
commit: commit,
|
||||
value: amount,
|
||||
status: OutputStatus::Unconfirmed,
|
||||
height: height,
|
||||
lock_height: 0,
|
||||
is_coinbase: false,
|
||||
tx_log_entry: Some(log_id),
|
||||
})?;
|
||||
batch.save_tx_log_entry(t, &parent_key_id)?;
|
||||
batch.commit()?;
|
||||
//TODO: Check whether we want to call this
|
||||
//wallet.store_tx(&format!("{}", t.tx_slate_id.unwrap()), tx)?;
|
||||
Ok(())
|
||||
};
|
||||
Ok((key_id, context, Box::new(wallet_add_fn)))
|
||||
}
|
||||
|
||||
/// 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 select_send_tx<T: ?Sized, C, K>(
|
||||
wallet: &mut T,
|
||||
amount: u64,
|
||||
current_height: u64,
|
||||
minimum_confirmations: u64,
|
||||
lock_height: u64,
|
||||
max_outputs: usize,
|
||||
change_outputs: usize,
|
||||
selection_strategy_is_use_all: bool,
|
||||
parent_key_id: &Identifier,
|
||||
) -> Result<
|
||||
(
|
||||
Vec<Box<build::Append<K>>>,
|
||||
Vec<OutputData>,
|
||||
Vec<(u64, Identifier, Option<u64>)>, // change amounts and derivations
|
||||
u64, // fee
|
||||
),
|
||||
Error,
|
||||
>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
let (coins, _total, amount, fee) = select_coins_and_fee(
|
||||
wallet,
|
||||
amount,
|
||||
current_height,
|
||||
minimum_confirmations,
|
||||
max_outputs,
|
||||
change_outputs,
|
||||
selection_strategy_is_use_all,
|
||||
&parent_key_id,
|
||||
)?;
|
||||
|
||||
// build transaction skeleton with inputs and change
|
||||
let (mut parts, change_amounts_derivations) =
|
||||
inputs_and_change(&coins, wallet, amount, fee, change_outputs)?;
|
||||
|
||||
// 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));
|
||||
|
||||
Ok((parts, coins, change_amounts_derivations, fee))
|
||||
}
|
||||
|
||||
/// Select outputs and calculating fee.
|
||||
pub fn select_coins_and_fee<T: ?Sized, C, K>(
|
||||
wallet: &mut T,
|
||||
amount: u64,
|
||||
current_height: u64,
|
||||
minimum_confirmations: u64,
|
||||
max_outputs: usize,
|
||||
change_outputs: usize,
|
||||
selection_strategy_is_use_all: bool,
|
||||
parent_key_id: &Identifier,
|
||||
) -> Result<
|
||||
(
|
||||
Vec<OutputData>,
|
||||
u64, // total
|
||||
u64, // amount
|
||||
u64, // fee
|
||||
),
|
||||
Error,
|
||||
>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
// select some spendable coins from the wallet
|
||||
let (max_outputs, mut coins) = select_coins(
|
||||
wallet,
|
||||
amount,
|
||||
current_height,
|
||||
minimum_confirmations,
|
||||
max_outputs,
|
||||
selection_strategy_is_use_all,
|
||||
parent_key_id,
|
||||
);
|
||||
|
||||
// sender is responsible for setting the fee on the partial tx
|
||||
// recipient should double check the fee calculation and not blindly trust the
|
||||
// sender
|
||||
|
||||
// TODO - Is it safe to spend without a change output? (1 input -> 1 output)
|
||||
// TODO - Does this not potentially reveal the senders private key?
|
||||
//
|
||||
// First attempt to spend without change
|
||||
let mut fee = tx_fee(coins.len(), 1, 1, 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 {
|
||||
available: 0,
|
||||
available_disp: amount_to_hr_string(0, false),
|
||||
needed: amount_with_fee as u64,
|
||||
needed_disp: amount_to_hr_string(amount_with_fee as u64, false),
|
||||
})?;
|
||||
}
|
||||
|
||||
// The amount with fee is more than the total values of our max outputs
|
||||
if total < amount_with_fee && coins.len() == max_outputs {
|
||||
return Err(ErrorKind::NotEnoughFunds {
|
||||
available: total,
|
||||
available_disp: amount_to_hr_string(total, false),
|
||||
needed: amount_with_fee as u64,
|
||||
needed_disp: amount_to_hr_string(amount_with_fee as u64, false),
|
||||
})?;
|
||||
}
|
||||
|
||||
let num_outputs = change_outputs + 1;
|
||||
|
||||
// We need to add a change address or amount with fee is more than total
|
||||
if total != amount_with_fee {
|
||||
fee = tx_fee(coins.len(), num_outputs, 1, 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 {
|
||||
available: total as u64,
|
||||
available_disp: amount_to_hr_string(total, false),
|
||||
needed: amount_with_fee as u64,
|
||||
needed_disp: amount_to_hr_string(amount_with_fee as u64, false),
|
||||
})?;
|
||||
}
|
||||
|
||||
// select some spendable coins from the wallet
|
||||
coins = select_coins(
|
||||
wallet,
|
||||
amount_with_fee,
|
||||
current_height,
|
||||
minimum_confirmations,
|
||||
max_outputs,
|
||||
selection_strategy_is_use_all,
|
||||
parent_key_id,
|
||||
)
|
||||
.1;
|
||||
fee = tx_fee(coins.len(), num_outputs, 1, None);
|
||||
total = coins.iter().map(|c| c.value).sum();
|
||||
amount_with_fee = amount + fee;
|
||||
}
|
||||
}
|
||||
Ok((coins, total, amount, fee))
|
||||
}
|
||||
|
||||
/// Selects inputs and change for a transaction
|
||||
pub fn inputs_and_change<T: ?Sized, C, K>(
|
||||
coins: &Vec<OutputData>,
|
||||
wallet: &mut T,
|
||||
amount: u64,
|
||||
fee: u64,
|
||||
num_change_outputs: usize,
|
||||
) -> Result<
|
||||
(
|
||||
Vec<Box<build::Append<K>>>,
|
||||
Vec<(u64, Identifier, Option<u64>)>,
|
||||
),
|
||||
Error,
|
||||
>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
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 {
|
||||
if coin.is_coinbase {
|
||||
parts.push(build::coinbase_input(coin.value, coin.key_id.clone()));
|
||||
} else {
|
||||
parts.push(build::input(coin.value, coin.key_id.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
let mut change_amounts_derivations = vec![];
|
||||
|
||||
if change == 0 {
|
||||
debug!("No change (sending exactly amount + fee), no change outputs to build");
|
||||
} else {
|
||||
debug!(
|
||||
"Building change outputs: total change: {} ({} outputs)",
|
||||
change, num_change_outputs
|
||||
);
|
||||
|
||||
let part_change = change / num_change_outputs as u64;
|
||||
let remainder_change = change % part_change;
|
||||
|
||||
for x in 0..num_change_outputs {
|
||||
// n-1 equal change_outputs and a final one accounting for any remainder
|
||||
let change_amount = if x == (num_change_outputs - 1) {
|
||||
part_change + remainder_change
|
||||
} else {
|
||||
part_change
|
||||
};
|
||||
|
||||
let change_key = wallet.next_child().unwrap();
|
||||
|
||||
change_amounts_derivations.push((change_amount, change_key.clone(), None));
|
||||
parts.push(build::output(change_amount, change_key));
|
||||
}
|
||||
}
|
||||
|
||||
Ok((parts, change_amounts_derivations))
|
||||
}
|
||||
|
||||
/// Select spendable coins from a wallet.
|
||||
/// Default strategy is to spend the maximum number of outputs (up to
|
||||
/// max_outputs). Alternative strategy is to spend smallest outputs first
|
||||
/// but only as many as necessary. When we introduce additional strategies
|
||||
/// we should pass something other than a bool in.
|
||||
/// TODO: Possibly move this into another trait to be owned by a wallet?
|
||||
|
||||
pub fn select_coins<T: ?Sized, C, K>(
|
||||
wallet: &mut T,
|
||||
amount: u64,
|
||||
current_height: u64,
|
||||
minimum_confirmations: u64,
|
||||
max_outputs: usize,
|
||||
select_all: bool,
|
||||
parent_key_id: &Identifier,
|
||||
) -> (usize, Vec<OutputData>)
|
||||
// max_outputs_available, Outputs
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
// first find all eligible outputs based on number of confirmations
|
||||
let mut eligible = wallet
|
||||
.iter()
|
||||
.filter(|out| {
|
||||
out.root_key_id == *parent_key_id
|
||||
&& out.eligible_to_spend(current_height, minimum_confirmations)
|
||||
})
|
||||
.collect::<Vec<OutputData>>();
|
||||
|
||||
let max_available = eligible.len();
|
||||
|
||||
// sort eligible outputs by increasing value
|
||||
eligible.sort_by_key(|out| out.value);
|
||||
|
||||
// use a sliding window to identify potential sets of possible outputs to spend
|
||||
// Case of amount > total amount of max_outputs(500):
|
||||
// The limit exists because by default, we always select as many inputs as
|
||||
// possible in a transaction, to reduce both the Output set and the fees.
|
||||
// But that only makes sense up to a point, hence the limit to avoid being too
|
||||
// greedy. But if max_outputs(500) is actually not enough to cover the whole
|
||||
// amount, the wallet should allow going over it to satisfy what the user
|
||||
// wants to send. So the wallet considers max_outputs more of a soft limit.
|
||||
if eligible.len() > max_outputs {
|
||||
for window in eligible.windows(max_outputs) {
|
||||
let windowed_eligibles = window.iter().cloned().collect::<Vec<_>>();
|
||||
if let Some(outputs) = select_from(amount, select_all, windowed_eligibles) {
|
||||
return (max_available, outputs);
|
||||
}
|
||||
}
|
||||
// Not exist in any window of which total amount >= amount.
|
||||
// Then take coins from the smallest one up to the total amount of selected
|
||||
// coins = the amount.
|
||||
if let Some(outputs) = select_from(amount, false, eligible.clone()) {
|
||||
debug!(
|
||||
"Extending maximum number of outputs. {} outputs selected.",
|
||||
outputs.len()
|
||||
);
|
||||
return (max_available, outputs);
|
||||
}
|
||||
} else {
|
||||
if let Some(outputs) = select_from(amount, select_all, eligible.clone()) {
|
||||
return (max_available, outputs);
|
||||
}
|
||||
}
|
||||
|
||||
// we failed to find a suitable set of outputs to spend,
|
||||
// so return the largest amount we can so we can provide guidance on what is
|
||||
// possible
|
||||
eligible.reverse();
|
||||
(
|
||||
max_available,
|
||||
eligible.iter().take(max_outputs).cloned().collect(),
|
||||
)
|
||||
}
|
||||
|
||||
fn select_from(amount: u64, select_all: bool, outputs: Vec<OutputData>) -> Option<Vec<OutputData>> {
|
||||
let total = outputs.iter().fold(0, |acc, x| acc + x.value);
|
||||
if total >= amount {
|
||||
if select_all {
|
||||
return Some(outputs.iter().cloned().collect());
|
||||
} else {
|
||||
let mut selected_amount = 0;
|
||||
return Some(
|
||||
outputs
|
||||
.iter()
|
||||
.take_while(|out| {
|
||||
let res = selected_amount < amount;
|
||||
selected_amount += out.value;
|
||||
res
|
||||
})
|
||||
.cloned()
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
// 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 uuid::Uuid;
|
||||
|
||||
use crate::keychain::{Identifier, Keychain};
|
||||
use crate::internal::{selection, updater};
|
||||
use crate::slate::Slate;
|
||||
use crate::types::{Context, NodeClient, OutputLockFn, TxLogEntryType, WalletBackend};
|
||||
use crate::{Error, ErrorKind};
|
||||
|
||||
/// Creates a new slate for a transaction, can be called by anyone involved in
|
||||
/// the transaction (sender(s), receiver(s))
|
||||
pub fn new_tx_slate<T: ?Sized, C, K>(
|
||||
wallet: &mut T,
|
||||
amount: u64,
|
||||
num_participants: usize,
|
||||
) -> Result<Slate, Error>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
let current_height = wallet.w2n_client().get_chain_height()?;
|
||||
let mut slate = Slate::blank(num_participants);
|
||||
slate.amount = amount;
|
||||
slate.height = current_height;
|
||||
slate.lock_height = current_height;
|
||||
Ok(slate)
|
||||
}
|
||||
|
||||
/// Estimates locked amount and fee for the transaction without creating one
|
||||
pub fn estimate_send_tx<T: ?Sized, C, K>(
|
||||
wallet: &mut T,
|
||||
amount: u64,
|
||||
minimum_confirmations: u64,
|
||||
max_outputs: usize,
|
||||
num_change_outputs: usize,
|
||||
selection_strategy_is_use_all: bool,
|
||||
parent_key_id: &Identifier,
|
||||
) -> Result<
|
||||
(
|
||||
u64, // total
|
||||
u64, // fee
|
||||
),
|
||||
Error,
|
||||
>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
// Get lock height
|
||||
let current_height = wallet.w2n_client().get_chain_height()?;
|
||||
// ensure outputs we're selecting are up to date
|
||||
updater::refresh_outputs(wallet, parent_key_id, false)?;
|
||||
|
||||
// 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 (_coins, total, _amount, fee) = selection::select_coins_and_fee(
|
||||
wallet,
|
||||
amount,
|
||||
current_height,
|
||||
minimum_confirmations,
|
||||
max_outputs,
|
||||
num_change_outputs,
|
||||
selection_strategy_is_use_all,
|
||||
parent_key_id,
|
||||
)?;
|
||||
Ok((total, fee))
|
||||
}
|
||||
|
||||
/// Add inputs to the slate (effectively becoming the sender)
|
||||
pub fn add_inputs_to_slate<T: ?Sized, C, K>(
|
||||
wallet: &mut T,
|
||||
slate: &mut Slate,
|
||||
minimum_confirmations: u64,
|
||||
max_outputs: usize,
|
||||
num_change_outputs: usize,
|
||||
selection_strategy_is_use_all: bool,
|
||||
parent_key_id: &Identifier,
|
||||
participant_id: usize,
|
||||
message: Option<String>,
|
||||
) -> Result<(Context, OutputLockFn<T, C, K>), Error>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
// sender should always refresh outputs
|
||||
updater::refresh_outputs(wallet, parent_key_id, false)?;
|
||||
|
||||
// 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 context, sender_lock_fn) = selection::build_send_tx(
|
||||
wallet,
|
||||
slate,
|
||||
minimum_confirmations,
|
||||
max_outputs,
|
||||
num_change_outputs,
|
||||
selection_strategy_is_use_all,
|
||||
parent_key_id.clone(),
|
||||
)?;
|
||||
|
||||
// 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,
|
||||
participant_id,
|
||||
message,
|
||||
)?;
|
||||
|
||||
Ok((context, sender_lock_fn))
|
||||
}
|
||||
|
||||
/// Add outputs to the slate, becoming the recipient
|
||||
pub fn add_output_to_slate<T: ?Sized, C, K>(
|
||||
wallet: &mut T,
|
||||
slate: &mut Slate,
|
||||
parent_key_id: &Identifier,
|
||||
participant_id: usize,
|
||||
message: Option<String>,
|
||||
) -> Result<(Context, OutputLockFn<T, C, K>), Error>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
// create an output using the amount in the slate
|
||||
let (_, mut context, create_fn) =
|
||||
selection::build_recipient_output(wallet, slate, parent_key_id.clone())?;
|
||||
|
||||
// fill public keys
|
||||
let _ = slate.fill_round_1(
|
||||
wallet.keychain(),
|
||||
&mut context.sec_key,
|
||||
&context.sec_nonce,
|
||||
1,
|
||||
message,
|
||||
)?;
|
||||
|
||||
// perform partial sig
|
||||
let _ = slate.fill_round_2(
|
||||
wallet.keychain(),
|
||||
&context.sec_key,
|
||||
&context.sec_nonce,
|
||||
participant_id,
|
||||
)?;
|
||||
|
||||
Ok((context, create_fn))
|
||||
}
|
||||
|
||||
/// Complete a transaction as the sender
|
||||
pub fn complete_tx<T: ?Sized, C, K>(
|
||||
wallet: &mut T,
|
||||
slate: &mut Slate,
|
||||
participant_id: usize,
|
||||
context: &Context,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
let _ = slate.fill_round_2(
|
||||
wallet.keychain(),
|
||||
&context.sec_key,
|
||||
&context.sec_nonce,
|
||||
participant_id,
|
||||
)?;
|
||||
// Final transaction can be built by anyone at this stage
|
||||
slate.finalize(wallet.keychain())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Rollback outputs associated with a transaction in the wallet
|
||||
pub fn cancel_tx<T: ?Sized, C, K>(
|
||||
wallet: &mut T,
|
||||
parent_key_id: &Identifier,
|
||||
tx_id: Option<u32>,
|
||||
tx_slate_id: Option<Uuid>,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
let mut tx_id_string = String::new();
|
||||
if let Some(tx_id) = tx_id {
|
||||
tx_id_string = tx_id.to_string();
|
||||
} else if let Some(tx_slate_id) = tx_slate_id {
|
||||
tx_id_string = tx_slate_id.to_string();
|
||||
}
|
||||
let tx_vec = updater::retrieve_txs(wallet, tx_id, tx_slate_id, Some(&parent_key_id), false)?;
|
||||
if tx_vec.len() != 1 {
|
||||
return Err(ErrorKind::TransactionDoesntExist(tx_id_string))?;
|
||||
}
|
||||
let tx = tx_vec[0].clone();
|
||||
if tx.tx_type != TxLogEntryType::TxSent && tx.tx_type != TxLogEntryType::TxReceived {
|
||||
return Err(ErrorKind::TransactionNotCancellable(tx_id_string))?;
|
||||
}
|
||||
if tx.confirmed == true {
|
||||
return Err(ErrorKind::TransactionNotCancellable(tx_id_string))?;
|
||||
}
|
||||
// get outputs associated with tx
|
||||
let res = updater::retrieve_outputs(wallet, false, Some(tx.id), Some(&parent_key_id))?;
|
||||
let outputs = res.iter().map(|(out, _)| out).cloned().collect();
|
||||
updater::cancel_tx_and_outputs(wallet, tx, outputs, parent_key_id)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Retrieve the associated stored finalised hex Transaction for a given transaction Id
|
||||
/// as well as whether it's been confirmed
|
||||
pub fn retrieve_tx_hex<T: ?Sized, C, K>(
|
||||
wallet: &mut T,
|
||||
parent_key_id: &Identifier,
|
||||
tx_id: u32,
|
||||
) -> Result<(bool, Option<String>), Error>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
let tx_vec = updater::retrieve_txs(wallet, Some(tx_id), None, Some(parent_key_id), false)?;
|
||||
if tx_vec.len() != 1 {
|
||||
return Err(ErrorKind::TransactionDoesntExist(tx_id.to_string()))?;
|
||||
}
|
||||
let tx = tx_vec[0].clone();
|
||||
Ok((tx.confirmed, tx.stored_tx))
|
||||
}
|
||||
|
||||
/// Update the stored transaction (this update needs to happen when the TX is finalised)
|
||||
pub fn update_stored_tx<T: ?Sized, C, K>(wallet: &mut T, slate: &Slate) -> Result<(), Error>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
// finalize command
|
||||
let tx_vec = updater::retrieve_txs(wallet, None, Some(slate.id), None, false)?;
|
||||
let mut tx = None;
|
||||
// don't want to assume this is the right tx, in case of self-sending
|
||||
for t in tx_vec {
|
||||
if t.tx_type == TxLogEntryType::TxSent {
|
||||
tx = Some(t.clone());
|
||||
break;
|
||||
}
|
||||
}
|
||||
let tx = match tx {
|
||||
Some(t) => t,
|
||||
None => return Err(ErrorKind::TransactionDoesntExist(slate.id.to_string()))?,
|
||||
};
|
||||
wallet.store_tx(&format!("{}", tx.tx_slate_id.unwrap()), &slate.tx)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update the transaction participant messages
|
||||
pub fn update_message<T: ?Sized, C, K>(wallet: &mut T, slate: &Slate) -> Result<(), Error>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
let tx_vec = updater::retrieve_txs(wallet, None, Some(slate.id), None, false)?;
|
||||
if tx_vec.is_empty() {
|
||||
return Err(ErrorKind::TransactionDoesntExist(slate.id.to_string()))?;
|
||||
}
|
||||
let mut batch = wallet.batch()?;
|
||||
for mut tx in tx_vec.into_iter() {
|
||||
tx.messages = Some(slate.participant_messages());
|
||||
let parent_key = tx.parent_key_id.clone();
|
||||
batch.save_tx_log_entry(tx, &parent_key)?;
|
||||
}
|
||||
batch.commit()?;
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::core::libtx::build;
|
||||
use crate::keychain::{ExtKeychain, ExtKeychainPath, Keychain};
|
||||
|
||||
#[test]
|
||||
// demonstrate that input.commitment == referenced output.commitment
|
||||
// based on the public key and amount begin spent
|
||||
fn output_commitment_equals_input_commitment_on_spend() {
|
||||
let keychain = ExtKeychain::from_random_seed(false).unwrap();
|
||||
let key_id1 = ExtKeychainPath::new(1, 1, 0, 0, 0).to_identifier();
|
||||
|
||||
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();
|
||||
|
||||
assert_eq!(tx1.outputs()[0].features, tx2.inputs()[0].features);
|
||||
assert_eq!(tx1.outputs()[0].commitment(), tx2.inputs()[0].commitment());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,517 @@
|
||||
// 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.
|
||||
|
||||
//! Utilities to check the status of all the outputs we have stored in
|
||||
//! the wallet storage and update them.
|
||||
|
||||
use failure::ResultExt;
|
||||
use std::collections::HashMap;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::core::consensus::reward;
|
||||
use crate::core::core::{Output, TxKernel};
|
||||
use crate::core::libtx::reward;
|
||||
use crate::core::{global, ser};
|
||||
use crate::keychain::{Identifier, Keychain};
|
||||
use crate::error::{Error, ErrorKind};
|
||||
use crate::internal::keys;
|
||||
use crate::types::{
|
||||
BlockFees, CbData, NodeClient, OutputData, OutputStatus, TxLogEntry, TxLogEntryType,
|
||||
WalletBackend, WalletInfo,
|
||||
};
|
||||
use crate::util;
|
||||
use crate::util::secp::pedersen;
|
||||
|
||||
/// Retrieve all of the outputs (doesn't attempt to update from node)
|
||||
pub fn retrieve_outputs<T: ?Sized, C, K>(
|
||||
wallet: &mut T,
|
||||
show_spent: bool,
|
||||
tx_id: Option<u32>,
|
||||
parent_key_id: Option<&Identifier>,
|
||||
) -> Result<Vec<(OutputData, pedersen::Commitment)>, Error>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
// just read the wallet here, no need for a write lock
|
||||
let mut outputs = wallet
|
||||
.iter()
|
||||
.filter(|out| show_spent || out.status != OutputStatus::Spent)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// only include outputs with a given tx_id if provided
|
||||
if let Some(id) = tx_id {
|
||||
outputs = outputs
|
||||
.into_iter()
|
||||
.filter(|out| out.tx_log_entry == Some(id))
|
||||
.collect::<Vec<_>>();
|
||||
}
|
||||
|
||||
if let Some(k) = parent_key_id {
|
||||
outputs = outputs
|
||||
.iter()
|
||||
.filter(|o| o.root_key_id == *k)
|
||||
.map(|o| o.clone())
|
||||
.collect();
|
||||
}
|
||||
|
||||
outputs.sort_by_key(|out| out.n_child);
|
||||
let keychain = wallet.keychain().clone();
|
||||
|
||||
let res = outputs
|
||||
.into_iter()
|
||||
.map(|out| {
|
||||
let commit = match out.commit.clone() {
|
||||
Some(c) => pedersen::Commitment::from_vec(util::from_hex(c).unwrap()),
|
||||
None => keychain.commit(out.value, &out.key_id).unwrap(),
|
||||
};
|
||||
(out, commit)
|
||||
})
|
||||
.collect();
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Retrieve all of the transaction entries, or a particular entry
|
||||
/// if `parent_key_id` is set, only return entries from that key
|
||||
pub fn retrieve_txs<T: ?Sized, C, K>(
|
||||
wallet: &mut T,
|
||||
tx_id: Option<u32>,
|
||||
tx_slate_id: Option<Uuid>,
|
||||
parent_key_id: Option<&Identifier>,
|
||||
outstanding_only: bool,
|
||||
) -> Result<Vec<TxLogEntry>, Error>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
let mut txs: Vec<TxLogEntry> = wallet
|
||||
.tx_log_iter()
|
||||
.filter(|tx_entry| {
|
||||
let f_pk = match parent_key_id {
|
||||
Some(k) => tx_entry.parent_key_id == *k,
|
||||
None => true,
|
||||
};
|
||||
let f_tx_id = match tx_id {
|
||||
Some(i) => tx_entry.id == i,
|
||||
None => true,
|
||||
};
|
||||
let f_txs = match tx_slate_id {
|
||||
Some(t) => tx_entry.tx_slate_id == Some(t),
|
||||
None => true,
|
||||
};
|
||||
let f_outstanding = match outstanding_only {
|
||||
true => {
|
||||
!tx_entry.confirmed
|
||||
&& (tx_entry.tx_type == TxLogEntryType::TxReceived
|
||||
|| tx_entry.tx_type == TxLogEntryType::TxSent)
|
||||
}
|
||||
false => true,
|
||||
};
|
||||
f_pk && f_tx_id && f_txs && f_outstanding
|
||||
})
|
||||
.collect();
|
||||
txs.sort_by_key(|tx| tx.creation_ts);
|
||||
Ok(txs)
|
||||
}
|
||||
|
||||
/// Refreshes the outputs in a wallet with the latest information
|
||||
/// from a node
|
||||
pub fn refresh_outputs<T: ?Sized, C, K>(
|
||||
wallet: &mut T,
|
||||
parent_key_id: &Identifier,
|
||||
update_all: bool,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
let height = wallet.w2n_client().get_chain_height()?;
|
||||
refresh_output_state(wallet, height, parent_key_id, update_all)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 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<T: ?Sized, C, K>(
|
||||
wallet: &mut T,
|
||||
parent_key_id: &Identifier,
|
||||
update_all: bool,
|
||||
) -> Result<HashMap<pedersen::Commitment, (Identifier, Option<u64>)>, Error>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
let mut wallet_outputs: HashMap<pedersen::Commitment, (Identifier, Option<u64>)> =
|
||||
HashMap::new();
|
||||
let keychain = wallet.keychain().clone();
|
||||
let unspents: Vec<OutputData> = wallet
|
||||
.iter()
|
||||
.filter(|x| x.root_key_id == *parent_key_id && x.status != OutputStatus::Spent)
|
||||
.collect();
|
||||
|
||||
let tx_entries = retrieve_txs(wallet, None, None, Some(&parent_key_id), true)?;
|
||||
|
||||
// Only select outputs that are actually involved in an outstanding transaction
|
||||
let unspents: Vec<OutputData> = match update_all {
|
||||
false => unspents
|
||||
.into_iter()
|
||||
.filter(|x| match x.tx_log_entry.as_ref() {
|
||||
Some(t) => {
|
||||
if let Some(_) = tx_entries.iter().find(|&te| te.id == *t) {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
None => true,
|
||||
})
|
||||
.collect(),
|
||||
true => unspents,
|
||||
};
|
||||
|
||||
for out in unspents {
|
||||
let commit = match out.commit.clone() {
|
||||
Some(c) => pedersen::Commitment::from_vec(util::from_hex(c).unwrap()),
|
||||
None => keychain.commit(out.value, &out.key_id).unwrap(),
|
||||
};
|
||||
wallet_outputs.insert(commit, (out.key_id.clone(), out.mmr_index));
|
||||
}
|
||||
Ok(wallet_outputs)
|
||||
}
|
||||
|
||||
/// Cancel transaction and associated outputs
|
||||
pub fn cancel_tx_and_outputs<T: ?Sized, C, K>(
|
||||
wallet: &mut T,
|
||||
tx: TxLogEntry,
|
||||
outputs: Vec<OutputData>,
|
||||
parent_key_id: &Identifier,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
let mut batch = wallet.batch()?;
|
||||
|
||||
for mut o in outputs {
|
||||
// unlock locked outputs
|
||||
if o.status == OutputStatus::Unconfirmed {
|
||||
batch.delete(&o.key_id, &o.mmr_index)?;
|
||||
}
|
||||
if o.status == OutputStatus::Locked {
|
||||
o.status = OutputStatus::Unspent;
|
||||
batch.save(o)?;
|
||||
}
|
||||
}
|
||||
let mut tx = tx.clone();
|
||||
if tx.tx_type == TxLogEntryType::TxSent {
|
||||
tx.tx_type = TxLogEntryType::TxSentCancelled;
|
||||
}
|
||||
if tx.tx_type == TxLogEntryType::TxReceived {
|
||||
tx.tx_type = TxLogEntryType::TxReceivedCancelled;
|
||||
}
|
||||
batch.save_tx_log_entry(tx, parent_key_id)?;
|
||||
batch.commit()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Apply refreshed API output data to the wallet
|
||||
pub fn apply_api_outputs<T: ?Sized, C, K>(
|
||||
wallet: &mut T,
|
||||
wallet_outputs: &HashMap<pedersen::Commitment, (Identifier, Option<u64>)>,
|
||||
api_outputs: &HashMap<pedersen::Commitment, (String, u64, u64)>,
|
||||
height: u64,
|
||||
parent_key_id: &Identifier,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
// 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.
|
||||
{
|
||||
let last_confirmed_height = wallet.last_confirmed_height()?;
|
||||
// If the server height is less than our confirmed height, don't apply
|
||||
// these changes as the chain is syncing, incorrect or forking
|
||||
if height < last_confirmed_height {
|
||||
warn!(
|
||||
"Not updating outputs as the height of the node's chain \
|
||||
is less than the last reported wallet update height."
|
||||
);
|
||||
warn!("Please wait for sync on node to complete or fork to resolve and try again.");
|
||||
return Ok(());
|
||||
}
|
||||
let mut batch = wallet.batch()?;
|
||||
for (commit, (id, mmr_index)) in wallet_outputs.iter() {
|
||||
if let Ok(mut output) = batch.get(id, mmr_index) {
|
||||
match api_outputs.get(&commit) {
|
||||
Some(o) => {
|
||||
// if this is a coinbase tx being confirmed, it's recordable in tx log
|
||||
if output.is_coinbase && output.status == OutputStatus::Unconfirmed {
|
||||
let log_id = batch.next_tx_log_id(parent_key_id)?;
|
||||
let mut t = TxLogEntry::new(
|
||||
parent_key_id.clone(),
|
||||
TxLogEntryType::ConfirmedCoinbase,
|
||||
log_id,
|
||||
);
|
||||
t.confirmed = true;
|
||||
t.amount_credited = output.value;
|
||||
t.amount_debited = 0;
|
||||
t.num_outputs = 1;
|
||||
t.update_confirmation_ts();
|
||||
output.tx_log_entry = Some(log_id);
|
||||
batch.save_tx_log_entry(t, &parent_key_id)?;
|
||||
}
|
||||
// also mark the transaction in which this output is involved as confirmed
|
||||
// note that one involved input/output confirmation SHOULD be enough
|
||||
// to reliably confirm the tx
|
||||
if !output.is_coinbase && output.status == OutputStatus::Unconfirmed {
|
||||
let tx = batch.tx_log_iter().find(|t| {
|
||||
Some(t.id) == output.tx_log_entry
|
||||
&& t.parent_key_id == *parent_key_id
|
||||
});
|
||||
if let Some(mut t) = tx {
|
||||
t.update_confirmation_ts();
|
||||
t.confirmed = true;
|
||||
batch.save_tx_log_entry(t, &parent_key_id)?;
|
||||
}
|
||||
}
|
||||
output.height = o.1;
|
||||
output.mark_unspent();
|
||||
}
|
||||
None => output.mark_spent(),
|
||||
};
|
||||
batch.save(output)?;
|
||||
}
|
||||
}
|
||||
{
|
||||
batch.save_last_confirmed_height(parent_key_id, height)?;
|
||||
}
|
||||
batch.commit()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 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<T: ?Sized, C, K>(
|
||||
wallet: &mut T,
|
||||
height: u64,
|
||||
parent_key_id: &Identifier,
|
||||
update_all: bool,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
debug!("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(wallet, parent_key_id, update_all)?;
|
||||
|
||||
let wallet_output_keys = wallet_outputs.keys().map(|commit| commit.clone()).collect();
|
||||
|
||||
let api_outputs = wallet
|
||||
.w2n_client()
|
||||
.get_outputs_from_node(wallet_output_keys)?;
|
||||
apply_api_outputs(wallet, &wallet_outputs, &api_outputs, height, parent_key_id)?;
|
||||
clean_old_unconfirmed(wallet, height)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clean_old_unconfirmed<T: ?Sized, C, K>(wallet: &mut T, height: u64) -> Result<(), Error>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
if height < 50 {
|
||||
return Ok(());
|
||||
}
|
||||
let mut ids_to_del = vec![];
|
||||
for out in wallet.iter() {
|
||||
if out.status == OutputStatus::Unconfirmed
|
||||
&& out.height > 0
|
||||
&& out.height < height - 50
|
||||
&& out.is_coinbase
|
||||
{
|
||||
ids_to_del.push(out.key_id.clone())
|
||||
}
|
||||
}
|
||||
let mut batch = wallet.batch()?;
|
||||
for id in ids_to_del {
|
||||
batch.delete(&id, &None)?;
|
||||
}
|
||||
batch.commit()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Retrieve summary info about the wallet
|
||||
/// caller should refresh first if desired
|
||||
pub fn retrieve_info<T: ?Sized, C, K>(
|
||||
wallet: &mut T,
|
||||
parent_key_id: &Identifier,
|
||||
minimum_confirmations: u64,
|
||||
) -> Result<WalletInfo, Error>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
let current_height = wallet.last_confirmed_height()?;
|
||||
let outputs = wallet
|
||||
.iter()
|
||||
.filter(|out| out.root_key_id == *parent_key_id);
|
||||
|
||||
let mut unspent_total = 0;
|
||||
let mut immature_total = 0;
|
||||
let mut unconfirmed_total = 0;
|
||||
let mut locked_total = 0;
|
||||
|
||||
for out in outputs {
|
||||
match out.status {
|
||||
OutputStatus::Unspent => {
|
||||
if out.is_coinbase && out.lock_height > current_height {
|
||||
immature_total += out.value;
|
||||
} else if out.num_confirmations(current_height) < minimum_confirmations {
|
||||
// Treat anything less than minimum confirmations as "unconfirmed".
|
||||
unconfirmed_total += out.value;
|
||||
} else {
|
||||
unspent_total += out.value;
|
||||
}
|
||||
}
|
||||
OutputStatus::Unconfirmed => {
|
||||
// We ignore unconfirmed coinbase outputs completely.
|
||||
if !out.is_coinbase {
|
||||
if minimum_confirmations == 0 {
|
||||
unspent_total += out.value;
|
||||
} else {
|
||||
unconfirmed_total += out.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
OutputStatus::Locked => {
|
||||
locked_total += out.value;
|
||||
}
|
||||
OutputStatus::Spent => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(WalletInfo {
|
||||
last_confirmed_height: current_height,
|
||||
minimum_confirmations,
|
||||
total: unspent_total + unconfirmed_total + immature_total,
|
||||
amount_awaiting_confirmation: unconfirmed_total,
|
||||
amount_immature: immature_total,
|
||||
amount_locked: locked_total,
|
||||
amount_currently_spendable: unspent_total,
|
||||
})
|
||||
}
|
||||
|
||||
/// Build a coinbase output and insert into wallet
|
||||
pub fn build_coinbase<T: ?Sized, C, K>(
|
||||
wallet: &mut T,
|
||||
block_fees: &BlockFees,
|
||||
) -> Result<CbData, Error>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
let (out, kern, block_fees) = receive_coinbase(wallet, block_fees).context(ErrorKind::Node)?;
|
||||
|
||||
let out_bin = ser::ser_vec(&out).context(ErrorKind::Node)?;
|
||||
|
||||
let kern_bin = ser::ser_vec(&kern).context(ErrorKind::Node)?;
|
||||
|
||||
let key_id_bin = match block_fees.key_id {
|
||||
Some(key_id) => ser::ser_vec(&key_id).context(ErrorKind::Node)?,
|
||||
None => vec![],
|
||||
};
|
||||
|
||||
Ok(CbData {
|
||||
output: util::to_hex(out_bin),
|
||||
kernel: util::to_hex(kern_bin),
|
||||
key_id: util::to_hex(key_id_bin),
|
||||
})
|
||||
}
|
||||
|
||||
//TODO: Split up the output creation and the wallet insertion
|
||||
/// Build a coinbase output and the corresponding kernel
|
||||
pub fn receive_coinbase<T: ?Sized, C, K>(
|
||||
wallet: &mut T,
|
||||
block_fees: &BlockFees,
|
||||
) -> Result<(Output, TxKernel, BlockFees), Error>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
let height = block_fees.height;
|
||||
let lock_height = height + global::coinbase_maturity();
|
||||
let key_id = block_fees.key_id();
|
||||
let parent_key_id = wallet.parent_key_id();
|
||||
|
||||
let key_id = match key_id {
|
||||
Some(key_id) => match keys::retrieve_existing_key(wallet, key_id, None) {
|
||||
Ok(k) => k.0,
|
||||
Err(_) => keys::next_available_key(wallet)?,
|
||||
},
|
||||
None => keys::next_available_key(wallet)?,
|
||||
};
|
||||
|
||||
{
|
||||
// Now acquire the wallet lock and write the new output.
|
||||
let amount = reward(block_fees.fees);
|
||||
let commit = wallet.calc_commit_for_cache(amount, &key_id)?;
|
||||
let mut batch = wallet.batch()?;
|
||||
batch.save(OutputData {
|
||||
root_key_id: parent_key_id,
|
||||
key_id: key_id.clone(),
|
||||
n_child: key_id.to_path().last_path_index(),
|
||||
mmr_index: None,
|
||||
commit: commit,
|
||||
value: amount,
|
||||
status: OutputStatus::Unconfirmed,
|
||||
height: height,
|
||||
lock_height: lock_height,
|
||||
is_coinbase: true,
|
||||
tx_log_entry: None,
|
||||
})?;
|
||||
batch.commit()?;
|
||||
}
|
||||
|
||||
debug!(
|
||||
"receive_coinbase: built candidate output - {:?}, {}",
|
||||
key_id.clone(),
|
||||
key_id,
|
||||
);
|
||||
|
||||
let mut block_fees = block_fees.clone();
|
||||
block_fees.key_id = Some(key_id.clone());
|
||||
|
||||
debug!("receive_coinbase: {:?}", block_fees);
|
||||
|
||||
let (out, kern) = reward::output(wallet.keychain(), &key_id, block_fees.fees).unwrap();
|
||||
/* .context(ErrorKind::Keychain)?; */
|
||||
Ok((out, kern, block_fees))
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright 2019 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.
|
||||
|
||||
//! Higher level wallet functions which can be used by callers to operate
|
||||
//! on the wallet, as well as helpers to invoke and instantiate wallets
|
||||
//! and listeners
|
||||
|
||||
#![deny(non_upper_case_globals)]
|
||||
#![deny(non_camel_case_types)]
|
||||
#![deny(non_snake_case)]
|
||||
#![deny(unused_mut)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate grin_core as core;
|
||||
|
||||
extern crate grin_keychain as keychain;
|
||||
extern crate grin_util as util;
|
||||
|
||||
use blake2_rfc as blake2;
|
||||
|
||||
use failure;
|
||||
extern crate failure_derive;
|
||||
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
pub mod api;
|
||||
mod error;
|
||||
pub mod internal;
|
||||
pub mod slate;
|
||||
pub mod slate_versions;
|
||||
pub mod types;
|
||||
|
||||
pub use crate::error::{Error, ErrorKind};
|
||||
@@ -0,0 +1,559 @@
|
||||
// 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 crate::blake2::blake2b::blake2b;
|
||||
use crate::keychain::{BlindSum, BlindingFactor, Keychain};
|
||||
use crate::error::{Error, ErrorKind};
|
||||
use crate::slate_versions::v0::SlateV0;
|
||||
use crate::util::secp;
|
||||
use crate::util::secp::key::{PublicKey, SecretKey};
|
||||
use crate::util::secp::Signature;
|
||||
use crate::util::RwLock;
|
||||
use grin_core::core::amount_to_hr_string;
|
||||
use grin_core::core::committed::Committed;
|
||||
use grin_core::core::transaction::{kernel_features, kernel_sig_msg, Transaction, Weighting};
|
||||
use grin_core::core::verifier_cache::LruVerifierCache;
|
||||
use grin_core::libtx::{aggsig, build, secp_ser, tx_fee};
|
||||
use rand::thread_rng;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
const CURRENT_SLATE_VERSION: u64 = 1;
|
||||
|
||||
/// A wrapper around slates the enables support for versioning
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[serde(untagged)]
|
||||
pub enum VersionedSlate {
|
||||
/// Pre versioning version
|
||||
V0(SlateV0),
|
||||
/// Version 1 with versioning and hex serialization - current
|
||||
V1(Slate),
|
||||
}
|
||||
|
||||
impl From<VersionedSlate> for Slate {
|
||||
fn from(ver: VersionedSlate) -> Self {
|
||||
match ver {
|
||||
VersionedSlate::V0(slate_v0) => Slate::from(slate_v0),
|
||||
VersionedSlate::V1(slate) => slate,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Public data for each participant in the slate
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct ParticipantData {
|
||||
/// Id of participant in the transaction. (For now, 0=sender, 1=rec)
|
||||
pub id: u64,
|
||||
/// Public key corresponding to private blinding factor
|
||||
#[serde(with = "secp_ser::pubkey_serde")]
|
||||
pub public_blind_excess: PublicKey,
|
||||
/// Public key corresponding to private nonce
|
||||
#[serde(with = "secp_ser::pubkey_serde")]
|
||||
pub public_nonce: PublicKey,
|
||||
/// Public partial signature
|
||||
#[serde(with = "secp_ser::option_sig_serde")]
|
||||
pub part_sig: Option<Signature>,
|
||||
/// A message for other participants
|
||||
pub message: Option<String>,
|
||||
/// Signature, created with private key corresponding to 'public_blind_excess'
|
||||
#[serde(with = "secp_ser::option_sig_serde")]
|
||||
pub message_sig: Option<Signature>,
|
||||
}
|
||||
|
||||
impl ParticipantData {
|
||||
/// A helper to return whether this participant
|
||||
/// has completed round 1 and round 2;
|
||||
/// Round 1 has to be completed before instantiation of this struct
|
||||
/// anyhow, and for each participant consists of:
|
||||
/// -Inputs added to transaction
|
||||
/// -Outputs added to transaction
|
||||
/// -Public signature nonce chosen and added
|
||||
/// -Public contribution to blinding factor chosen and added
|
||||
/// Round 2 can only be completed after all participants have
|
||||
/// performed round 1, and adds:
|
||||
/// -Part sig is filled out
|
||||
pub fn is_complete(&self) -> bool {
|
||||
self.part_sig.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
/// Public message data (for serialising and storage)
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct ParticipantMessageData {
|
||||
/// id of the particpant in the tx
|
||||
pub id: u64,
|
||||
/// Public key
|
||||
#[serde(with = "secp_ser::pubkey_serde")]
|
||||
pub public_key: PublicKey,
|
||||
/// Message,
|
||||
pub message: Option<String>,
|
||||
/// Signature
|
||||
#[serde(with = "secp_ser::option_sig_serde")]
|
||||
pub message_sig: Option<Signature>,
|
||||
}
|
||||
|
||||
impl ParticipantMessageData {
|
||||
/// extract relevant message data from participant data
|
||||
pub fn from_participant_data(p: &ParticipantData) -> ParticipantMessageData {
|
||||
ParticipantMessageData {
|
||||
id: p.id,
|
||||
public_key: p.public_blind_excess,
|
||||
message: p.message.clone(),
|
||||
message_sig: p.message_sig.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A 'Slate' is passed around to all parties to build up all of the public
|
||||
/// transaction data needed to create a finalized transaction. Callers can pass
|
||||
/// the slate around by whatever means they choose, (but we can provide some
|
||||
/// binary or JSON serialization helpers here).
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct Slate {
|
||||
/// The number of participants intended to take part in this transaction
|
||||
pub num_participants: usize,
|
||||
/// Unique transaction ID, selected by sender
|
||||
pub id: Uuid,
|
||||
/// The core transaction data:
|
||||
/// inputs, outputs, kernels, kernel offset
|
||||
pub tx: Transaction,
|
||||
/// base amount (excluding fee)
|
||||
pub amount: u64,
|
||||
/// fee amount
|
||||
pub fee: u64,
|
||||
/// Block height for the transaction
|
||||
pub height: u64,
|
||||
/// Lock height
|
||||
pub lock_height: u64,
|
||||
/// Participant data, each participant in the transaction will
|
||||
/// insert their public data here. For now, 0 is sender and 1
|
||||
/// is receiver, though this will change for multi-party
|
||||
pub participant_data: Vec<ParticipantData>,
|
||||
/// Slate format version
|
||||
#[serde(default = "no_version")]
|
||||
pub version: u64,
|
||||
}
|
||||
|
||||
fn no_version() -> u64 {
|
||||
0
|
||||
}
|
||||
|
||||
/// Helper just to facilitate serialization
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct ParticipantMessages {
|
||||
/// included messages
|
||||
pub messages: Vec<ParticipantMessageData>,
|
||||
}
|
||||
|
||||
impl Slate {
|
||||
/// Create a new slate
|
||||
pub fn blank(num_participants: usize) -> Slate {
|
||||
Slate {
|
||||
num_participants: num_participants,
|
||||
id: Uuid::new_v4(),
|
||||
tx: Transaction::empty(),
|
||||
amount: 0,
|
||||
fee: 0,
|
||||
height: 0,
|
||||
lock_height: 0,
|
||||
participant_data: vec![],
|
||||
version: CURRENT_SLATE_VERSION,
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds selected inputs and outputs to the slate's transaction
|
||||
/// Returns blinding factor
|
||||
pub fn add_transaction_elements<K>(
|
||||
&mut self,
|
||||
keychain: &K,
|
||||
mut elems: Vec<Box<build::Append<K>>>,
|
||||
) -> Result<BlindingFactor, Error>
|
||||
where
|
||||
K: Keychain,
|
||||
{
|
||||
// Append to the exiting transaction
|
||||
if self.tx.kernels().len() != 0 {
|
||||
elems.insert(0, build::initial_tx(self.tx.clone()));
|
||||
}
|
||||
let (tx, blind) = build::partial_transaction(elems, keychain)?;
|
||||
self.tx = tx;
|
||||
Ok(blind)
|
||||
}
|
||||
|
||||
/// Completes callers part of round 1, adding public key info
|
||||
/// to the slate
|
||||
pub fn fill_round_1<K>(
|
||||
&mut self,
|
||||
keychain: &K,
|
||||
sec_key: &mut SecretKey,
|
||||
sec_nonce: &SecretKey,
|
||||
participant_id: usize,
|
||||
message: Option<String>,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
K: Keychain,
|
||||
{
|
||||
// Whoever does this first generates the offset
|
||||
if self.tx.offset == BlindingFactor::zero() {
|
||||
self.generate_offset(keychain, sec_key)?;
|
||||
}
|
||||
self.add_participant_info(
|
||||
keychain,
|
||||
&sec_key,
|
||||
&sec_nonce,
|
||||
participant_id,
|
||||
None,
|
||||
message,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// This is the msg that we will sign as part of the tx kernel.
|
||||
// Currently includes the fee and the lock_height.
|
||||
fn msg_to_sign(&self) -> Result<secp::Message, Error> {
|
||||
// Currently we only support interactively creating a tx with a "default" kernel.
|
||||
let features = kernel_features(self.lock_height);
|
||||
let msg = kernel_sig_msg(self.fee, self.lock_height, features)?;
|
||||
Ok(msg)
|
||||
}
|
||||
|
||||
/// Completes caller's part of round 2, completing signatures
|
||||
pub fn fill_round_2<K>(
|
||||
&mut self,
|
||||
keychain: &K,
|
||||
sec_key: &SecretKey,
|
||||
sec_nonce: &SecretKey,
|
||||
participant_id: usize,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
K: Keychain,
|
||||
{
|
||||
self.check_fees()?;
|
||||
|
||||
self.verify_part_sigs(keychain.secp())?;
|
||||
let sig_part = aggsig::calculate_partial_sig(
|
||||
keychain.secp(),
|
||||
sec_key,
|
||||
sec_nonce,
|
||||
&self.pub_nonce_sum(keychain.secp())?,
|
||||
Some(&self.pub_blind_sum(keychain.secp())?),
|
||||
&self.msg_to_sign()?,
|
||||
)?;
|
||||
self.participant_data[participant_id].part_sig = Some(sig_part);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Creates the final signature, callable by either the sender or recipient
|
||||
/// (after phase 3: sender confirmation)
|
||||
/// TODO: Only callable by receiver at the moment
|
||||
pub fn finalize<K>(&mut self, keychain: &K) -> Result<(), Error>
|
||||
where
|
||||
K: Keychain,
|
||||
{
|
||||
let final_sig = self.finalize_signature(keychain)?;
|
||||
self.finalize_transaction(keychain, &final_sig)
|
||||
}
|
||||
|
||||
/// Return the sum of public nonces
|
||||
fn pub_nonce_sum(&self, secp: &secp::Secp256k1) -> Result<PublicKey, Error> {
|
||||
let pub_nonces = self
|
||||
.participant_data
|
||||
.iter()
|
||||
.map(|p| &p.public_nonce)
|
||||
.collect();
|
||||
match PublicKey::from_combination(secp, pub_nonces) {
|
||||
Ok(k) => Ok(k),
|
||||
Err(e) => Err(ErrorKind::Secp(e))?,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the sum of public blinding factors
|
||||
fn pub_blind_sum(&self, secp: &secp::Secp256k1) -> Result<PublicKey, Error> {
|
||||
let pub_blinds = self
|
||||
.participant_data
|
||||
.iter()
|
||||
.map(|p| &p.public_blind_excess)
|
||||
.collect();
|
||||
match PublicKey::from_combination(secp, pub_blinds) {
|
||||
Ok(k) => Ok(k),
|
||||
Err(e) => Err(ErrorKind::Secp(e))?,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return vector of all partial sigs
|
||||
fn part_sigs(&self) -> Vec<&Signature> {
|
||||
self.participant_data
|
||||
.iter()
|
||||
.map(|p| p.part_sig.as_ref().unwrap())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Adds participants public keys to the slate data
|
||||
/// and saves participant's transaction context
|
||||
/// sec_key can be overridden to replace the blinding
|
||||
/// factor (by whoever split the offset)
|
||||
fn add_participant_info<K>(
|
||||
&mut self,
|
||||
keychain: &K,
|
||||
sec_key: &SecretKey,
|
||||
sec_nonce: &SecretKey,
|
||||
id: usize,
|
||||
part_sig: Option<Signature>,
|
||||
message: Option<String>,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
K: Keychain,
|
||||
{
|
||||
// Add our public key and nonce to the slate
|
||||
let pub_key = PublicKey::from_secret_key(keychain.secp(), &sec_key)?;
|
||||
let pub_nonce = PublicKey::from_secret_key(keychain.secp(), &sec_nonce)?;
|
||||
// Sign the provided message
|
||||
let message_sig = {
|
||||
if let Some(m) = message.clone() {
|
||||
let hashed = blake2b(secp::constants::MESSAGE_SIZE, &[], &m.as_bytes()[..]);
|
||||
let m = secp::Message::from_slice(&hashed.as_bytes())?;
|
||||
let res = aggsig::sign_single(&keychain.secp(), &m, &sec_key, Some(&pub_key))?;
|
||||
Some(res)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
self.participant_data.push(ParticipantData {
|
||||
id: id as u64,
|
||||
public_blind_excess: pub_key,
|
||||
public_nonce: pub_nonce,
|
||||
part_sig: part_sig,
|
||||
message: message,
|
||||
message_sig: message_sig,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// helper to return all participant messages
|
||||
pub fn participant_messages(&self) -> ParticipantMessages {
|
||||
let mut ret = ParticipantMessages { messages: vec![] };
|
||||
for ref m in self.participant_data.iter() {
|
||||
ret.messages
|
||||
.push(ParticipantMessageData::from_participant_data(m));
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
/// Somebody involved needs to generate an offset with their private key
|
||||
/// For now, we'll have the transaction initiator be responsible for it
|
||||
/// Return offset private key for the participant to use later in the
|
||||
/// transaction
|
||||
fn generate_offset<K>(&mut self, keychain: &K, sec_key: &mut SecretKey) -> Result<(), Error>
|
||||
where
|
||||
K: Keychain,
|
||||
{
|
||||
// Generate a random kernel offset here
|
||||
// and subtract it from the blind_sum so we create
|
||||
// the aggsig context with the "split" key
|
||||
self.tx.offset =
|
||||
BlindingFactor::from_secret_key(SecretKey::new(&keychain.secp(), &mut thread_rng()));
|
||||
let blind_offset = keychain.blind_sum(
|
||||
&BlindSum::new()
|
||||
.add_blinding_factor(BlindingFactor::from_secret_key(sec_key.clone()))
|
||||
.sub_blinding_factor(self.tx.offset),
|
||||
)?;
|
||||
*sec_key = blind_offset.secret_key(&keychain.secp())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Checks the fees in the transaction in the given slate are valid
|
||||
fn check_fees(&self) -> Result<(), Error> {
|
||||
// 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(
|
||||
self.tx.inputs().len(),
|
||||
self.tx.outputs().len(),
|
||||
self.tx.kernels().len(),
|
||||
None,
|
||||
);
|
||||
if fee > self.tx.fee() {
|
||||
return Err(ErrorKind::Fee(
|
||||
format!("Fee Dispute Error: {}, {}", self.tx.fee(), fee,).to_string(),
|
||||
))?;
|
||||
}
|
||||
|
||||
if fee > self.amount + self.fee {
|
||||
let reason = format!(
|
||||
"Rejected the transfer because transaction fee ({}) exceeds received amount ({}).",
|
||||
amount_to_hr_string(fee, false),
|
||||
amount_to_hr_string(self.amount + self.fee, false)
|
||||
);
|
||||
info!("{}", reason);
|
||||
return Err(ErrorKind::Fee(reason.to_string()))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Verifies all of the partial signatures in the Slate are valid
|
||||
fn verify_part_sigs(&self, secp: &secp::Secp256k1) -> Result<(), Error> {
|
||||
// collect public nonces
|
||||
for p in self.participant_data.iter() {
|
||||
if p.is_complete() {
|
||||
aggsig::verify_partial_sig(
|
||||
secp,
|
||||
p.part_sig.as_ref().unwrap(),
|
||||
&self.pub_nonce_sum(secp)?,
|
||||
&p.public_blind_excess,
|
||||
Some(&self.pub_blind_sum(secp)?),
|
||||
&self.msg_to_sign()?,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Verifies any messages in the slate's participant data match their signatures
|
||||
pub fn verify_messages(&self, secp: &secp::Secp256k1) -> Result<(), Error> {
|
||||
for p in self.participant_data.iter() {
|
||||
if let Some(msg) = &p.message {
|
||||
let hashed = blake2b(secp::constants::MESSAGE_SIZE, &[], &msg.as_bytes()[..]);
|
||||
let m = secp::Message::from_slice(&hashed.as_bytes())?;
|
||||
let signature = match p.message_sig {
|
||||
None => {
|
||||
error!("verify_messages - participant message doesn't have signature. Message: \"{}\"",
|
||||
String::from_utf8_lossy(&msg.as_bytes()[..]));
|
||||
return Err(ErrorKind::Signature(
|
||||
"Optional participant messages doesn't have signature".to_owned(),
|
||||
))?;
|
||||
}
|
||||
Some(s) => s,
|
||||
};
|
||||
if !aggsig::verify_single(
|
||||
secp,
|
||||
&signature,
|
||||
&m,
|
||||
None,
|
||||
&p.public_blind_excess,
|
||||
Some(&p.public_blind_excess),
|
||||
false,
|
||||
) {
|
||||
error!("verify_messages - participant message doesn't match signature. Message: \"{}\"",
|
||||
String::from_utf8_lossy(&msg.as_bytes()[..]));
|
||||
return Err(ErrorKind::Signature(
|
||||
"Optional participant messages do not match signatures".to_owned(),
|
||||
))?;
|
||||
} else {
|
||||
info!(
|
||||
"verify_messages - signature verified ok. Participant message: \"{}\"",
|
||||
String::from_utf8_lossy(&msg.as_bytes()[..])
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 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 finalize_signature<K>(&mut self, keychain: &K) -> Result<Signature, Error>
|
||||
where
|
||||
K: Keychain,
|
||||
{
|
||||
self.verify_part_sigs(keychain.secp())?;
|
||||
|
||||
let part_sigs = self.part_sigs();
|
||||
let pub_nonce_sum = self.pub_nonce_sum(keychain.secp())?;
|
||||
let final_pubkey = self.pub_blind_sum(keychain.secp())?;
|
||||
// get the final signature
|
||||
let final_sig = aggsig::add_signatures(&keychain.secp(), part_sigs, &pub_nonce_sum)?;
|
||||
|
||||
// Calculate the final public key (for our own sanity check)
|
||||
|
||||
// Check our final sig verifies
|
||||
aggsig::verify_completed_sig(
|
||||
&keychain.secp(),
|
||||
&final_sig,
|
||||
&final_pubkey,
|
||||
Some(&final_pubkey),
|
||||
&self.msg_to_sign()?,
|
||||
)?;
|
||||
|
||||
Ok(final_sig)
|
||||
}
|
||||
|
||||
/// builds a final transaction after the aggregated sig exchange
|
||||
fn finalize_transaction<K>(
|
||||
&mut self,
|
||||
keychain: &K,
|
||||
final_sig: &secp::Signature,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
K: Keychain,
|
||||
{
|
||||
let kernel_offset = self.tx.offset;
|
||||
|
||||
self.check_fees()?;
|
||||
|
||||
let mut final_tx = self.tx.clone();
|
||||
|
||||
// build the final excess based on final tx and offset
|
||||
let final_excess = {
|
||||
// sum the input/output commitments on the final tx
|
||||
let overage = final_tx.fee() as i64;
|
||||
let tx_excess = final_tx.sum_commitments(overage)?;
|
||||
|
||||
// subtract the kernel_excess (built from kernel_offset)
|
||||
let offset_excess = keychain
|
||||
.secp()
|
||||
.commit(0, kernel_offset.secret_key(&keychain.secp())?)?;
|
||||
keychain
|
||||
.secp()
|
||||
.commit_sum(vec![tx_excess], vec![offset_excess])?
|
||||
};
|
||||
|
||||
// update the tx kernel to reflect the offset excess and sig
|
||||
assert_eq!(final_tx.kernels().len(), 1);
|
||||
final_tx.kernels_mut()[0].excess = final_excess.clone();
|
||||
final_tx.kernels_mut()[0].excess_sig = final_sig.clone();
|
||||
|
||||
// confirm the kernel verifies successfully before proceeding
|
||||
debug!("Validating final transaction");
|
||||
final_tx.kernels()[0].verify()?;
|
||||
|
||||
// confirm the overall transaction is valid (including the updated kernel)
|
||||
// accounting for tx weight limits
|
||||
let verifier_cache = Arc::new(RwLock::new(LruVerifierCache::new()));
|
||||
let _ = final_tx.validate(Weighting::AsTransaction, verifier_cache)?;
|
||||
|
||||
self.tx = final_tx;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright 2019 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.
|
||||
|
||||
//! This module contains old slate versions and conversions to the newest slate version
|
||||
//! Used for serialization and deserialization of slates in a backwards compatible way.
|
||||
#[allow(missing_docs)]
|
||||
pub mod v0;
|
||||
@@ -0,0 +1,370 @@
|
||||
// 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.
|
||||
|
||||
// Contains V0 of the slate
|
||||
use crate::core::core::transaction::{
|
||||
Input, KernelFeatures, Output, OutputFeatures, Transaction, TransactionBody, TxKernel,
|
||||
};
|
||||
use crate::keychain::BlindingFactor;
|
||||
use crate::slate::{ParticipantData, Slate};
|
||||
use crate::util::secp;
|
||||
use crate::util::secp::key::PublicKey;
|
||||
use crate::util::secp::pedersen::{Commitment, RangeProof};
|
||||
use crate::util::secp::Signature;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct SlateV0 {
|
||||
/// The number of participants intended to take part in this transaction
|
||||
pub num_participants: usize,
|
||||
/// Unique transaction ID, selected by sender
|
||||
pub id: Uuid,
|
||||
/// The core transaction data:
|
||||
/// inputs, outputs, kernels, kernel offset
|
||||
pub tx: TransactionV0,
|
||||
/// base amount (excluding fee)
|
||||
pub amount: u64,
|
||||
/// fee amount
|
||||
pub fee: u64,
|
||||
/// Block height for the transaction
|
||||
pub height: u64,
|
||||
/// Lock height
|
||||
pub lock_height: u64,
|
||||
/// Participant data, each participant in the transaction will
|
||||
/// insert their public data here. For now, 0 is sender and 1
|
||||
/// is receiver, though this will change for multi-party
|
||||
pub participant_data: Vec<ParticipantDataV0>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct ParticipantDataV0 {
|
||||
/// Id of participant in the transaction. (For now, 0=sender, 1=rec)
|
||||
pub id: u64,
|
||||
/// Public key corresponding to private blinding factor
|
||||
pub public_blind_excess: PublicKey,
|
||||
/// Public key corresponding to private nonce
|
||||
pub public_nonce: PublicKey,
|
||||
/// Public partial signature
|
||||
pub part_sig: Option<Signature>,
|
||||
/// A message for other participants
|
||||
pub message: Option<String>,
|
||||
/// Signature, created with private key corresponding to 'public_blind_excess'
|
||||
pub message_sig: Option<Signature>,
|
||||
}
|
||||
|
||||
/// A transaction
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct TransactionV0 {
|
||||
/// The kernel "offset" k2
|
||||
/// excess is k1G after splitting the key k = k1 + k2
|
||||
pub offset: BlindingFactor,
|
||||
/// The transaction body - inputs/outputs/kernels
|
||||
pub body: TransactionBodyV0,
|
||||
}
|
||||
|
||||
/// TransactionBody is a common abstraction for transaction and block
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct TransactionBodyV0 {
|
||||
/// List of inputs spent by the transaction.
|
||||
pub inputs: Vec<InputV0>,
|
||||
/// List of outputs the transaction produces.
|
||||
pub outputs: Vec<OutputV0>,
|
||||
/// List of kernels that make up this transaction (usually a single kernel).
|
||||
pub kernels: Vec<TxKernelV0>,
|
||||
}
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct InputV0 {
|
||||
/// The features of the output being spent.
|
||||
/// We will check maturity for coinbase output.
|
||||
pub features: OutputFeatures,
|
||||
/// The commit referencing the output being spent.
|
||||
pub commit: Commitment,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
pub struct OutputV0 {
|
||||
/// Options for an output's structure or use
|
||||
pub features: OutputFeatures,
|
||||
/// The homomorphic commitment representing the output amount
|
||||
pub commit: Commitment,
|
||||
/// A proof that the commitment is in the right range
|
||||
pub proof: RangeProof,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct TxKernelV0 {
|
||||
/// Options for a kernel's structure or use
|
||||
pub features: KernelFeatures,
|
||||
/// Fee originally included in the transaction this proof is for.
|
||||
pub fee: u64,
|
||||
/// This kernel is not valid earlier than lock_height blocks
|
||||
/// The max lock_height of all *inputs* to this transaction
|
||||
pub lock_height: u64,
|
||||
/// Remainder of the sum of all transaction commitments. If the transaction
|
||||
/// is well formed, amounts components should sum to zero and the excess
|
||||
/// is hence a valid public key.
|
||||
pub excess: Commitment,
|
||||
/// The signature proving the excess is a valid public key, which signs
|
||||
/// the transaction fee.
|
||||
pub excess_sig: secp::Signature,
|
||||
}
|
||||
|
||||
impl From<SlateV0> for Slate {
|
||||
fn from(slate: SlateV0) -> Slate {
|
||||
let SlateV0 {
|
||||
num_participants,
|
||||
id,
|
||||
tx,
|
||||
amount,
|
||||
fee,
|
||||
height,
|
||||
lock_height,
|
||||
participant_data,
|
||||
} = slate;
|
||||
let tx = Transaction::from(tx);
|
||||
let participant_data = map_vec!(participant_data, |data| ParticipantData::from(data));
|
||||
let version = 0;
|
||||
Slate {
|
||||
num_participants,
|
||||
id,
|
||||
tx,
|
||||
amount,
|
||||
fee,
|
||||
height,
|
||||
lock_height,
|
||||
participant_data,
|
||||
version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ParticipantDataV0> for ParticipantData {
|
||||
fn from(data: &ParticipantDataV0) -> ParticipantData {
|
||||
let ParticipantDataV0 {
|
||||
id,
|
||||
public_blind_excess,
|
||||
public_nonce,
|
||||
part_sig,
|
||||
message,
|
||||
message_sig,
|
||||
} = data;
|
||||
let id = *id;
|
||||
let public_blind_excess = *public_blind_excess;
|
||||
let public_nonce = *public_nonce;
|
||||
let part_sig = *part_sig;
|
||||
let message: Option<String> = message.as_ref().map(|t| String::from(&**t));
|
||||
let message_sig = *message_sig;
|
||||
ParticipantData {
|
||||
id,
|
||||
public_blind_excess,
|
||||
public_nonce,
|
||||
part_sig,
|
||||
message,
|
||||
message_sig,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TransactionV0> for Transaction {
|
||||
fn from(tx: TransactionV0) -> Transaction {
|
||||
let TransactionV0 { offset, body } = tx;
|
||||
let body = TransactionBody::from(&body);
|
||||
let transaction = Transaction::new(body.inputs, body.outputs, body.kernels);
|
||||
transaction.with_offset(offset)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&TransactionBodyV0> for TransactionBody {
|
||||
fn from(body: &TransactionBodyV0) -> Self {
|
||||
let TransactionBodyV0 {
|
||||
inputs,
|
||||
outputs,
|
||||
kernels,
|
||||
} = body;
|
||||
|
||||
let inputs = map_vec!(inputs, |inp| Input::from(inp));
|
||||
let outputs = map_vec!(outputs, |out| Output::from(out));
|
||||
let kernels = map_vec!(kernels, |kern| TxKernel::from(kern));
|
||||
TransactionBody {
|
||||
inputs,
|
||||
outputs,
|
||||
kernels,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&InputV0> for Input {
|
||||
fn from(input: &InputV0) -> Input {
|
||||
let InputV0 { features, commit } = *input;
|
||||
Input { features, commit }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&OutputV0> for Output {
|
||||
fn from(output: &OutputV0) -> Output {
|
||||
let OutputV0 {
|
||||
features,
|
||||
commit,
|
||||
proof,
|
||||
} = *output;
|
||||
Output {
|
||||
features,
|
||||
commit,
|
||||
proof,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&TxKernelV0> for TxKernel {
|
||||
fn from(kernel: &TxKernelV0) -> TxKernel {
|
||||
let TxKernelV0 {
|
||||
features,
|
||||
fee,
|
||||
lock_height,
|
||||
excess,
|
||||
excess_sig,
|
||||
} = *kernel;
|
||||
TxKernel {
|
||||
features,
|
||||
fee,
|
||||
lock_height,
|
||||
excess,
|
||||
excess_sig,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Slate> for SlateV0 {
|
||||
fn from(slate: Slate) -> SlateV0 {
|
||||
let Slate {
|
||||
num_participants,
|
||||
id,
|
||||
tx,
|
||||
amount,
|
||||
fee,
|
||||
height,
|
||||
lock_height,
|
||||
participant_data,
|
||||
version: _,
|
||||
} = slate;
|
||||
let tx = TransactionV0::from(tx);
|
||||
let participant_data = map_vec!(participant_data, |data| ParticipantDataV0::from(data));
|
||||
SlateV0 {
|
||||
num_participants,
|
||||
id,
|
||||
tx,
|
||||
amount,
|
||||
fee,
|
||||
height,
|
||||
lock_height,
|
||||
participant_data,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ParticipantData> for ParticipantDataV0 {
|
||||
fn from(data: &ParticipantData) -> ParticipantDataV0 {
|
||||
let ParticipantData {
|
||||
id,
|
||||
public_blind_excess,
|
||||
public_nonce,
|
||||
part_sig,
|
||||
message,
|
||||
message_sig,
|
||||
} = data;
|
||||
let id = *id;
|
||||
let public_blind_excess = *public_blind_excess;
|
||||
let public_nonce = *public_nonce;
|
||||
let part_sig = *part_sig;
|
||||
let message: Option<String> = message.as_ref().map(|t| String::from(&**t));
|
||||
let message_sig = *message_sig;
|
||||
ParticipantDataV0 {
|
||||
id,
|
||||
public_blind_excess,
|
||||
public_nonce,
|
||||
part_sig,
|
||||
message,
|
||||
message_sig,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Transaction> for TransactionV0 {
|
||||
fn from(tx: Transaction) -> TransactionV0 {
|
||||
let offset = tx.offset;
|
||||
let body: TransactionBody = tx.into();
|
||||
let body = TransactionBodyV0::from(&body);
|
||||
TransactionV0 { offset, body }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&TransactionBody> for TransactionBodyV0 {
|
||||
fn from(body: &TransactionBody) -> Self {
|
||||
let TransactionBody {
|
||||
inputs,
|
||||
outputs,
|
||||
kernels,
|
||||
} = body;
|
||||
|
||||
let inputs = map_vec!(inputs, |inp| InputV0::from(inp));
|
||||
let outputs = map_vec!(outputs, |out| OutputV0::from(out));
|
||||
let kernels = map_vec!(kernels, |kern| TxKernelV0::from(kern));
|
||||
TransactionBodyV0 {
|
||||
inputs,
|
||||
outputs,
|
||||
kernels,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Input> for InputV0 {
|
||||
fn from(input: &Input) -> Self {
|
||||
let Input { features, commit } = *input;
|
||||
InputV0 { features, commit }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Output> for OutputV0 {
|
||||
fn from(output: &Output) -> Self {
|
||||
let Output {
|
||||
features,
|
||||
commit,
|
||||
proof,
|
||||
} = *output;
|
||||
OutputV0 {
|
||||
features,
|
||||
commit,
|
||||
proof,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&TxKernel> for TxKernelV0 {
|
||||
fn from(kernel: &TxKernel) -> Self {
|
||||
let TxKernel {
|
||||
features,
|
||||
fee,
|
||||
lock_height,
|
||||
excess,
|
||||
excess_sig,
|
||||
} = *kernel;
|
||||
TxKernelV0 {
|
||||
features,
|
||||
fee,
|
||||
lock_height,
|
||||
excess,
|
||||
excess_sig,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,721 @@
|
||||
// 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.
|
||||
|
||||
//! Types and traits that should be provided by a wallet
|
||||
//! implementation
|
||||
|
||||
use crate::core::core::hash::Hash;
|
||||
use crate::core::core::Transaction;
|
||||
use crate::core::libtx::aggsig;
|
||||
use crate::core::ser;
|
||||
use crate::keychain::{Identifier, Keychain};
|
||||
use crate::error::{Error, ErrorKind};
|
||||
use crate::slate::ParticipantMessages;
|
||||
use crate::util::secp::key::{PublicKey, SecretKey};
|
||||
use crate::util::secp::{self, pedersen, Secp256k1};
|
||||
use chrono::prelude::*;
|
||||
use failure::ResultExt;
|
||||
use serde;
|
||||
use serde_json;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Lock function type
|
||||
pub type OutputLockFn<T, C, K> =
|
||||
Box<dyn FnMut(&mut T, &Transaction, PhantomData<C>, PhantomData<K>) -> Result<(), Error>>;
|
||||
|
||||
/// Combined trait to allow dynamic wallet dispatch
|
||||
pub trait WalletInst<C, K>: WalletBackend<C, K> + Send + Sync + 'static
|
||||
where
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
}
|
||||
impl<T, C, K> WalletInst<C, K> for T
|
||||
where
|
||||
T: WalletBackend<C, K> + Send + Sync + 'static,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
}
|
||||
|
||||
/// TODO:
|
||||
/// Wallets should implement this backend for their storage. All functions
|
||||
/// here expect that the wallet instance has instantiated itself or stored
|
||||
/// whatever credentials it needs
|
||||
pub trait WalletBackend<C, K>
|
||||
where
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
/// Initialize with whatever stored credentials we have
|
||||
fn open_with_credentials(&mut self) -> Result<(), Error>;
|
||||
|
||||
/// Close wallet and remove any stored credentials (TBD)
|
||||
fn close(&mut self) -> Result<(), Error>;
|
||||
|
||||
/// Return the keychain being used
|
||||
fn keychain(&mut self) -> &mut K;
|
||||
|
||||
/// Return the client being used to communicate with the node
|
||||
fn w2n_client(&mut self) -> &mut C;
|
||||
|
||||
/// return the commit for caching if allowed, none otherwise
|
||||
fn calc_commit_for_cache(
|
||||
&mut self,
|
||||
amount: u64,
|
||||
id: &Identifier,
|
||||
) -> Result<Option<String>, Error>;
|
||||
|
||||
/// Set parent key id by stored account name
|
||||
fn set_parent_key_id_by_name(&mut self, label: &str) -> Result<(), Error>;
|
||||
|
||||
/// The BIP32 path of the parent path to use for all output-related
|
||||
/// functions, (essentially 'accounts' within a wallet.
|
||||
fn set_parent_key_id(&mut self, _: Identifier);
|
||||
|
||||
/// return the parent path
|
||||
fn parent_key_id(&mut self) -> Identifier;
|
||||
|
||||
/// Iterate over all output data stored by the backend
|
||||
fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = OutputData> + 'a>;
|
||||
|
||||
/// Get output data by id
|
||||
fn get(&self, id: &Identifier, mmr_index: &Option<u64>) -> Result<OutputData, Error>;
|
||||
|
||||
/// Get an (Optional) tx log entry by uuid
|
||||
fn get_tx_log_entry(&self, uuid: &Uuid) -> Result<Option<TxLogEntry>, Error>;
|
||||
|
||||
/// Retrieves the private context associated with a given slate id
|
||||
fn get_private_context(&mut self, slate_id: &[u8]) -> Result<Context, Error>;
|
||||
|
||||
/// Iterate over all output data stored by the backend
|
||||
fn tx_log_iter<'a>(&'a self) -> Box<dyn Iterator<Item = TxLogEntry> + 'a>;
|
||||
|
||||
/// Iterate over all stored account paths
|
||||
fn acct_path_iter<'a>(&'a self) -> Box<dyn Iterator<Item = AcctPathMapping> + 'a>;
|
||||
|
||||
/// Gets an account path for a given label
|
||||
fn get_acct_path(&self, label: String) -> Result<Option<AcctPathMapping>, Error>;
|
||||
|
||||
/// Stores a transaction
|
||||
fn store_tx(&self, uuid: &str, tx: &Transaction) -> Result<(), Error>;
|
||||
|
||||
/// Retrieves a stored transaction from a TxLogEntry
|
||||
fn get_stored_tx(&self, entry: &TxLogEntry) -> Result<Option<Transaction>, Error>;
|
||||
|
||||
/// Create a new write batch to update or remove output data
|
||||
fn batch<'a>(&'a mut self) -> Result<Box<dyn WalletOutputBatch<K> + 'a>, Error>;
|
||||
|
||||
/// Next child ID when we want to create a new output, based on current parent
|
||||
fn next_child<'a>(&mut self) -> Result<Identifier, Error>;
|
||||
|
||||
/// last verified height of outputs directly descending from the given parent key
|
||||
fn last_confirmed_height<'a>(&mut self) -> Result<u64, Error>;
|
||||
|
||||
/// Attempt to restore the contents of a wallet from seed
|
||||
fn restore(&mut self) -> Result<(), Error>;
|
||||
|
||||
/// Attempt to check and fix wallet state
|
||||
fn check_repair(&mut self) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
/// Batch trait to update the output data backend atomically. Trying to use a
|
||||
/// batch after commit MAY result in a panic. Due to this being a trait, the
|
||||
/// commit method can't take ownership.
|
||||
/// TODO: Should these be split into separate batch objects, for outputs,
|
||||
/// tx_log entries and meta/details?
|
||||
pub trait WalletOutputBatch<K>
|
||||
where
|
||||
K: Keychain,
|
||||
{
|
||||
/// Return the keychain being used
|
||||
fn keychain(&mut self) -> &mut K;
|
||||
|
||||
/// Add or update data about an output to the backend
|
||||
fn save(&mut self, out: OutputData) -> Result<(), Error>;
|
||||
|
||||
/// Gets output data by id
|
||||
fn get(&self, id: &Identifier, mmr_index: &Option<u64>) -> Result<OutputData, Error>;
|
||||
|
||||
/// Iterate over all output data stored by the backend
|
||||
fn iter(&self) -> Box<dyn Iterator<Item = OutputData>>;
|
||||
|
||||
/// Delete data about an output from the backend
|
||||
fn delete(&mut self, id: &Identifier, mmr_index: &Option<u64>) -> Result<(), Error>;
|
||||
|
||||
/// Save last stored child index of a given parent
|
||||
fn save_child_index(&mut self, parent_key_id: &Identifier, child_n: u32) -> Result<(), Error>;
|
||||
|
||||
/// Save last confirmed height of outputs for a given parent
|
||||
fn save_last_confirmed_height(
|
||||
&mut self,
|
||||
parent_key_id: &Identifier,
|
||||
height: u64,
|
||||
) -> Result<(), Error>;
|
||||
|
||||
/// get next tx log entry for the parent
|
||||
fn next_tx_log_id(&mut self, parent_key_id: &Identifier) -> Result<u32, Error>;
|
||||
|
||||
/// Iterate over tx log data stored by the backend
|
||||
fn tx_log_iter(&self) -> Box<dyn Iterator<Item = TxLogEntry>>;
|
||||
|
||||
/// save a tx log entry
|
||||
fn save_tx_log_entry(&mut self, t: TxLogEntry, parent_id: &Identifier) -> Result<(), Error>;
|
||||
|
||||
/// save an account label -> path mapping
|
||||
fn save_acct_path(&mut self, mapping: AcctPathMapping) -> Result<(), Error>;
|
||||
|
||||
/// Iterate over account names stored in backend
|
||||
fn acct_path_iter(&self) -> Box<dyn Iterator<Item = AcctPathMapping>>;
|
||||
|
||||
/// Save an output as locked in the backend
|
||||
fn lock_output(&mut self, out: &mut OutputData) -> Result<(), Error>;
|
||||
|
||||
/// Saves the private context associated with a slate id
|
||||
fn save_private_context(&mut self, slate_id: &[u8], ctx: &Context) -> Result<(), Error>;
|
||||
|
||||
/// Delete the private context associated with the slate id
|
||||
fn delete_private_context(&mut self, slate_id: &[u8]) -> Result<(), Error>;
|
||||
|
||||
/// Write the wallet data to backend file
|
||||
fn commit(&self) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
/// Encapsulate all wallet-node communication functions. No functions within libwallet
|
||||
/// should care about communication details
|
||||
pub trait NodeClient: Sync + Send + Clone {
|
||||
/// Return the URL of the check node
|
||||
fn node_url(&self) -> &str;
|
||||
|
||||
/// Set the node URL
|
||||
fn set_node_url(&mut self, node_url: &str);
|
||||
|
||||
/// Return the node api secret
|
||||
fn node_api_secret(&self) -> Option<String>;
|
||||
|
||||
/// Change the API secret
|
||||
fn set_node_api_secret(&mut self, node_api_secret: Option<String>);
|
||||
|
||||
/// Posts a transaction to a grin node
|
||||
fn post_tx(&self, tx: &TxWrapper, fluff: bool) -> Result<(), Error>;
|
||||
|
||||
/// retrieves the current tip from the specified grin node
|
||||
fn get_chain_height(&self) -> Result<u64, Error>;
|
||||
|
||||
/// retrieve a list of outputs from the specified grin node
|
||||
/// need "by_height" and "by_id" variants
|
||||
fn get_outputs_from_node(
|
||||
&self,
|
||||
wallet_outputs: Vec<pedersen::Commitment>,
|
||||
) -> Result<HashMap<pedersen::Commitment, (String, u64, u64)>, Error>;
|
||||
|
||||
/// Get a list of outputs from the node by traversing the UTXO
|
||||
/// set in PMMR index order.
|
||||
/// Returns
|
||||
/// (last available output index, last insertion index retrieved,
|
||||
/// outputs(commit, proof, is_coinbase, height, mmr_index))
|
||||
fn get_outputs_by_pmmr_index(
|
||||
&self,
|
||||
start_height: u64,
|
||||
max_outputs: u64,
|
||||
) -> Result<
|
||||
(
|
||||
u64,
|
||||
u64,
|
||||
Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64, u64)>,
|
||||
),
|
||||
Error,
|
||||
>;
|
||||
}
|
||||
|
||||
/// Information about an output that's being tracked by the wallet. Must be
|
||||
/// enough to reconstruct the commitment associated with the ouput when the
|
||||
/// root private key is known.
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
|
||||
pub struct OutputData {
|
||||
/// Root key_id that the key for this output is derived from
|
||||
pub root_key_id: Identifier,
|
||||
/// Derived key for this output
|
||||
pub key_id: Identifier,
|
||||
/// How many derivations down from the root key
|
||||
pub n_child: u32,
|
||||
/// The actual commit, optionally stored
|
||||
pub commit: Option<String>,
|
||||
/// PMMR Index, used on restore in case of duplicate wallets using the same
|
||||
/// key_id (2 wallets using same seed, for instance
|
||||
pub mmr_index: Option<u64>,
|
||||
/// Value of the output, necessary to rebuild the commitment
|
||||
pub value: u64,
|
||||
/// Current status of the output
|
||||
pub status: OutputStatus,
|
||||
/// Height of the output
|
||||
pub height: u64,
|
||||
/// Height we are locked until
|
||||
pub lock_height: u64,
|
||||
/// Is this a coinbase output? Is it subject to coinbase locktime?
|
||||
pub is_coinbase: bool,
|
||||
/// Optional corresponding internal entry in tx entry log
|
||||
pub tx_log_entry: Option<u32>,
|
||||
}
|
||||
|
||||
impl ser::Writeable for OutputData {
|
||||
fn write<W: ser::Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
||||
writer.write_bytes(&serde_json::to_vec(self).map_err(|_| ser::Error::CorruptedData)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl ser::Readable for OutputData {
|
||||
fn read(reader: &mut dyn ser::Reader) -> Result<OutputData, ser::Error> {
|
||||
let data = reader.read_bytes_len_prefix()?;
|
||||
serde_json::from_slice(&data[..]).map_err(|_| ser::Error::CorruptedData)
|
||||
}
|
||||
}
|
||||
|
||||
impl OutputData {
|
||||
/// Lock a given output to avoid conflicting use
|
||||
pub fn lock(&mut self) {
|
||||
self.status = OutputStatus::Locked;
|
||||
}
|
||||
|
||||
/// How many confirmations has this output received?
|
||||
/// If height == 0 then we are either Unconfirmed or the output was
|
||||
/// cut-through
|
||||
/// so we do not actually know how many confirmations this output had (and
|
||||
/// never will).
|
||||
pub fn num_confirmations(&self, current_height: u64) -> u64 {
|
||||
if self.height > current_height {
|
||||
return 0;
|
||||
}
|
||||
if self.status == OutputStatus::Unconfirmed {
|
||||
0
|
||||
} else {
|
||||
// if an output has height n and we are at block n
|
||||
// then we have a single confirmation (the block it originated in)
|
||||
1 + (current_height - self.height)
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if output is eligible to spend based on state and height and
|
||||
/// confirmations
|
||||
pub fn eligible_to_spend(&self, current_height: u64, minimum_confirmations: u64) -> bool {
|
||||
if [OutputStatus::Spent, OutputStatus::Locked].contains(&self.status) {
|
||||
return false;
|
||||
} else if self.status == OutputStatus::Unconfirmed && self.is_coinbase {
|
||||
return false;
|
||||
} else if self.lock_height > current_height {
|
||||
return false;
|
||||
} else if self.status == OutputStatus::Unspent
|
||||
&& self.num_confirmations(current_height) >= minimum_confirmations
|
||||
{
|
||||
return true;
|
||||
} else if self.status == OutputStatus::Unconfirmed && minimum_confirmations == 0 {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Marks this output as unspent if it was previously unconfirmed
|
||||
pub fn mark_unspent(&mut self) {
|
||||
match self.status {
|
||||
OutputStatus::Unconfirmed => self.status = OutputStatus::Unspent,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark an output as spent
|
||||
pub fn mark_spent(&mut self) {
|
||||
match self.status {
|
||||
OutputStatus::Unspent => self.status = OutputStatus::Spent,
|
||||
OutputStatus::Locked => self.status = OutputStatus::Spent,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Status of an output that's being tracked by the wallet. Can either be
|
||||
/// unconfirmed, spent, unspent, or locked (when it's been used to generate
|
||||
/// a transaction but we don't have confirmation that the transaction was
|
||||
/// broadcasted or mined).
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Ord, PartialOrd)]
|
||||
pub enum OutputStatus {
|
||||
/// Unconfirmed
|
||||
Unconfirmed,
|
||||
/// Unspent
|
||||
Unspent,
|
||||
/// Locked
|
||||
Locked,
|
||||
/// Spent
|
||||
Spent,
|
||||
}
|
||||
|
||||
impl fmt::Display for OutputStatus {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
OutputStatus::Unconfirmed => write!(f, "Unconfirmed"),
|
||||
OutputStatus::Unspent => write!(f, "Unspent"),
|
||||
OutputStatus::Locked => write!(f, "Locked"),
|
||||
OutputStatus::Spent => write!(f, "Spent"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
/// Holds the context for a single aggsig transaction
|
||||
pub struct Context {
|
||||
/// Secret key (of which public is shared)
|
||||
pub sec_key: SecretKey,
|
||||
/// Secret nonce (of which public is shared)
|
||||
/// (basically a SecretKey)
|
||||
pub sec_nonce: SecretKey,
|
||||
/// store my outputs between invocations
|
||||
pub output_ids: Vec<(Identifier, Option<u64>)>,
|
||||
/// store my inputs
|
||||
pub input_ids: Vec<(Identifier, Option<u64>)>,
|
||||
/// store the calculated fee
|
||||
pub fee: u64,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
/// Create a new context with defaults
|
||||
pub fn new(secp: &secp::Secp256k1, sec_key: SecretKey) -> Context {
|
||||
Context {
|
||||
sec_key: sec_key,
|
||||
sec_nonce: aggsig::create_secnonce(secp).unwrap(),
|
||||
input_ids: vec![],
|
||||
output_ids: vec![],
|
||||
fee: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Context {
|
||||
/// Tracks an output contributing to my excess value (if it needs to
|
||||
/// be kept between invocations
|
||||
pub fn add_output(&mut self, output_id: &Identifier, mmr_index: &Option<u64>) {
|
||||
self.output_ids.push((output_id.clone(), mmr_index.clone()));
|
||||
}
|
||||
|
||||
/// Returns all stored outputs
|
||||
pub fn get_outputs(&self) -> Vec<(Identifier, Option<u64>)> {
|
||||
self.output_ids.clone()
|
||||
}
|
||||
|
||||
/// Tracks IDs of my inputs into the transaction
|
||||
/// be kept between invocations
|
||||
pub fn add_input(&mut self, input_id: &Identifier, mmr_index: &Option<u64>) {
|
||||
self.input_ids.push((input_id.clone(), mmr_index.clone()));
|
||||
}
|
||||
|
||||
/// Returns all stored input identifiers
|
||||
pub fn get_inputs(&self) -> Vec<(Identifier, Option<u64>)> {
|
||||
self.input_ids.clone()
|
||||
}
|
||||
|
||||
/// Returns private key, private nonce
|
||||
pub fn get_private_keys(&self) -> (SecretKey, SecretKey) {
|
||||
(self.sec_key.clone(), self.sec_nonce.clone())
|
||||
}
|
||||
|
||||
/// Returns public key, public nonce
|
||||
pub fn get_public_keys(&self, secp: &Secp256k1) -> (PublicKey, PublicKey) {
|
||||
(
|
||||
PublicKey::from_secret_key(secp, &self.sec_key).unwrap(),
|
||||
PublicKey::from_secret_key(secp, &self.sec_nonce).unwrap(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ser::Writeable for Context {
|
||||
fn write<W: ser::Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
||||
writer.write_bytes(&serde_json::to_vec(self).map_err(|_| ser::Error::CorruptedData)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl ser::Readable for Context {
|
||||
fn read(reader: &mut dyn ser::Reader) -> Result<Context, ser::Error> {
|
||||
let data = reader.read_bytes_len_prefix()?;
|
||||
serde_json::from_slice(&data[..]).map_err(|_| ser::Error::CorruptedData)
|
||||
}
|
||||
}
|
||||
|
||||
/// Block Identifier
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
|
||||
pub struct BlockIdentifier(pub Hash);
|
||||
|
||||
impl BlockIdentifier {
|
||||
/// return hash
|
||||
pub fn hash(&self) -> Hash {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// convert to hex string
|
||||
pub fn from_hex(hex: &str) -> Result<BlockIdentifier, Error> {
|
||||
let hash =
|
||||
Hash::from_hex(hex).context(ErrorKind::GenericError("Invalid hex".to_owned()))?;
|
||||
Ok(BlockIdentifier(hash))
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::ser::Serialize for BlockIdentifier {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::ser::Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.0.to_hex())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::de::Deserialize<'de> for BlockIdentifier {
|
||||
fn deserialize<D>(deserializer: D) -> Result<BlockIdentifier, D::Error>
|
||||
where
|
||||
D: serde::de::Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_str(BlockIdentifierVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
struct BlockIdentifierVisitor;
|
||||
|
||||
impl<'de> serde::de::Visitor<'de> for BlockIdentifierVisitor {
|
||||
type Value = BlockIdentifier;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("a block hash")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
let block_hash = Hash::from_hex(s).unwrap();
|
||||
Ok(BlockIdentifier(block_hash))
|
||||
}
|
||||
}
|
||||
|
||||
/// Fees in block to use for coinbase amount calculation
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct BlockFees {
|
||||
/// fees
|
||||
pub fees: u64,
|
||||
/// height
|
||||
pub height: u64,
|
||||
/// key id
|
||||
pub key_id: Option<Identifier>,
|
||||
}
|
||||
|
||||
impl BlockFees {
|
||||
/// return key id
|
||||
pub fn key_id(&self) -> Option<Identifier> {
|
||||
self.key_id.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Response to build a coinbase output.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct CbData {
|
||||
/// Output
|
||||
pub output: String,
|
||||
/// Kernel
|
||||
pub kernel: String,
|
||||
/// Key Id
|
||||
pub key_id: String,
|
||||
}
|
||||
|
||||
/// a contained wallet info struct, so automated tests can parse wallet info
|
||||
/// can add more fields here over time as needed
|
||||
#[derive(Serialize, Eq, PartialEq, Deserialize, Debug, Clone)]
|
||||
pub struct WalletInfo {
|
||||
/// height from which info was taken
|
||||
pub last_confirmed_height: u64,
|
||||
/// Minimum number of confirmations for an output to be treated as "spendable".
|
||||
pub minimum_confirmations: u64,
|
||||
/// total amount in the wallet
|
||||
pub total: u64,
|
||||
/// amount awaiting confirmation
|
||||
pub amount_awaiting_confirmation: u64,
|
||||
/// coinbases waiting for lock height
|
||||
pub amount_immature: u64,
|
||||
/// amount currently spendable
|
||||
pub amount_currently_spendable: u64,
|
||||
/// amount locked via previous transactions
|
||||
pub amount_locked: u64,
|
||||
}
|
||||
|
||||
/// Types of transactions that can be contained within a TXLog entry
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
|
||||
pub enum TxLogEntryType {
|
||||
/// A coinbase transaction becomes confirmed
|
||||
ConfirmedCoinbase,
|
||||
/// Outputs created when a transaction is received
|
||||
TxReceived,
|
||||
/// Inputs locked + change outputs when a transaction is created
|
||||
TxSent,
|
||||
/// Received transaction that was rolled back by user
|
||||
TxReceivedCancelled,
|
||||
/// Sent transaction that was rolled back by user
|
||||
TxSentCancelled,
|
||||
}
|
||||
|
||||
impl fmt::Display for TxLogEntryType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
TxLogEntryType::ConfirmedCoinbase => write!(f, "Confirmed \nCoinbase"),
|
||||
TxLogEntryType::TxReceived => write!(f, "Received Tx"),
|
||||
TxLogEntryType::TxSent => write!(f, "Sent Tx"),
|
||||
TxLogEntryType::TxReceivedCancelled => write!(f, "Received Tx\n- Cancelled"),
|
||||
TxLogEntryType::TxSentCancelled => write!(f, "Sent Tx\n- Cancelled"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Optional transaction information, recorded when an event happens
|
||||
/// to add or remove funds from a wallet. One Transaction log entry
|
||||
/// maps to one or many outputs
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct TxLogEntry {
|
||||
/// BIP32 account path used for creating this tx
|
||||
pub parent_key_id: Identifier,
|
||||
/// Local id for this transaction (distinct from a slate transaction id)
|
||||
pub id: u32,
|
||||
/// Slate transaction this entry is associated with, if any
|
||||
pub tx_slate_id: Option<Uuid>,
|
||||
/// Transaction type (as above)
|
||||
pub tx_type: TxLogEntryType,
|
||||
/// Time this tx entry was created
|
||||
/// #[serde(with = "tx_date_format")]
|
||||
pub creation_ts: DateTime<Utc>,
|
||||
/// Time this tx was confirmed (by this wallet)
|
||||
/// #[serde(default, with = "opt_tx_date_format")]
|
||||
pub confirmation_ts: Option<DateTime<Utc>>,
|
||||
/// Whether the inputs+outputs involved in this transaction have been
|
||||
/// confirmed (In all cases either all outputs involved in a tx should be
|
||||
/// confirmed, or none should be; otherwise there's a deeper problem)
|
||||
pub confirmed: bool,
|
||||
/// number of inputs involved in TX
|
||||
pub num_inputs: usize,
|
||||
/// number of outputs involved in TX
|
||||
pub num_outputs: usize,
|
||||
/// Amount credited via this transaction
|
||||
pub amount_credited: u64,
|
||||
/// Amount debited via this transaction
|
||||
pub amount_debited: u64,
|
||||
/// Fee
|
||||
pub fee: Option<u64>,
|
||||
/// Message data, stored as json
|
||||
pub messages: Option<ParticipantMessages>,
|
||||
/// Location of the store transaction, (reference or resending)
|
||||
pub stored_tx: Option<String>,
|
||||
}
|
||||
|
||||
impl ser::Writeable for TxLogEntry {
|
||||
fn write<W: ser::Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
||||
writer.write_bytes(&serde_json::to_vec(self).map_err(|_| ser::Error::CorruptedData)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl ser::Readable for TxLogEntry {
|
||||
fn read(reader: &mut dyn ser::Reader) -> Result<TxLogEntry, ser::Error> {
|
||||
let data = reader.read_bytes_len_prefix()?;
|
||||
serde_json::from_slice(&data[..]).map_err(|_| ser::Error::CorruptedData)
|
||||
}
|
||||
}
|
||||
|
||||
impl TxLogEntry {
|
||||
/// Return a new blank with TS initialised with next entry
|
||||
pub fn new(parent_key_id: Identifier, t: TxLogEntryType, id: u32) -> Self {
|
||||
TxLogEntry {
|
||||
parent_key_id: parent_key_id,
|
||||
tx_type: t,
|
||||
id: id,
|
||||
tx_slate_id: None,
|
||||
creation_ts: Utc::now(),
|
||||
confirmation_ts: None,
|
||||
confirmed: false,
|
||||
amount_credited: 0,
|
||||
amount_debited: 0,
|
||||
num_inputs: 0,
|
||||
num_outputs: 0,
|
||||
fee: None,
|
||||
messages: None,
|
||||
stored_tx: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a vec of TX log entries, return credited + debited sums
|
||||
pub fn sum_confirmed(txs: &Vec<TxLogEntry>) -> (u64, u64) {
|
||||
txs.iter().fold((0, 0), |acc, tx| match tx.confirmed {
|
||||
true => (acc.0 + tx.amount_credited, acc.1 + tx.amount_debited),
|
||||
false => acc,
|
||||
})
|
||||
}
|
||||
|
||||
/// Update confirmation TS with now
|
||||
pub fn update_confirmation_ts(&mut self) {
|
||||
self.confirmation_ts = Some(Utc::now());
|
||||
}
|
||||
}
|
||||
|
||||
/// Map of named accounts to BIP32 paths
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct AcctPathMapping {
|
||||
/// label used by user
|
||||
pub label: String,
|
||||
/// Corresponding parent BIP32 derivation path
|
||||
pub path: Identifier,
|
||||
}
|
||||
|
||||
impl ser::Writeable for AcctPathMapping {
|
||||
fn write<W: ser::Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
||||
writer.write_bytes(&serde_json::to_vec(self).map_err(|_| ser::Error::CorruptedData)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl ser::Readable for AcctPathMapping {
|
||||
fn read(reader: &mut dyn ser::Reader) -> Result<AcctPathMapping, ser::Error> {
|
||||
let data = reader.read_bytes_len_prefix()?;
|
||||
serde_json::from_slice(&data[..]).map_err(|_| ser::Error::CorruptedData)
|
||||
}
|
||||
}
|
||||
|
||||
/// Dummy wrapper for the hex-encoded serialized transaction.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct TxWrapper {
|
||||
/// hex representation of transaction
|
||||
pub tx_hex: String,
|
||||
}
|
||||
|
||||
/// Send TX API Args
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct SendTXArgs {
|
||||
/// amount to send
|
||||
pub amount: u64,
|
||||
/// minimum confirmations
|
||||
pub minimum_confirmations: u64,
|
||||
/// payment method
|
||||
pub method: String,
|
||||
/// destination url
|
||||
pub dest: String,
|
||||
/// Max number of outputs
|
||||
pub max_outputs: usize,
|
||||
/// Number of change outputs to generate
|
||||
pub num_change_outputs: usize,
|
||||
/// whether to use all outputs (combine)
|
||||
pub selection_strategy_is_use_all: bool,
|
||||
/// Optional message, that will be signed
|
||||
pub message: Option<String>,
|
||||
}
|
||||
Reference in New Issue
Block a user