Merge pull request #328 from ethcore/fix_319
Replace non-final transactions in mempool + block assembler won't include such transactions in block
This commit is contained in:
commit
64c059ee71
|
@ -68,7 +68,7 @@ impl Block {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_final(&self, height: u32) -> bool {
|
pub fn is_final(&self, height: u32) -> bool {
|
||||||
self.transactions.iter().all(|t| t.is_final(height, self.block_header.time))
|
self.transactions.iter().all(|t| t.is_final_in_block(height, self.block_header.time))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,7 @@ impl IndexedBlock {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_final(&self, height: u32) -> bool {
|
pub fn is_final(&self, height: u32) -> bool {
|
||||||
self.transactions.iter().all(|tx| tx.raw.is_final(height, self.header.raw.time))
|
self.transactions.iter().all(|tx| tx.raw.is_final_in_block(height, self.header.raw.time))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -229,7 +229,17 @@ impl Transaction {
|
||||||
self.inputs.len() == 1 && self.inputs[0].previous_output.is_null()
|
self.inputs.len() == 1 && self.inputs[0].previous_output.is_null()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_final(&self, block_height: u32, block_time: u32) -> bool {
|
pub fn is_final(&self) -> bool {
|
||||||
|
// if lock_time is 0, transaction is final
|
||||||
|
if self.lock_time == 0 {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// setting all sequence numbers to 0xffffffff disables the time lock, so if you want to use locktime,
|
||||||
|
// at least one input must have a sequence number below the maximum.
|
||||||
|
self.inputs.iter().all(TransactionInput::is_final)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_final_in_block(&self, block_height: u32, block_time: u32) -> bool {
|
||||||
if self.lock_time == 0 {
|
if self.lock_time == 0 {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use std::collections::HashSet;
|
||||||
use primitives::hash::H256;
|
use primitives::hash::H256;
|
||||||
use chain::{OutPoint, TransactionOutput, IndexedTransaction};
|
use chain::{OutPoint, TransactionOutput, IndexedTransaction};
|
||||||
use db::{SharedStore, PreviousTransactionOutputProvider};
|
use db::{SharedStore, PreviousTransactionOutputProvider};
|
||||||
|
@ -102,10 +103,6 @@ impl SizePolicy {
|
||||||
self.finish_counter += 1;
|
self.finish_counter += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if fits {
|
|
||||||
self.current_size += size;
|
|
||||||
}
|
|
||||||
|
|
||||||
match (fits, finish) {
|
match (fits, finish) {
|
||||||
(true, true) => NextStep::FinishAndAppend,
|
(true, true) => NextStep::FinishAndAppend,
|
||||||
(true, false) => NextStep::Append,
|
(true, false) => NextStep::Append,
|
||||||
|
@ -113,6 +110,10 @@ impl SizePolicy {
|
||||||
(false, false) => NextStep::Ignore,
|
(false, false) => NextStep::Ignore,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn apply(&mut self, size: u32) {
|
||||||
|
self.current_size += size;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Block assembler
|
/// Block assembler
|
||||||
|
@ -136,25 +137,34 @@ struct FittingTransactionsIterator<'a, T> {
|
||||||
store: &'a PreviousTransactionOutputProvider,
|
store: &'a PreviousTransactionOutputProvider,
|
||||||
/// Memory pool transactions iterator
|
/// Memory pool transactions iterator
|
||||||
iter: T,
|
iter: T,
|
||||||
|
/// New block height
|
||||||
|
block_height: u32,
|
||||||
|
/// New block time
|
||||||
|
block_time: u32,
|
||||||
/// Size policy decides if transactions size fits the block
|
/// Size policy decides if transactions size fits the block
|
||||||
block_size: SizePolicy,
|
block_size: SizePolicy,
|
||||||
/// Sigops policy decides if transactions sigops fits the block
|
/// Sigops policy decides if transactions sigops fits the block
|
||||||
sigops: SizePolicy,
|
sigops: SizePolicy,
|
||||||
/// Previous entries are needed to get previous transaction outputs
|
/// Previous entries are needed to get previous transaction outputs
|
||||||
previous_entries: Vec<&'a Entry>,
|
previous_entries: Vec<&'a Entry>,
|
||||||
|
/// Hashes of ignored entries
|
||||||
|
ignored: HashSet<H256>,
|
||||||
/// True if block is already full
|
/// True if block is already full
|
||||||
finished: bool,
|
finished: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T> FittingTransactionsIterator<'a, T> where T: Iterator<Item = &'a Entry> {
|
impl<'a, T> FittingTransactionsIterator<'a, T> where T: Iterator<Item = &'a Entry> {
|
||||||
fn new(store: &'a PreviousTransactionOutputProvider, iter: T, max_block_size: u32, max_block_sigops: u32) -> Self {
|
fn new(store: &'a PreviousTransactionOutputProvider, iter: T, max_block_size: u32, max_block_sigops: u32, block_height: u32, block_time: u32) -> Self {
|
||||||
FittingTransactionsIterator {
|
FittingTransactionsIterator {
|
||||||
store: store,
|
store: store,
|
||||||
iter: iter,
|
iter: iter,
|
||||||
|
block_height: block_height,
|
||||||
|
block_time: block_time,
|
||||||
// reserve some space for header and transations len field
|
// reserve some space for header and transations len field
|
||||||
block_size: SizePolicy::new(BLOCK_HEADER_SIZE + 4, max_block_size, 1_000, 50),
|
block_size: SizePolicy::new(BLOCK_HEADER_SIZE + 4, max_block_size, 1_000, 50),
|
||||||
sigops: SizePolicy::new(0, max_block_sigops, 8, 50),
|
sigops: SizePolicy::new(0, max_block_sigops, 8, 50),
|
||||||
previous_entries: Vec::new(),
|
previous_entries: Vec::new(),
|
||||||
|
ignored: HashSet::new(),
|
||||||
finished: false,
|
finished: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -192,18 +202,34 @@ impl<'a, T> Iterator for FittingTransactionsIterator<'a, T> where T: Iterator<It
|
||||||
let size_step = self.block_size.decide(transaction_size);
|
let size_step = self.block_size.decide(transaction_size);
|
||||||
let sigops_step = self.sigops.decide(sigops_count);
|
let sigops_step = self.sigops.decide(sigops_count);
|
||||||
|
|
||||||
|
// both next checks could be checked above, but then it will break finishing
|
||||||
|
// check if transaction is still not finalized in this block
|
||||||
|
if !entry.transaction.is_final_in_block(self.block_height, self.block_time) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// check if any parent transaction has been ignored
|
||||||
|
if !self.ignored.is_empty() && entry.transaction.inputs.iter().any(|input| self.ignored.contains(&input.previous_output.hash)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
match size_step.and(sigops_step) {
|
match size_step.and(sigops_step) {
|
||||||
NextStep::Append => {
|
NextStep::Append => {
|
||||||
|
self.block_size.apply(transaction_size);
|
||||||
|
self.sigops.apply(transaction_size);
|
||||||
self.previous_entries.push(entry);
|
self.previous_entries.push(entry);
|
||||||
return Some(entry);
|
return Some(entry);
|
||||||
},
|
},
|
||||||
NextStep::FinishAndAppend => {
|
NextStep::FinishAndAppend => {
|
||||||
self.finished = true;
|
self.finished = true;
|
||||||
|
self.block_size.apply(transaction_size);
|
||||||
|
self.sigops.apply(transaction_size);
|
||||||
self.previous_entries.push(entry);
|
self.previous_entries.push(entry);
|
||||||
return Some(entry);
|
return Some(entry);
|
||||||
},
|
},
|
||||||
NextStep::Ignore => (),
|
NextStep::Ignore => (),
|
||||||
NextStep::FinishAndIgnore => {
|
NextStep::FinishAndIgnore => {
|
||||||
|
self.ignored.insert(entry.hash.clone());
|
||||||
self.finished = true;
|
self.finished = true;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -227,7 +253,7 @@ impl BlockAssembler {
|
||||||
let mut transactions = Vec::new();
|
let mut transactions = Vec::new();
|
||||||
|
|
||||||
let mempool_iter = mempool.iter(OrderingStrategy::ByTransactionScore);
|
let mempool_iter = mempool.iter(OrderingStrategy::ByTransactionScore);
|
||||||
let tx_iter = FittingTransactionsIterator::new(store.as_previous_transaction_output_provider(), mempool_iter, self.max_block_size, self.max_block_sigops);
|
let tx_iter = FittingTransactionsIterator::new(store.as_previous_transaction_output_provider(), mempool_iter, self.max_block_size, self.max_block_sigops, height, time);
|
||||||
for entry in tx_iter {
|
for entry in tx_iter {
|
||||||
// miner_fee is i64, but we can safely cast it to u64
|
// miner_fee is i64, but we can safely cast it to u64
|
||||||
// memory pool should restrict miner fee to be positive
|
// memory pool should restrict miner fee to be positive
|
||||||
|
@ -260,18 +286,18 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_size_policy() {
|
fn test_size_policy() {
|
||||||
let mut size_policy = SizePolicy::new(0, 1000, 200, 3);
|
let mut size_policy = SizePolicy::new(0, 1000, 200, 3);
|
||||||
assert_eq!(size_policy.decide(100), NextStep::Append);
|
assert_eq!(size_policy.decide(100), NextStep::Append); size_policy.apply(100);
|
||||||
assert_eq!(size_policy.decide(500), NextStep::Append);
|
assert_eq!(size_policy.decide(500), NextStep::Append); size_policy.apply(500);
|
||||||
assert_eq!(size_policy.decide(600), NextStep::Ignore);
|
assert_eq!(size_policy.decide(600), NextStep::Ignore);
|
||||||
assert_eq!(size_policy.decide(200), NextStep::Append);
|
assert_eq!(size_policy.decide(200), NextStep::Append); size_policy.apply(200);
|
||||||
assert_eq!(size_policy.decide(300), NextStep::Ignore);
|
assert_eq!(size_policy.decide(300), NextStep::Ignore);
|
||||||
assert_eq!(size_policy.decide(300), NextStep::Ignore);
|
assert_eq!(size_policy.decide(300), NextStep::Ignore);
|
||||||
// this transaction will make counter + buffer > max size
|
// this transaction will make counter + buffer > max size
|
||||||
assert_eq!(size_policy.decide(1), NextStep::Append);
|
assert_eq!(size_policy.decide(1), NextStep::Append); size_policy.apply(1);
|
||||||
// so now only 3 more transactions may accepted / ignored
|
// so now only 3 more transactions may accepted / ignored
|
||||||
assert_eq!(size_policy.decide(1), NextStep::Append);
|
assert_eq!(size_policy.decide(1), NextStep::Append); size_policy.apply(1);
|
||||||
assert_eq!(size_policy.decide(1000), NextStep::Ignore);
|
assert_eq!(size_policy.decide(1000), NextStep::Ignore);
|
||||||
assert_eq!(size_policy.decide(1), NextStep::FinishAndAppend);
|
assert_eq!(size_policy.decide(1), NextStep::FinishAndAppend); size_policy.apply(1);
|
||||||
// we should not call decide again after it returned finish...
|
// we should not call decide again after it returned finish...
|
||||||
// but we can, let's check if result is ok
|
// but we can, let's check if result is ok
|
||||||
assert_eq!(size_policy.decide(1000), NextStep::FinishAndIgnore);
|
assert_eq!(size_policy.decide(1000), NextStep::FinishAndIgnore);
|
||||||
|
@ -294,11 +320,21 @@ mod tests {
|
||||||
let store_ref = IndexedTransactionsRef::new(&store);
|
let store_ref = IndexedTransactionsRef::new(&store);
|
||||||
let entries: Vec<Entry> = Vec::new();
|
let entries: Vec<Entry> = Vec::new();
|
||||||
|
|
||||||
let iter = FittingTransactionsIterator::new(&store_ref, entries.iter(), MAX_BLOCK_SIZE as u32, MAX_BLOCK_SIGOPS as u32);
|
let iter = FittingTransactionsIterator::new(&store_ref, entries.iter(), MAX_BLOCK_SIZE as u32, MAX_BLOCK_SIGOPS as u32, 0, 0);
|
||||||
assert!(iter.collect::<Vec<_>>().is_empty());
|
assert!(iter.collect::<Vec<_>>().is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_fitting_transactions_iterator_max_block_size_reached() {
|
fn test_fitting_transactions_iterator_max_block_size_reached() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fitting_transactions_iterator_ignored_parent() {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fitting_transactions_iterator_locked_transaction() {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,5 +17,6 @@ mod memory_pool;
|
||||||
|
|
||||||
pub use block_assembler::{BlockAssembler, BlockTemplate};
|
pub use block_assembler::{BlockAssembler, BlockTemplate};
|
||||||
pub use cpu_miner::find_solution;
|
pub use cpu_miner::find_solution;
|
||||||
pub use memory_pool::{MemoryPool, Information as MemoryPoolInformation, OrderingStrategy as MemoryPoolOrderingStrategy};
|
pub use memory_pool::{MemoryPool, HashedOutPoint, Information as MemoryPoolInformation,
|
||||||
|
OrderingStrategy as MemoryPoolOrderingStrategy, DoubleSpendCheckResult, NonFinalDoubleSpendSet};
|
||||||
pub use fee::{transaction_fee, transaction_fee_rate};
|
pub use fee::{transaction_fee, transaction_fee_rate};
|
||||||
|
|
|
@ -141,11 +141,33 @@ struct ByPackageScoreOrderedEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
struct HashedOutPoint {
|
pub struct HashedOutPoint {
|
||||||
/// Transasction output point
|
/// Transaction output point
|
||||||
out_point: OutPoint,
|
out_point: OutPoint,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Result of checking double spend with
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum DoubleSpendCheckResult {
|
||||||
|
/// No double spend
|
||||||
|
NoDoubleSpend,
|
||||||
|
/// Input {self.1, self.2} of new transaction is already spent in previous final memory-pool transaction {self.0}
|
||||||
|
DoubleSpend(H256, H256, u32),
|
||||||
|
/// Some inputs of new transaction are already spent by non-final memory-pool transactions
|
||||||
|
NonFinalDoubleSpend(NonFinalDoubleSpendSet),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set of transaction outputs, which can be replaced if newer transaction
|
||||||
|
/// replaces non-final transaction in memory pool
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct NonFinalDoubleSpendSet {
|
||||||
|
/// Double-spend outputs (outputs of newer transaction, which are also spent by nonfinal transactions of mempool)
|
||||||
|
pub double_spends: HashSet<HashedOutPoint>,
|
||||||
|
/// Outputs which also will be removed from memory pool in case of newer transaction insertion
|
||||||
|
/// (i.e. outputs of nonfinal transactions && their descendants)
|
||||||
|
pub dependent_spends: HashSet<HashedOutPoint>,
|
||||||
|
}
|
||||||
|
|
||||||
impl From<OutPoint> for HashedOutPoint {
|
impl From<OutPoint> for HashedOutPoint {
|
||||||
fn from(out_point: OutPoint) -> Self {
|
fn from(out_point: OutPoint) -> Self {
|
||||||
HashedOutPoint {
|
HashedOutPoint {
|
||||||
|
@ -400,6 +422,49 @@ impl Storage {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn check_double_spend(&self, transaction: &Transaction) -> DoubleSpendCheckResult {
|
||||||
|
let mut double_spends: HashSet<HashedOutPoint> = HashSet::new();
|
||||||
|
let mut dependent_spends: HashSet<HashedOutPoint> = HashSet::new();
|
||||||
|
|
||||||
|
for input in &transaction.inputs {
|
||||||
|
// find transaction that spends the same output
|
||||||
|
let prevout: HashedOutPoint = input.previous_output.clone().into();
|
||||||
|
if let Some(entry_hash) = self.by_previous_output.get(&prevout).cloned() {
|
||||||
|
// check if this is final transaction. If so, that's a potential double-spend error
|
||||||
|
let entry = self.by_hash.get(&entry_hash).expect("checked that it exists line above; qed");
|
||||||
|
if entry.transaction.is_final() {
|
||||||
|
return DoubleSpendCheckResult::DoubleSpend(entry_hash, prevout.out_point.hash, prevout.out_point.index);
|
||||||
|
}
|
||||||
|
// else remember this double spend
|
||||||
|
double_spends.insert(prevout.clone());
|
||||||
|
// and 'virtually' remove entry && all descendants from mempool
|
||||||
|
let mut queue: VecDeque<HashedOutPoint> = VecDeque::new();
|
||||||
|
queue.push_back(prevout);
|
||||||
|
while let Some(dependent_prevout) = queue.pop_front() {
|
||||||
|
// if the same output is already spent with another in-pool transaction
|
||||||
|
if let Some(dependent_entry_hash) = self.by_previous_output.get(&dependent_prevout).cloned() {
|
||||||
|
let dependent_entry = self.by_hash.get(&dependent_entry_hash).expect("checked that it exists line above; qed");
|
||||||
|
let dependent_outputs: Vec<_> = dependent_entry.transaction.outputs.iter().enumerate().map(|(idx, _)| OutPoint {
|
||||||
|
hash: dependent_entry_hash.clone(),
|
||||||
|
index: idx as u32,
|
||||||
|
}.into()).collect();
|
||||||
|
dependent_spends.extend(dependent_outputs.clone());
|
||||||
|
queue.extend(dependent_outputs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if double_spends.is_empty() {
|
||||||
|
DoubleSpendCheckResult::NoDoubleSpend
|
||||||
|
} else {
|
||||||
|
DoubleSpendCheckResult::NonFinalDoubleSpend(NonFinalDoubleSpendSet {
|
||||||
|
double_spends: double_spends,
|
||||||
|
dependent_spends: dependent_spends,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn remove_by_prevout(&mut self, prevout: &OutPoint) -> Option<Vec<Transaction>> {
|
pub fn remove_by_prevout(&mut self, prevout: &OutPoint) -> Option<Vec<Transaction>> {
|
||||||
let mut queue: VecDeque<OutPoint> = VecDeque::new();
|
let mut queue: VecDeque<OutPoint> = VecDeque::new();
|
||||||
let mut removed: Vec<Transaction> = Vec::new();
|
let mut removed: Vec<Transaction> = Vec::new();
|
||||||
|
@ -407,7 +472,7 @@ impl Storage {
|
||||||
|
|
||||||
while let Some(prevout) = queue.pop_front() {
|
while let Some(prevout) = queue.pop_front() {
|
||||||
if let Some(entry_hash) = self.by_previous_output.get(&prevout.clone().into()).cloned() {
|
if let Some(entry_hash) = self.by_previous_output.get(&prevout.clone().into()).cloned() {
|
||||||
let entry = self.remove_by_hash(&entry_hash).expect("checket that it exists line above; qed");
|
let entry = self.remove_by_hash(&entry_hash).expect("checked that it exists line above; qed");
|
||||||
queue.extend(entry.transaction.outputs.iter().enumerate().map(|(idx, _)| OutPoint {
|
queue.extend(entry.transaction.outputs.iter().enumerate().map(|(idx, _)| OutPoint {
|
||||||
hash: entry_hash.clone(),
|
hash: entry_hash.clone(),
|
||||||
index: idx as u32,
|
index: idx as u32,
|
||||||
|
@ -604,6 +669,11 @@ impl MemoryPool {
|
||||||
self.storage.remove_by_hash(h).map(|entry| entry.transaction)
|
self.storage.remove_by_hash(h).map(|entry| entry.transaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks if `transaction` spends some outputs, already spent by inpool transactions.
|
||||||
|
pub fn check_double_spend(&self, transaction: &Transaction) -> DoubleSpendCheckResult {
|
||||||
|
self.storage.check_double_spend(transaction)
|
||||||
|
}
|
||||||
|
|
||||||
/// Removes transaction (and all its descendants) which has spent given output
|
/// Removes transaction (and all its descendants) which has spent given output
|
||||||
pub fn remove_by_prevout(&mut self, prevout: &OutPoint) -> Option<Vec<Transaction>> {
|
pub fn remove_by_prevout(&mut self, prevout: &OutPoint) -> Option<Vec<Transaction>> {
|
||||||
self.storage.remove_by_prevout(prevout)
|
self.storage.remove_by_prevout(prevout)
|
||||||
|
@ -795,7 +865,7 @@ impl<'a> Iterator for MemoryPoolIterator<'a> {
|
||||||
mod tests {
|
mod tests {
|
||||||
use chain::{Transaction, OutPoint};
|
use chain::{Transaction, OutPoint};
|
||||||
use heapsize::HeapSizeOf;
|
use heapsize::HeapSizeOf;
|
||||||
use super::{MemoryPool, OrderingStrategy};
|
use super::{MemoryPool, OrderingStrategy, DoubleSpendCheckResult};
|
||||||
use test_data::{ChainBuilder, TransactionBuilder};
|
use test_data::{ChainBuilder, TransactionBuilder};
|
||||||
|
|
||||||
fn to_memory_pool(chain: &mut ChainBuilder) -> MemoryPool {
|
fn to_memory_pool(chain: &mut ChainBuilder) -> MemoryPool {
|
||||||
|
@ -1222,4 +1292,88 @@ mod tests {
|
||||||
assert_eq!(pool.remove_by_prevout(&OutPoint { hash: chain.hash(0), index: 0 }), Some(vec![chain.at(1), chain.at(2)]));
|
assert_eq!(pool.remove_by_prevout(&OutPoint { hash: chain.hash(0), index: 0 }), Some(vec![chain.at(1), chain.at(2)]));
|
||||||
assert_eq!(pool.information().transactions_count, 2);
|
assert_eq!(pool.information().transactions_count, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_memory_pool_check_double_spend() {
|
||||||
|
let chain = &mut ChainBuilder::new();
|
||||||
|
|
||||||
|
TransactionBuilder::with_output(10).add_output(10).add_output(10).store(chain) // t0
|
||||||
|
.reset().set_input(&chain.at(0), 0).add_output(20).lock().store(chain) // nonfinal: t0[0] -> t1
|
||||||
|
.reset().set_input(&chain.at(1), 0).add_output(30).store(chain) // dependent: t0[0] -> t1[0] -> t2
|
||||||
|
.reset().set_input(&chain.at(0), 0).add_output(40).store(chain) // good replacement: t0[0] -> t3
|
||||||
|
.reset().set_input(&chain.at(0), 1).add_output(50).store(chain) // final: t0[1] -> t4
|
||||||
|
.reset().set_input(&chain.at(0), 1).add_output(60).store(chain) // bad replacement: t0[1] -> t5
|
||||||
|
.reset().set_input(&chain.at(0), 2).add_output(70).store(chain); // no double spend: t0[2] -> t6
|
||||||
|
|
||||||
|
let mut pool = MemoryPool::new();
|
||||||
|
pool.insert_verified(chain.at(1));
|
||||||
|
pool.insert_verified(chain.at(2));
|
||||||
|
pool.insert_verified(chain.at(4));
|
||||||
|
// when output is spent by nonfinal transaction
|
||||||
|
match pool.check_double_spend(&chain.at(3)) {
|
||||||
|
DoubleSpendCheckResult::NonFinalDoubleSpend(set) => {
|
||||||
|
assert_eq!(set.double_spends.len(), 1);
|
||||||
|
assert!(set.double_spends.contains(&chain.at(1).inputs[0].previous_output.clone().into()));
|
||||||
|
assert_eq!(set.dependent_spends.len(), 2);
|
||||||
|
assert!(set.dependent_spends.contains(&OutPoint {
|
||||||
|
hash: chain.at(1).hash(),
|
||||||
|
index: 0,
|
||||||
|
}.into()));
|
||||||
|
assert!(set.dependent_spends.contains(&OutPoint {
|
||||||
|
hash: chain.at(2).hash(),
|
||||||
|
index: 0,
|
||||||
|
}.into()));
|
||||||
|
},
|
||||||
|
_ => panic!("unexpected"),
|
||||||
|
}
|
||||||
|
// when output is spent by final transaction
|
||||||
|
match pool.check_double_spend(&chain.at(5)) {
|
||||||
|
DoubleSpendCheckResult::DoubleSpend(inpool_hash, prev_hash, prev_index) => {
|
||||||
|
assert_eq!(inpool_hash, chain.at(4).hash());
|
||||||
|
assert_eq!(prev_hash, chain.at(0).hash());
|
||||||
|
assert_eq!(prev_index, 1);
|
||||||
|
},
|
||||||
|
_ => panic!("unexpected"),
|
||||||
|
}
|
||||||
|
// when output is not spent at all
|
||||||
|
match pool.check_double_spend(&chain.at(6)) {
|
||||||
|
DoubleSpendCheckResult::NoDoubleSpend => (),
|
||||||
|
_ => panic!("unexpected"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_memory_pool_check_double_spend_multiple_dependent_outputs() {
|
||||||
|
let chain = &mut ChainBuilder::new();
|
||||||
|
|
||||||
|
TransactionBuilder::with_output(100).store(chain) // t0
|
||||||
|
.reset().set_input(&chain.at(0), 0).add_output(20).add_output(30).add_output(50).lock().store(chain) // nonfinal: t0[0] -> t1
|
||||||
|
.reset().set_input(&chain.at(0), 0).add_output(40).store(chain); // good replacement: t0[0] -> t2
|
||||||
|
|
||||||
|
let mut pool = MemoryPool::new();
|
||||||
|
pool.insert_verified(chain.at(1));
|
||||||
|
|
||||||
|
// when output is spent by nonfinal transaction
|
||||||
|
match pool.check_double_spend(&chain.at(2)) {
|
||||||
|
DoubleSpendCheckResult::NonFinalDoubleSpend(set) => {
|
||||||
|
assert_eq!(set.double_spends.len(), 1);
|
||||||
|
assert!(set.double_spends.contains(&chain.at(1).inputs[0].previous_output.clone().into()));
|
||||||
|
assert_eq!(set.dependent_spends.len(), 3);
|
||||||
|
assert!(set.dependent_spends.contains(&OutPoint {
|
||||||
|
hash: chain.at(1).hash(),
|
||||||
|
index: 0,
|
||||||
|
}.into()));
|
||||||
|
assert!(set.dependent_spends.contains(&OutPoint {
|
||||||
|
hash: chain.at(1).hash(),
|
||||||
|
index: 1,
|
||||||
|
}.into()));
|
||||||
|
assert!(set.dependent_spends.contains(&OutPoint {
|
||||||
|
hash: chain.at(1).hash(),
|
||||||
|
index: 2,
|
||||||
|
}.into()));
|
||||||
|
},
|
||||||
|
_ => panic!("unexpected"),
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -634,12 +634,12 @@ mod tests {
|
||||||
let transaction_hash = transaction.hash();
|
let transaction_hash = transaction.hash();
|
||||||
|
|
||||||
let result = local_node.accept_transaction(transaction);
|
let result = local_node.accept_transaction(transaction);
|
||||||
assert_eq!(result, Ok(transaction_hash));
|
assert_eq!(result, Ok(transaction_hash.clone()));
|
||||||
|
|
||||||
assert_eq!(executor.lock().take_tasks(), vec![Task::SendInventory(peer_index1,
|
assert_eq!(executor.lock().take_tasks(), vec![Task::SendInventory(peer_index1,
|
||||||
vec![InventoryVector {
|
vec![InventoryVector {
|
||||||
inv_type: InventoryType::MessageTx,
|
inv_type: InventoryType::MessageTx,
|
||||||
hash: "0791efccd035c5fe501023ff888106eba5eff533965de4a6e06400f623bcac34".into(),
|
hash: transaction_hash,
|
||||||
}]
|
}]
|
||||||
)]
|
)]
|
||||||
);
|
);
|
||||||
|
|
|
@ -677,6 +677,13 @@ impl Chain {
|
||||||
|
|
||||||
/// Insert transaction to memory pool
|
/// Insert transaction to memory pool
|
||||||
pub fn insert_verified_transaction(&mut self, transaction: Transaction) {
|
pub fn insert_verified_transaction(&mut self, transaction: Transaction) {
|
||||||
|
// we have verified transaction, but possibly this transaction replaces
|
||||||
|
// existing transaction from memory pool
|
||||||
|
// => remove previous transactions before
|
||||||
|
for input in &transaction.inputs {
|
||||||
|
self.memory_pool.remove_by_prevout(&input.previous_output);
|
||||||
|
}
|
||||||
|
// now insert transaction itself
|
||||||
self.memory_pool.insert_verified(transaction);
|
self.memory_pool.insert_verified(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1269,4 +1276,20 @@ mod tests {
|
||||||
headers[4].clone(),
|
headers[4].clone(),
|
||||||
]), HeadersIntersection::DeadEnd(0));
|
]), HeadersIntersection::DeadEnd(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn update_memory_pool_transaction() {
|
||||||
|
use test_data::{ChainBuilder, TransactionBuilder};
|
||||||
|
|
||||||
|
let data_chain = &mut ChainBuilder::new();
|
||||||
|
TransactionBuilder::with_output(10).add_output(10).add_output(10).store(data_chain) // transaction0
|
||||||
|
.reset().set_input(&data_chain.at(0), 0).add_output(20).lock().store(data_chain) // transaction0 -> transaction1
|
||||||
|
.reset().set_input(&data_chain.at(0), 0).add_output(30).store(data_chain); // transaction0 -> transaction2
|
||||||
|
|
||||||
|
let mut chain = Chain::new(Arc::new(db::TestStorage::with_genesis_block()));
|
||||||
|
chain.insert_verified_transaction(data_chain.at(1));
|
||||||
|
assert_eq!(chain.information().transactions.transactions_count, 1);
|
||||||
|
chain.insert_verified_transaction(data_chain.at(2));
|
||||||
|
assert_eq!(chain.information().transactions.transactions_count, 1); // tx was replaces
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2935,4 +2935,9 @@ pub mod tests {
|
||||||
// should not panic
|
// should not panic
|
||||||
sync.on_peer_transaction(1, test_data::TransactionBuilder::with_default_input(0).into());
|
sync.on_peer_transaction(1, test_data::TransactionBuilder::with_default_input(0).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn when_transaction_replaces_locked_transaction() {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,10 @@ use std::sync::Arc;
|
||||||
use std::sync::mpsc::{channel, Sender, Receiver};
|
use std::sync::mpsc::{channel, Sender, Receiver};
|
||||||
use chain::{Transaction, OutPoint, TransactionOutput, IndexedBlock};
|
use chain::{Transaction, OutPoint, TransactionOutput, IndexedBlock};
|
||||||
use network::Magic;
|
use network::Magic;
|
||||||
|
use miner::{DoubleSpendCheckResult, NonFinalDoubleSpendSet};
|
||||||
use primitives::hash::H256;
|
use primitives::hash::H256;
|
||||||
use synchronization_chain::ChainRef;
|
use synchronization_chain::ChainRef;
|
||||||
use verification::{BackwardsCompatibleChainVerifier as ChainVerifier, Verify as VerificationVerify, Chain};
|
use verification::{self, BackwardsCompatibleChainVerifier as ChainVerifier, Verify as VerificationVerify, Chain};
|
||||||
use db::{SharedStore, PreviousTransactionOutputProvider, TransactionOutputObserver};
|
use db::{SharedStore, PreviousTransactionOutputProvider, TransactionOutputObserver};
|
||||||
use time::get_time;
|
use time::get_time;
|
||||||
|
|
||||||
|
@ -57,12 +58,23 @@ pub struct AsyncVerifier {
|
||||||
verification_worker_thread: Option<thread::JoinHandle<()>>,
|
verification_worker_thread: Option<thread::JoinHandle<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Transaction output observer, which looks into storage && into memory pool
|
||||||
struct ChainMemoryPoolTransactionOutputProvider {
|
struct ChainMemoryPoolTransactionOutputProvider {
|
||||||
|
/// Chain reference
|
||||||
chain: ChainRef,
|
chain: ChainRef,
|
||||||
|
/// Previous outputs, for which we should return 'Not spent' value.
|
||||||
|
/// These are used when new version of transaction is received.
|
||||||
|
nonfinal_spends: Option<NonFinalDoubleSpendSet>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
impl VerificationTask {
|
||||||
struct EmptyTransactionOutputProvider {
|
/// Returns transaction reference if it is transaction verification task
|
||||||
|
pub fn transaction(&self) -> Option<&Transaction> {
|
||||||
|
match self {
|
||||||
|
&VerificationTask::VerifyTransaction(_, ref transaction) => Some(&transaction),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsyncVerifier {
|
impl AsyncVerifier {
|
||||||
|
@ -86,8 +98,18 @@ impl AsyncVerifier {
|
||||||
match task {
|
match task {
|
||||||
VerificationTask::Stop => break,
|
VerificationTask::Stop => break,
|
||||||
_ => {
|
_ => {
|
||||||
let prevout_provider = ChainMemoryPoolTransactionOutputProvider::with_chain(chain.clone());
|
let prevout_provider = if let Some(ref transaction) = task.transaction() {
|
||||||
execute_verification_task(&sink, &prevout_provider, &verifier, task)
|
match ChainMemoryPoolTransactionOutputProvider::for_transaction(chain.clone(), transaction) {
|
||||||
|
Err(e) => {
|
||||||
|
sink.on_transaction_verification_error(&format!("{:?}", e), &transaction.hash());
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
Ok(prevout_provider) => Some(prevout_provider),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
execute_verification_task(&sink, prevout_provider.as_ref(), &verifier, task)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -143,17 +165,17 @@ impl<T> SyncVerifier<T> where T: VerificationSink {
|
||||||
impl<T> Verifier for SyncVerifier<T> where T: VerificationSink {
|
impl<T> Verifier for SyncVerifier<T> where T: VerificationSink {
|
||||||
/// Verify block
|
/// Verify block
|
||||||
fn verify_block(&self, block: IndexedBlock) {
|
fn verify_block(&self, block: IndexedBlock) {
|
||||||
execute_verification_task(&self.sink, &EmptyTransactionOutputProvider::default(), &self.verifier, VerificationTask::VerifyBlock(block))
|
execute_verification_task::<T, ChainMemoryPoolTransactionOutputProvider>(&self.sink, None, &self.verifier, VerificationTask::VerifyBlock(block))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify transaction
|
/// Verify transaction
|
||||||
fn verify_transaction(&self, height: u32, transaction: Transaction) {
|
fn verify_transaction(&self, _height: u32, _transaction: Transaction) {
|
||||||
execute_verification_task(&self.sink, &EmptyTransactionOutputProvider::default(), &self.verifier, VerificationTask::VerifyTransaction(height, transaction))
|
unimplemented!() // sync verifier is currently only used for blocks verification
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Execute single verification task
|
/// Execute single verification task
|
||||||
fn execute_verification_task<T: VerificationSink, U: PreviousTransactionOutputProvider + TransactionOutputObserver>(sink: &Arc<T>, tx_output_provider: &U, verifier: &ChainVerifier, task: VerificationTask) {
|
fn execute_verification_task<T: VerificationSink, U: TransactionOutputObserver + PreviousTransactionOutputProvider>(sink: &Arc<T>, tx_output_provider: Option<&U>, verifier: &ChainVerifier, task: VerificationTask) {
|
||||||
let mut tasks_queue: VecDeque<VerificationTask> = VecDeque::new();
|
let mut tasks_queue: VecDeque<VerificationTask> = VecDeque::new();
|
||||||
tasks_queue.push_back(task);
|
tasks_queue.push_back(task);
|
||||||
|
|
||||||
|
@ -178,6 +200,7 @@ fn execute_verification_task<T: VerificationSink, U: PreviousTransactionOutputPr
|
||||||
},
|
},
|
||||||
VerificationTask::VerifyTransaction(height, transaction) => {
|
VerificationTask::VerifyTransaction(height, transaction) => {
|
||||||
let time: u32 = get_time().sec as u32;
|
let time: u32 = get_time().sec as u32;
|
||||||
|
let tx_output_provider = tx_output_provider.expect("must be provided for transaction checks");
|
||||||
match verifier.verify_mempool_transaction(tx_output_provider, height, time, &transaction) {
|
match verifier.verify_mempool_transaction(tx_output_provider, height, time, &transaction) {
|
||||||
Ok(_) => sink.on_transaction_verification_success(transaction),
|
Ok(_) => sink.on_transaction_verification_success(transaction),
|
||||||
Err(e) => sink.on_transaction_verification_error(&format!("{:?}", e), &transaction.hash()),
|
Err(e) => sink.on_transaction_verification_error(&format!("{:?}", e), &transaction.hash()),
|
||||||
|
@ -189,40 +212,59 @@ fn execute_verification_task<T: VerificationSink, U: PreviousTransactionOutputPr
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChainMemoryPoolTransactionOutputProvider {
|
impl ChainMemoryPoolTransactionOutputProvider {
|
||||||
pub fn with_chain(chain: ChainRef) -> Self {
|
pub fn for_transaction(chain: ChainRef, transaction: &Transaction) -> Result<Self, verification::TransactionError> {
|
||||||
ChainMemoryPoolTransactionOutputProvider {
|
// we have to check if there are another in-mempool transactions which spent same outputs here
|
||||||
chain: chain,
|
let check_result = chain.read().memory_pool().check_double_spend(transaction);
|
||||||
}
|
ChainMemoryPoolTransactionOutputProvider::for_double_spend_check_result(chain, check_result)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PreviousTransactionOutputProvider for ChainMemoryPoolTransactionOutputProvider {
|
pub fn for_double_spend_check_result(chain: ChainRef, check_result: DoubleSpendCheckResult) -> Result<Self, verification::TransactionError> {
|
||||||
fn previous_transaction_output(&self, prevout: &OutPoint) -> Option<TransactionOutput> {
|
match check_result {
|
||||||
let chain = self.chain.read();
|
DoubleSpendCheckResult::DoubleSpend(_, hash, index) => Err(verification::TransactionError::UsingSpentOutput(hash, index)),
|
||||||
chain.memory_pool().previous_transaction_output(prevout)
|
DoubleSpendCheckResult::NoDoubleSpend => Ok(ChainMemoryPoolTransactionOutputProvider {
|
||||||
.or_else(|| chain.storage().as_previous_transaction_output_provider().previous_transaction_output(prevout))
|
chain: chain.clone(),
|
||||||
|
nonfinal_spends: None,
|
||||||
|
}),
|
||||||
|
DoubleSpendCheckResult::NonFinalDoubleSpend(nonfinal_spends) => Ok(ChainMemoryPoolTransactionOutputProvider {
|
||||||
|
chain: chain.clone(),
|
||||||
|
nonfinal_spends: Some(nonfinal_spends),
|
||||||
|
}),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TransactionOutputObserver for ChainMemoryPoolTransactionOutputProvider {
|
impl TransactionOutputObserver for ChainMemoryPoolTransactionOutputProvider {
|
||||||
fn is_spent(&self, prevout: &OutPoint) -> Option<bool> {
|
fn is_spent(&self, prevout: &OutPoint) -> Option<bool> {
|
||||||
|
// check if this output is 'locked' by mempool transaction
|
||||||
|
if let Some(ref nonfinal_spends) = self.nonfinal_spends {
|
||||||
|
if nonfinal_spends.double_spends.contains(&prevout.clone().into()) {
|
||||||
|
return Some(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we can omit memory_pool check here, because it has been completed in `for_transaction` method
|
||||||
|
// => just check spending in storage
|
||||||
|
self.chain.read().storage()
|
||||||
|
.transaction_meta(&prevout.hash)
|
||||||
|
.and_then(|tm| tm.is_spent(prevout.index as usize))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PreviousTransactionOutputProvider for ChainMemoryPoolTransactionOutputProvider {
|
||||||
|
fn previous_transaction_output(&self, prevout: &OutPoint) -> Option<TransactionOutput> {
|
||||||
|
// check if that is output of some transaction, which is vitually removed from memory pool
|
||||||
|
if let Some(ref nonfinal_spends) = self.nonfinal_spends {
|
||||||
|
if nonfinal_spends.dependent_spends.contains(&prevout.clone().into()) {
|
||||||
|
// transaction is trying to replace some nonfinal transaction
|
||||||
|
// + it is also depends on this transaction
|
||||||
|
// => this is definitely an error
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let chain = self.chain.read();
|
let chain = self.chain.read();
|
||||||
if chain.memory_pool().is_spent(prevout) {
|
chain.memory_pool().previous_transaction_output(prevout)
|
||||||
return Some(true);
|
.or_else(|| chain.storage().as_previous_transaction_output_provider().previous_transaction_output(prevout))
|
||||||
}
|
|
||||||
chain.storage().transaction_meta(&prevout.hash).and_then(|tm| tm.is_spent(prevout.index as usize))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PreviousTransactionOutputProvider for EmptyTransactionOutputProvider {
|
|
||||||
fn previous_transaction_output(&self, _prevout: &OutPoint) -> Option<TransactionOutput> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TransactionOutputObserver for EmptyTransactionOutputProvider {
|
|
||||||
fn is_spent(&self, _prevout: &OutPoint) -> Option<bool> {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,15 +272,16 @@ impl TransactionOutputObserver for EmptyTransactionOutputProvider {
|
||||||
pub mod tests {
|
pub mod tests {
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use parking_lot::RwLock;
|
use chain::{Transaction, OutPoint};
|
||||||
use chain::{Transaction, IndexedBlock};
|
|
||||||
use synchronization_chain::{Chain, ChainRef};
|
use synchronization_chain::{Chain, ChainRef};
|
||||||
use synchronization_client::CoreVerificationSink;
|
use synchronization_client::CoreVerificationSink;
|
||||||
use synchronization_executor::tests::DummyTaskExecutor;
|
use synchronization_executor::tests::DummyTaskExecutor;
|
||||||
use primitives::hash::H256;
|
use primitives::hash::H256;
|
||||||
|
use chain::IndexedBlock;
|
||||||
use super::{Verifier, BlockVerificationSink, TransactionVerificationSink, ChainMemoryPoolTransactionOutputProvider};
|
use super::{Verifier, BlockVerificationSink, TransactionVerificationSink, ChainMemoryPoolTransactionOutputProvider};
|
||||||
use db;
|
use db::{self, TransactionOutputObserver, PreviousTransactionOutputProvider};
|
||||||
use test_data;
|
use test_data;
|
||||||
|
use parking_lot::RwLock;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct DummyVerifier {
|
pub struct DummyVerifier {
|
||||||
|
@ -283,16 +326,46 @@ pub mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn when_transaction_spends_output_twice() {
|
fn when_transaction_spends_output_twice() {
|
||||||
use db::TransactionOutputObserver;
|
|
||||||
let tx1: Transaction = test_data::TransactionBuilder::with_default_input(0).into();
|
let tx1: Transaction = test_data::TransactionBuilder::with_default_input(0).into();
|
||||||
let tx2: Transaction = test_data::TransactionBuilder::with_default_input(1).into();
|
let tx2: Transaction = test_data::TransactionBuilder::with_default_input(1).into();
|
||||||
let out1 = tx1.inputs[0].previous_output.clone();
|
let out1 = tx1.inputs[0].previous_output.clone();
|
||||||
let out2 = tx2.inputs[0].previous_output.clone();
|
let out2 = tx2.inputs[0].previous_output.clone();
|
||||||
let mut chain = Chain::new(Arc::new(db::TestStorage::with_genesis_block()));
|
let mut chain = Chain::new(Arc::new(db::TestStorage::with_genesis_block()));
|
||||||
chain.memory_pool_mut().insert_verified(tx1);
|
chain.memory_pool_mut().insert_verified(tx1);
|
||||||
|
assert!(chain.memory_pool().is_spent(&out1));
|
||||||
|
assert!(!chain.memory_pool().is_spent(&out2));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn when_transaction_depends_on_removed_nonfinal_transaction() {
|
||||||
|
let dchain = &mut test_data::ChainBuilder::new();
|
||||||
|
|
||||||
|
test_data::TransactionBuilder::with_output(10).store(dchain) // t0
|
||||||
|
.reset().set_input(&dchain.at(0), 0).add_output(20).lock().store(dchain) // nonfinal: t0[0] -> t1
|
||||||
|
.reset().set_input(&dchain.at(1), 0).add_output(30).store(dchain) // dependent: t0[0] -> t1[0] -> t2
|
||||||
|
.reset().set_input(&dchain.at(0), 0).add_output(40).store(dchain); // good replacement: t0[0] -> t3
|
||||||
|
|
||||||
|
let mut chain = Chain::new(Arc::new(db::TestStorage::with_genesis_block()));
|
||||||
|
chain.memory_pool_mut().insert_verified(dchain.at(0));
|
||||||
|
chain.memory_pool_mut().insert_verified(dchain.at(1));
|
||||||
|
chain.memory_pool_mut().insert_verified(dchain.at(2));
|
||||||
|
|
||||||
|
// when inserting t3:
|
||||||
|
// check that is_spent(t0[0]) == Some(false) (as it is spent by nonfinal t1)
|
||||||
|
// check that is_spent(t1[0]) == None (as t1 is virtually removed)
|
||||||
|
// check that is_spent(t2[0]) == None (as t2 is virtually removed)
|
||||||
|
// check that previous_transaction_output(t0[0]) = Some(_)
|
||||||
|
// check that previous_transaction_output(t1[0]) = None (as t1 is virtually removed)
|
||||||
|
// check that previous_transaction_output(t2[0]) = None (as t2 is virtually removed)
|
||||||
|
// =>
|
||||||
|
// if t3 is also depending on t1[0] || t2[0], it will be rejected by verification as missing inputs
|
||||||
let chain = ChainRef::new(RwLock::new(chain));
|
let chain = ChainRef::new(RwLock::new(chain));
|
||||||
let provider = ChainMemoryPoolTransactionOutputProvider::with_chain(chain);
|
let provider = ChainMemoryPoolTransactionOutputProvider::for_transaction(chain, &dchain.at(3)).unwrap();
|
||||||
assert!(provider.is_spent(&out1).unwrap_or_default());
|
assert_eq!(provider.is_spent(&OutPoint { hash: dchain.at(0).hash(), index: 0, }), Some(false));
|
||||||
assert!(!provider.is_spent(&out2).unwrap_or_default());
|
assert_eq!(provider.is_spent(&OutPoint { hash: dchain.at(1).hash(), index: 0, }), None);
|
||||||
|
assert_eq!(provider.is_spent(&OutPoint { hash: dchain.at(2).hash(), index: 0, }), None);
|
||||||
|
assert_eq!(provider.previous_transaction_output(&OutPoint { hash: dchain.at(0).hash(), index: 0, }), Some(dchain.at(0).outputs[0].clone()));
|
||||||
|
assert_eq!(provider.previous_transaction_output(&OutPoint { hash: dchain.at(1).hash(), index: 0, }), None);
|
||||||
|
assert_eq!(provider.previous_transaction_output(&OutPoint { hash: dchain.at(2).hash(), index: 0, }), None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,7 +97,7 @@ impl TransactionBuilder {
|
||||||
index: output_index,
|
index: output_index,
|
||||||
},
|
},
|
||||||
script_sig: Bytes::new_with_len(0),
|
script_sig: Bytes::new_with_len(0),
|
||||||
sequence: 0,
|
sequence: 0xffffffff,
|
||||||
});
|
});
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -113,11 +113,17 @@ impl TransactionBuilder {
|
||||||
index: output_index,
|
index: output_index,
|
||||||
},
|
},
|
||||||
script_sig: Bytes::new_with_len(0),
|
script_sig: Bytes::new_with_len(0),
|
||||||
sequence: 0,
|
sequence: 0xffffffff,
|
||||||
}];
|
}];
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn lock(mut self) -> Self {
|
||||||
|
self.transaction.inputs[0].sequence = 0;
|
||||||
|
self.transaction.lock_time = 500000;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn store(self, chain: &mut ChainBuilder) -> Self {
|
pub fn store(self, chain: &mut ChainBuilder) -> Self {
|
||||||
chain.transactions.push(self.transaction.clone());
|
chain.transactions.push(self.transaction.clone());
|
||||||
self
|
self
|
||||||
|
|
Loading…
Reference in New Issue