mirror of
https://github.com/mimblewimble/grin.git
synced 2025-01-22 12:01:08 +03:00
0967a5302b
* Add kernel commitments sum and kernel and output MMR sizes to block header * Sum a block without including previous sums, cleanup. Blocks are now summed and validated based on their own totals and not the totals since genesis. This allows to get rid of BlockSum and simplified the setting of a new block's roots, kernel sum and MMR sizes. Fixes #116 * Additional kernel MMR validation to check all prior header roots successively * Wallet tests fix
223 lines
7.2 KiB
Rust
223 lines
7.2 KiB
Rust
// Copyright 2018 The Grin Developers
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
//! Common functions to facilitate wallet, walletlib and transaction testing
|
|
use std::collections::HashMap;
|
|
use std::collections::hash_map::Entry;
|
|
|
|
extern crate grin_api as api;
|
|
extern crate grin_chain as chain;
|
|
extern crate grin_core as core;
|
|
extern crate grin_keychain as keychain;
|
|
extern crate grin_wallet as wallet;
|
|
extern crate time;
|
|
|
|
use chain::Chain;
|
|
use core::core::hash::Hashed;
|
|
use core::core::{Output, OutputFeatures, OutputIdentifier, Transaction, TxKernel};
|
|
use core::{consensus, global, pow};
|
|
use keychain::ExtKeychain;
|
|
use wallet::file_wallet::{FileWallet, WalletConfig};
|
|
use wallet::libwallet::internal::updater;
|
|
use wallet::libwallet::types::{BlockFees, BlockIdentifier, MerkleProofWrapper, OutputStatus,
|
|
WalletBackend};
|
|
use wallet::libwallet::{Error, ErrorKind};
|
|
|
|
use util;
|
|
use util::secp::pedersen;
|
|
|
|
/// Mostly for testing, refreshes output state against a local chain instance
|
|
/// instead of via an http API call
|
|
pub fn refresh_output_state_local<T, K>(wallet: &mut T, chain: &chain::Chain) -> Result<(), Error>
|
|
where
|
|
T: WalletBackend<K>,
|
|
K: keychain::Keychain,
|
|
{
|
|
let wallet_outputs = updater::map_wallet_outputs(wallet)?;
|
|
let chain_outputs: Vec<Option<api::Output>> = wallet_outputs
|
|
.keys()
|
|
.map(|k| match get_output_local(chain, &k) {
|
|
Err(_) => None,
|
|
Ok(k) => Some(k),
|
|
})
|
|
.collect();
|
|
let mut api_outputs: HashMap<pedersen::Commitment, String> = HashMap::new();
|
|
for out in chain_outputs {
|
|
match out {
|
|
Some(o) => {
|
|
api_outputs.insert(o.commit.commit(), util::to_hex(o.commit.to_vec()));
|
|
}
|
|
None => {}
|
|
}
|
|
}
|
|
let height = chain.head().unwrap().height;
|
|
updater::apply_api_outputs(wallet, &wallet_outputs, &api_outputs, height)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Return the spendable wallet balance from the local chain
|
|
/// (0:total, 1:amount_awaiting_confirmation, 2:confirmed but locked,
|
|
/// 3:currently_spendable, 4:locked total) TODO: Should be a wallet lib
|
|
/// function with nicer return values
|
|
pub fn get_wallet_balances<T, K>(
|
|
wallet: &mut T,
|
|
height: u64,
|
|
) -> Result<(u64, u64, u64, u64, u64), Error>
|
|
where
|
|
T: WalletBackend<K>,
|
|
K: keychain::Keychain,
|
|
{
|
|
let ret_val = wallet.read_wallet(|wallet_data| {
|
|
let mut unspent_total = 0;
|
|
let mut unspent_but_locked_total = 0;
|
|
let mut unconfirmed_total = 0;
|
|
let mut locked_total = 0;
|
|
let keychain = wallet_data.keychain().clone();
|
|
for out in wallet_data
|
|
.outputs()
|
|
.values()
|
|
.filter(|out| out.root_key_id == keychain.root_key_id())
|
|
{
|
|
if out.status == OutputStatus::Unspent {
|
|
unspent_total += out.value;
|
|
if out.lock_height > height {
|
|
unspent_but_locked_total += out.value;
|
|
}
|
|
}
|
|
if out.status == OutputStatus::Unconfirmed && !out.is_coinbase {
|
|
unconfirmed_total += out.value;
|
|
}
|
|
if out.status == OutputStatus::Locked {
|
|
locked_total += out.value;
|
|
}
|
|
}
|
|
|
|
Ok((
|
|
unspent_total + unconfirmed_total, //total
|
|
unconfirmed_total, //amount_awaiting_confirmation
|
|
unspent_but_locked_total, // confirmed but locked
|
|
unspent_total - unspent_but_locked_total, // currently spendable
|
|
locked_total, // locked total
|
|
))
|
|
});
|
|
ret_val
|
|
}
|
|
|
|
/// Get an output from the chain locally and present it back as an API output
|
|
fn get_output_local(
|
|
chain: &chain::Chain,
|
|
commit: &pedersen::Commitment,
|
|
) -> Result<api::Output, Error> {
|
|
let outputs = [
|
|
OutputIdentifier::new(OutputFeatures::DEFAULT_OUTPUT, commit),
|
|
OutputIdentifier::new(OutputFeatures::COINBASE_OUTPUT, commit),
|
|
];
|
|
|
|
for x in outputs.iter() {
|
|
if let Ok(_) = chain.is_unspent(&x) {
|
|
return Ok(api::Output::new(&commit));
|
|
}
|
|
}
|
|
Err(ErrorKind::GenericError(
|
|
"Can't get output from local instance of chain",
|
|
))?
|
|
}
|
|
|
|
/// Adds a block with a given reward to the chain and mines it
|
|
pub fn add_block_with_reward(chain: &Chain, txs: Vec<&Transaction>, reward: (Output, TxKernel)) {
|
|
let prev = chain.head_header().unwrap();
|
|
let difficulty = consensus::next_difficulty(chain.difficulty_iter()).unwrap();
|
|
let mut b = core::core::Block::new(
|
|
&prev,
|
|
txs.into_iter().cloned().collect(),
|
|
difficulty.clone(),
|
|
reward,
|
|
).unwrap();
|
|
b.header.timestamp = prev.timestamp + time::Duration::seconds(60);
|
|
chain.set_block_roots(&mut b, false).unwrap();
|
|
pow::pow_size(
|
|
&mut b.header,
|
|
difficulty,
|
|
global::proofsize(),
|
|
global::sizeshift(),
|
|
).unwrap();
|
|
chain.process_block(b, chain::Options::MINE).unwrap();
|
|
chain.validate(false).unwrap();
|
|
}
|
|
|
|
/// adds a reward output to a wallet, includes that reward in a block, mines
|
|
/// the block and adds it to the chain, with option transactions included.
|
|
/// Helpful for building up precise wallet balances for testing.
|
|
pub fn award_block_to_wallet<T, K>(chain: &Chain, txs: Vec<&Transaction>, wallet: &mut T)
|
|
where
|
|
T: WalletBackend<K>,
|
|
K: keychain::Keychain,
|
|
{
|
|
let prev = chain.head_header().unwrap();
|
|
let fee_amt = txs.iter().map(|tx| tx.fee()).sum();
|
|
let fees = BlockFees {
|
|
fees: fee_amt,
|
|
key_id: None,
|
|
height: prev.height + 1,
|
|
};
|
|
let coinbase_tx = wallet::libwallet::internal::updater::receive_coinbase(wallet, &fees);
|
|
let (coinbase_tx, fees) = match coinbase_tx {
|
|
Ok(t) => ((t.0, t.1), t.2),
|
|
Err(e) => {
|
|
panic!("Unable to create block reward: {:?}", e);
|
|
}
|
|
};
|
|
add_block_with_reward(chain, txs, coinbase_tx.clone());
|
|
// build merkle proof and block identifier and save in wallet
|
|
let output_id = OutputIdentifier::from_output(&coinbase_tx.0.clone());
|
|
let m_proof = chain.get_merkle_proof(&output_id, &chain.head_header().unwrap());
|
|
let block_id = Some(BlockIdentifier(chain.head_header().unwrap().hash()));
|
|
let _ = wallet.with_wallet(|wallet_data| {
|
|
if let Entry::Occupied(mut output) = wallet_data
|
|
.outputs()
|
|
.entry(fees.key_id.as_ref().unwrap().to_hex())
|
|
{
|
|
let output = output.get_mut();
|
|
output.block = block_id;
|
|
output.merkle_proof = Some(MerkleProofWrapper(m_proof.unwrap()));
|
|
}
|
|
});
|
|
}
|
|
|
|
/// adds many block rewards to a wallet, no transactions
|
|
pub fn award_blocks_to_wallet<T, K>(chain: &Chain, wallet: &mut T, num_rewards: usize)
|
|
where
|
|
T: WalletBackend<K>,
|
|
K: keychain::Keychain,
|
|
{
|
|
for _ in 0..num_rewards {
|
|
award_block_to_wallet(chain, vec![], wallet);
|
|
}
|
|
}
|
|
|
|
/// Create a new wallet in a particular directory
|
|
pub fn create_wallet(dir: &str) -> FileWallet<ExtKeychain> {
|
|
let mut wallet_config = WalletConfig::default();
|
|
wallet_config.data_file_dir = String::from(dir);
|
|
wallet::WalletSeed::init_file(&wallet_config).expect("Failed to create wallet seed file.");
|
|
let mut wallet = FileWallet::new(wallet_config.clone(), "")
|
|
.unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, wallet_config));
|
|
wallet.open_with_credentials().unwrap_or_else(|e| {
|
|
panic!(
|
|
"Error initializing wallet: {:?} Config: {:?}",
|
|
e, wallet_config
|
|
)
|
|
});
|
|
wallet
|
|
}
|