refactor burn key into key_overrides on keychain (#178)

* refactor burn key into key_overrides on keychain
* introduce UnconfirmedChange output status, we can potentially spend these with zero confirmations
* pass in burn_key_id for the burn enabled keychain, spend *all* coins when spending from a wallet, spend UnconfirmedChange coins also
* add comment about simplifying wallet_data.select logic
* replace UnconfirmedChange output status with a more flexible zero_ok, flag on the output data
This commit is contained in:
AntiochP
2017-10-16 13:11:01 -04:00
committed by Ignotus Peverell
parent 472912c68c
commit c84a136e48
6 changed files with 66 additions and 53 deletions
+3 -2
View File
@@ -24,7 +24,7 @@ pub fn show_info(config: &WalletConfig, keychain: &Keychain) {
let _ = WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
println!("Outputs - ");
println!("key_id, height, lock_height, status, value");
println!("key_id, height, lock_height, status, zero_ok, value");
println!("----------------------------------");
let mut outputs = wallet_data
@@ -35,11 +35,12 @@ pub fn show_info(config: &WalletConfig, keychain: &Keychain) {
outputs.sort_by_key(|out| out.n_child);
for out in outputs {
println!(
"{}, {}, {}, {:?}, {}",
"{}, {}, {}, {:?}, {}, {}",
out.key_id,
out.height,
out.lock_height,
out.status,
out.zero_ok,
out.value
);
}
+7 -2
View File
@@ -183,8 +183,11 @@ fn receive_coinbase(config: &WalletConfig,
let key_id = block_fees.key_id();
let (key_id, derivation) = match key_id {
Some(key_id) => {
let derivation = keychain.derivation_from_key_id(&key_id)?;
(key_id.clone(), derivation)
if let Some(existing) = wallet_data.get_output(&key_id) {
(existing.key_id.clone(), existing.n_child)
} else {
panic!("should never happen");
}
},
None => {
let derivation = wallet_data.next_child(root_key_id.clone());
@@ -202,6 +205,7 @@ fn receive_coinbase(config: &WalletConfig,
status: OutputStatus::Unconfirmed,
height: 0,
lock_height: 0,
zero_ok: false,
});
debug!(
@@ -276,6 +280,7 @@ fn receive_transaction(
status: OutputStatus::Unconfirmed,
height: 0,
lock_height: 0,
zero_ok: false,
});
debug!(
LOGGER,
+12 -13
View File
@@ -16,7 +16,7 @@ use api;
use checker;
use core::core::{Transaction, build};
use core::ser;
use keychain::{BlindingFactor, Keychain, Identifier, IDENTIFIER_SIZE};
use keychain::{BlindingFactor, Keychain, Identifier};
use receiver::TxWrapper;
use types::*;
use util::LOGGER;
@@ -72,12 +72,10 @@ fn build_send_tx(
WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
// select some suitable outputs to spend from our local wallet
let (coins, change) = wallet_data.select(key_id.clone(), amount);
if change < 0 {
return Err(Error::NotEnoughFunds((-change) as u64));
}
let (coins, _) = wallet_data.select(key_id.clone(), u64::max_value());
// build transaction skeleton with inputs and change
// TODO - should probably also check we are sending enough to cover the fees + non-zero output
let mut parts = inputs_and_change(&coins, keychain, key_id, wallet_data, amount)?;
// This is more proof of concept than anything but here we set a
@@ -92,8 +90,11 @@ fn build_send_tx(
}
pub fn issue_burn_tx(config: &WalletConfig, keychain: &Keychain, amount: u64) -> Result<(), Error> {
let keychain = &Keychain::burn_enabled(keychain, &Identifier::zero());
let _ = checker::refresh_outputs(config, keychain);
let key_id = keychain.clone().root_key_id();
let key_id = keychain.root_key_id();
// operate within a lock on wallet data
WalletData::with_wallet(&config.data_file_dir, |mut wallet_data| {
@@ -105,10 +106,8 @@ pub fn issue_burn_tx(config: &WalletConfig, keychain: &Keychain, amount: u64) ->
let mut parts = inputs_and_change(&coins, keychain, key_id, &mut wallet_data, amount)?;
// add burn output and fees
parts.push(build::output(
amount,
Identifier::from_bytes(&[0; IDENTIFIER_SIZE]),
));
let fee = tx_fee(coins.len(), 2, None);
parts.push(build::output(amount - fee, Identifier::zero()));
// finalize the burn transaction and send
let (tx_burn, _) = build::transaction(parts, &keychain)?;
@@ -162,8 +161,7 @@ fn inputs_and_change(
let change_key = keychain.derive_key_id(change_derivation)?;
parts.push(build::output(change, change_key.clone()));
// we got that far, time to start tracking the new output
// and lock the outputs used
// we got that far, time to start tracking the output representing our change
wallet_data.add_output(OutputData {
root_key_id: root_key_id.clone(),
key_id: change_key.clone(),
@@ -172,9 +170,10 @@ fn inputs_and_change(
status: OutputStatus::Unconfirmed,
height: 0,
lock_height: 0,
zero_ok: true,
});
// lock the ouputs we're spending
// now lock the ouputs we're spending so we avoid accidental double spend attempt
for coin in coins {
wallet_data.lock_output(coin);
}
+11 -3
View File
@@ -168,6 +168,8 @@ pub struct OutputData {
pub height: u64,
/// Height we are locked until
pub lock_height: u64,
/// Can we spend with zero confirmations? (Did it originate from us, change output etc.)
pub zero_ok: bool,
}
impl OutputData {
@@ -307,16 +309,22 @@ impl WalletData {
}
}
pub fn get_output(&self, key_id: &keychain::Identifier) -> Option<&OutputData> {
self.outputs.get(&key_id.to_hex())
}
/// Select a subset of unspent outputs to spend in a transaction
/// transferring the provided amount.
pub fn select(&self, root_key_id: keychain::Identifier, amount: u64) -> (Vec<OutputData>, i64) {
let mut to_spend = vec![];
let mut input_total = 0;
// TODO very naive impl for now - definitely better coin selection
// algos available
for out in self.outputs.values() {
if out.status == OutputStatus::Unspent && out.root_key_id == root_key_id {
if out.root_key_id == root_key_id
&& (out.status == OutputStatus::Unspent)
// the following will let us spend zero confirmation change outputs
// || (out.status == OutputStatus::Unconfirmed && out.zero_ok))
{
to_spend.push(out.clone());
input_total += out.value;
if input_total >= amount {