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:
committed by
Ignotus Peverell
parent
dc0dbc62be
commit
bf7c1fb44f
+1
-1
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user