add lock_height to Transaction and TxKernel (#167)

* add lock_height to Transaction and TxKernel, sign msg including both fee and lock_height in kernel
* make the order of the fields in tx and kernel more consistent
* rename to kernel_sig_msg
* add test to cover adding timelocked tx to pool, where tx is invalid based on current height of the blockchain
* add tests for adding timelocked txs to blocks (valid and otherwise)
This commit is contained in:
AntiochP
2017-10-11 14:12:01 -04:00
committed by Ignotus Peverell
parent dc0dbc62be
commit bf7c1fb44f
20 changed files with 432 additions and 167 deletions
+1 -1
View File
@@ -271,7 +271,7 @@ mod tests {
proof: keychain.range_proof(100, &pk1, output_commit, msg).unwrap(),
},
];
let test_transaction = core::transaction::Transaction::new(inputs, outputs, 5);
let test_transaction = core::transaction::Transaction::new(inputs, outputs, 5, 0);
let test_pool_entry = PoolEntry::new(&test_transaction);
+79 -12
View File
@@ -153,6 +153,13 @@ where
return Err(PoolError::AlreadyInPool);
}
let head_header = self.blockchain.head_header()?;
if head_header.height < tx.lock_height {
return Err(PoolError::ImmatureTransaction {
lock_height: tx.lock_height,
});
}
// The next issue is to identify all unspent outputs that
// this transaction will consume and make sure they exist in the set.
let mut pool_refs: Vec<graph::Edge> = Vec::new();
@@ -174,15 +181,12 @@ where
if let Ok(out_header) = self.blockchain
.get_block_header_by_output_commit(&output.commitment())
{
if let Ok(head_header) = self.blockchain.head_header() {
if head_header.height <=
out_header.height + global::coinbase_maturity()
{
return Err(PoolError::ImmatureCoinbase {
header: out_header,
output: output.commitment(),
});
};
let lock_height = out_header.height + global::coinbase_maturity();
if head_header.height < lock_height {
return Err(PoolError::ImmatureCoinbase {
header: out_header,
output: output.commitment(),
});
};
};
};
@@ -549,6 +553,10 @@ where
/// Fetch mineable transactions.
///
/// Select a set of mineable transactions for block building.
///
/// TODO - txs have lock_heights, so possible to have "invalid" (immature)
/// txs here?
///
pub fn prepare_mineable_transactions(
&self,
num_to_fetch: u32,
@@ -589,7 +597,6 @@ where
#[cfg(test)]
mod tests {
use super::*;
use types::*;
use core::core::build;
use blockchain::{DummyChain, DummyChainImpl, DummyUtxoSet};
use secp;
@@ -617,6 +624,11 @@ mod tests {
/// A basic test; add a pair of transactions to the pool.
fn test_basic_pool_add() {
let mut dummy_chain = DummyChainImpl::new();
let head_header = block::BlockHeader {
height: 1,
..block::BlockHeader::default()
};
dummy_chain.store_head_header(&head_header);
let parent_transaction = test_transaction(vec![5, 6, 7], vec![11, 3]);
// We want this transaction to be rooted in the blockchain.
@@ -674,6 +686,11 @@ mod tests {
/// Testing various expected error conditions
pub fn test_pool_add_error() {
let mut dummy_chain = DummyChainImpl::new();
let head_header = block::BlockHeader {
height: 1,
..block::BlockHeader::default()
};
dummy_chain.store_head_header(&head_header);
let new_utxo = DummyUtxoSet::empty()
.with_output(test_output(5))
@@ -757,6 +774,17 @@ mod tests {
};
assert_eq!(write_pool.total_size(), 1);
// now attempt to add a timelocked tx to the pool
// should fail as invalid based on current height
let timelocked_tx_1 = timelocked_transaction(vec![9], vec![5], 10);
match write_pool.add_to_memory_pool(test_source(), timelocked_tx_1) {
Err(PoolError::ImmatureTransaction { lock_height: height }) => {
assert_eq!(height, 10);
}
Err(e) => panic!("expected ImmatureTransaction error here - {:?}", e),
Ok(_) => panic!("expected ImmatureTransaction error here"),
};
}
}
@@ -801,7 +829,7 @@ mod tests {
};
let head_header = block::BlockHeader {
height: 4,
height: 3,
..block::BlockHeader::default()
};
chain_ref.store_head_header(&head_header);
@@ -843,6 +871,11 @@ mod tests {
/// Testing block reconciliation
fn test_block_reconciliation() {
let mut dummy_chain = DummyChainImpl::new();
let head_header = block::BlockHeader {
height: 1,
..block::BlockHeader::default()
};
dummy_chain.store_head_header(&head_header);
let new_utxo = DummyUtxoSet::empty()
.with_output(test_output(10))
@@ -992,6 +1025,11 @@ mod tests {
fn test_block_building() {
// Add a handful of transactions
let mut dummy_chain = DummyChainImpl::new();
let head_header = block::BlockHeader {
height: 1,
..block::BlockHeader::default()
};
dummy_chain.store_head_header(&head_header);
let new_utxo = DummyUtxoSet::empty()
.with_output(test_output(10))
@@ -1005,7 +1043,7 @@ mod tests {
let pool = RwLock::new(test_setup(&chain_ref));
let root_tx_1 = test_transaction(vec![10,20], vec![24]);
let root_tx_1 = test_transaction(vec![10, 20], vec![24]);
let root_tx_2 = test_transaction(vec![30], vec![28]);
let root_tx_3 = test_transaction(vec![40], vec![38]);
@@ -1106,6 +1144,35 @@ mod tests {
tx
}
fn timelocked_transaction(
input_values: Vec<u64>,
output_values: Vec<u64>,
lock_height: u64,
) -> transaction::Transaction {
let keychain = keychain_for_tests();
let fees: i64 = input_values.iter().sum::<u64>() as i64 -
output_values.iter().sum::<u64>() as i64;
assert!(fees >= 0);
let mut tx_elements = Vec::new();
for input_value in input_values {
let pubkey = keychain.derive_pubkey(input_value as u32).unwrap();
tx_elements.push(build::input(input_value, pubkey));
}
for output_value in output_values {
let pubkey = keychain.derive_pubkey(output_value as u32).unwrap();
tx_elements.push(build::output(output_value, pubkey));
}
tx_elements.push(build::with_fee(fees as u64));
tx_elements.push(build::with_lock_height(lock_height));
let (tx, _) = build::transaction(tx_elements, &keychain).unwrap();
tx
}
/// Deterministically generate an output defined by our test scheme
fn test_output(value: u64) -> transaction::Output {
let keychain = keychain_for_tests();
+10 -3
View File
@@ -115,13 +115,20 @@ pub enum PoolError {
/// The spent output
spent_output: Commitment,
},
/// Attempt to spend a coinbase output before it matures (1000 blocks?)
/// Attempt to spend an output before it matures
/// lock_height must not exceed current block height
ImmatureCoinbase {
/// The block header of the block containing the coinbase output
/// The block header of the block containing the output
header: block::BlockHeader,
/// The unspent coinbase output
/// The unspent output
output: Commitment,
},
/// Attempt to add a transaction to the pool with lock_height
/// greater than height of current block
ImmatureTransaction {
/// The lock height of the invalid transaction
lock_height: u64,
},
/// An orphan successfully added to the orphans set
OrphanTransaction,
/// TODO - wip, just getting imports working, remove this and use more