[WIP] txpool (tx validation) using block sums for full validation (#1567)

tx validation (txpool) rework/simplify
This commit is contained in:
Antioch Peverell
2018-09-24 09:24:10 +01:00
committed by GitHub
parent 82b785282c
commit e72d8b58e4
22 changed files with 521 additions and 712 deletions
+116 -60
View File
@@ -23,7 +23,7 @@ use core::core::hash::{Hash, Hashed};
use core::core::id::{ShortId, ShortIdentifiable};
use core::core::transaction;
use core::core::verifier_cache::VerifierCache;
use core::core::{Block, Transaction, TxKernel};
use core::core::{Block, BlockHeader, BlockSums, Committed, Transaction, TxKernel};
use types::{BlockChain, PoolEntry, PoolEntryState, PoolError};
use util::LOGGER;
@@ -111,9 +111,8 @@ impl Pool {
/// appropriate to put in a mined block. Aggregates chains of dependent
/// transactions, orders by fee over weight and ensures to total weight
/// doesn't exceed block limits.
pub fn prepare_mineable_transactions(&self) -> Vec<Transaction> {
let header = self.blockchain.chain_head().unwrap();
pub fn prepare_mineable_transactions(&self) -> Result<Vec<Transaction>, PoolError> {
let header = self.blockchain.chain_head()?;
let tx_buckets = self.bucket_transactions();
// flatten buckets using aggregate (with cut-through)
@@ -121,8 +120,9 @@ impl Pool {
.into_iter()
.filter_map(|mut bucket| {
bucket.truncate(MAX_TX_CHAIN);
transaction::aggregate(bucket, self.verifier_cache.clone()).ok()
}).collect();
transaction::aggregate(bucket).ok()
}).filter(|x| x.validate(self.verifier_cache.clone()).is_ok())
.collect();
// sort by fees over weight, multiplying by 1000 to keep some precision
// don't think we'll ever see a >max_u64/1000 fee transaction
@@ -135,11 +135,12 @@ impl Pool {
weight < MAX_MINEABLE_WEIGHT
});
// make sure those txs are all valid together, no Error is expected
// when passing None
self.blockchain
.validate_raw_txs(flat_txs, None, &header.hash())
.expect("should never happen")
// Iteratively apply the txs to the current chain state,
// rejecting any that do not result in a valid state.
// Return a vec of all the valid txs.
let block_sums = self.blockchain.get_block_sums(&header.hash())?;
let txs = self.validate_raw_txs(flat_txs, None, &header, &block_sums)?;
Ok(txs)
}
pub fn all_transactions(&self) -> Vec<Transaction> {
@@ -152,39 +153,37 @@ impl Pool {
return Ok(None);
}
let tx = transaction::aggregate(txs, self.verifier_cache.clone())?;
let tx = transaction::aggregate(txs)?;
tx.validate(self.verifier_cache.clone())?;
Ok(Some(tx))
}
pub fn select_valid_transactions(
&mut self,
from_state: PoolEntryState,
to_state: PoolEntryState,
&self,
txs: Vec<Transaction>,
extra_tx: Option<Transaction>,
block_hash: &Hash,
header: &BlockHeader,
) -> Result<Vec<Transaction>, PoolError> {
let entries = &mut self
.entries
.iter_mut()
.filter(|x| x.state == from_state)
.collect::<Vec<_>>();
let block_sums = self.blockchain.get_block_sums(&header.hash())?;
let valid_txs = self.validate_raw_txs(txs, extra_tx, header, &block_sums)?;
Ok(valid_txs)
}
let candidate_txs: Vec<Transaction> = entries.iter().map(|x| x.tx.clone()).collect();
if candidate_txs.is_empty() {
return Ok(vec![]);
}
let valid_txs = self
.blockchain
.validate_raw_txs(candidate_txs, extra_tx, block_hash)?;
pub fn get_transactions_in_state(&self, state: PoolEntryState) -> Vec<Transaction> {
self.entries
.iter()
.filter(|x| x.state == state)
.map(|x| x.tx.clone())
.collect::<Vec<_>>()
}
// Update state on all entries included in final vec of valid txs.
for x in &mut entries.iter_mut() {
if valid_txs.contains(&x.tx) {
x.state = to_state.clone();
// Transition the specified pool entries to the new state.
pub fn transition_to_state(&mut self, txs: &Vec<Transaction>, state: PoolEntryState) {
for x in self.entries.iter_mut() {
if txs.contains(&x.tx) {
x.state = state.clone();
}
}
Ok(valid_txs)
}
// Aggregate this new tx with all existing txs in the pool.
@@ -194,7 +193,7 @@ impl Pool {
&mut self,
entry: PoolEntry,
extra_txs: Vec<Transaction>,
block_hash: &Hash,
header: &BlockHeader,
) -> Result<(), PoolError> {
debug!(
LOGGER,
@@ -205,7 +204,7 @@ impl Pool {
entry.tx.inputs().len(),
entry.tx.outputs().len(),
entry.tx.kernels().len(),
block_hash,
header.hash(),
);
// Combine all the txs from the pool with any extra txs provided.
@@ -225,13 +224,14 @@ impl Pool {
// Create a single aggregated tx from the existing pool txs and the
// new entry
txs.push(entry.tx.clone());
transaction::aggregate(txs, self.verifier_cache.clone())?
let tx = transaction::aggregate(txs)?;
tx.validate(self.verifier_cache.clone())?;
tx
};
// Validate aggregated tx against a known chain state (via txhashset
// extension).
self.blockchain
.validate_raw_txs(vec![], Some(agg_tx), block_hash)?;
// Validate aggregated tx against a known chain state.
self.validate_raw_tx(&agg_tx, header)?;
// If we get here successfully then we can safely add the entry to the pool.
self.entries.push(entry);
@@ -239,32 +239,88 @@ impl Pool {
Ok(())
}
fn validate_raw_tx(
&self,
tx: &Transaction,
header: &BlockHeader,
) -> Result<BlockSums, PoolError> {
let block_sums = self.blockchain.get_block_sums(&header.hash())?;
let new_sums = self.apply_txs_to_block_sums(&block_sums, vec![tx.clone()], header)?;
Ok(new_sums)
}
fn validate_raw_txs(
&self,
txs: Vec<Transaction>,
extra_tx: Option<Transaction>,
header: &BlockHeader,
block_sums: &BlockSums,
) -> Result<Vec<Transaction>, PoolError> {
let mut valid_txs = vec![];
for tx in txs {
let mut candidate_txs = vec![];
if let Some(extra_tx) = extra_tx.clone() {
candidate_txs.push(extra_tx);
};
candidate_txs.extend(valid_txs.clone());
candidate_txs.push(tx.clone());
if self
.apply_txs_to_block_sums(&block_sums, candidate_txs, header)
.is_ok()
{
valid_txs.push(tx);
}
}
Ok(valid_txs)
}
fn apply_txs_to_block_sums(
&self,
block_sums: &BlockSums,
txs: Vec<Transaction>,
header: &BlockHeader,
) -> Result<BlockSums, PoolError> {
// Build a single aggregate tx and validate it.
let tx = transaction::aggregate(txs)?;
tx.validate(self.verifier_cache.clone())?;
// Validate the tx against current chain state.
// Check all inputs are in the current UTXO set.
// Check all outputs are unique in current UTXO set.
self.blockchain.validate_tx(&tx, header)?;
let overage = tx.overage();
let offset = (header.total_kernel_offset() + tx.offset)?;
// Verify the kernel sums for the block_sums with the new tx applied,
// accounting for overage and offset.
let (utxo_sum, kernel_sum) =
(block_sums.clone(), &tx as &Committed).verify_kernel_sums(overage, offset)?;
Ok(BlockSums {
utxo_sum,
kernel_sum,
})
}
pub fn reconcile(
&mut self,
extra_tx: Option<Transaction>,
block_hash: &Hash,
header: &BlockHeader,
) -> Result<(), PoolError> {
let candidate_txs = self.all_transactions();
let existing_len = candidate_txs.len();
let existing_entries = self.entries.clone();
self.entries.clear();
if candidate_txs.is_empty() {
return Ok(());
let mut extra_txs = vec![];
if let Some(extra_tx) = extra_tx {
extra_txs.push(extra_tx);
}
// Go through the candidate txs and keep everything that validates incrementally
// against a known chain state, accounting for the "extra tx" as necessary.
let valid_txs = self
.blockchain
.validate_raw_txs(candidate_txs, extra_tx, block_hash)?;
self.entries.retain(|x| valid_txs.contains(&x.tx));
debug!(
LOGGER,
"pool [{}]: reconcile: existing txs {}, retained txs {}",
self.name,
existing_len,
self.entries.len(),
);
for x in existing_entries {
let _ = self.add_to_pool(x.clone(), extra_txs.clone(), header);
}
Ok(())
}
+19 -13
View File
@@ -24,7 +24,7 @@ use chrono::prelude::Utc;
use core::core::hash::{Hash, Hashed};
use core::core::id::ShortId;
use core::core::verifier_cache::VerifierCache;
use core::core::{transaction, Block, Transaction};
use core::core::{transaction, Block, BlockHeader, Transaction};
use pool::Pool;
use types::{BlockChain, PoolAdapter, PoolConfig, PoolEntry, PoolEntryState, PoolError, TxSource};
@@ -61,33 +61,39 @@ impl TransactionPool {
}
}
fn add_to_stempool(&mut self, entry: PoolEntry, block_hash: &Hash) -> Result<(), PoolError> {
fn add_to_stempool(&mut self, entry: PoolEntry, header: &BlockHeader) -> Result<(), PoolError> {
// Add tx to stempool (passing in all txs from txpool to validate against).
self.stempool
.add_to_pool(entry.clone(), self.txpool.all_transactions(), block_hash)?;
.add_to_pool(entry.clone(), self.txpool.all_transactions(), header)?;
// Note: we do not notify the adapter here,
// we let the dandelion monitor handle this.
Ok(())
}
fn add_to_txpool(&mut self, mut entry: PoolEntry, block_hash: &Hash) -> Result<(), PoolError> {
fn add_to_txpool(
&mut self,
mut entry: PoolEntry,
header: &BlockHeader,
) -> Result<(), PoolError> {
// First deaggregate the tx based on current txpool txs.
if entry.tx.kernels().len() > 1 {
let txs = self
.txpool
.find_matching_transactions(entry.tx.kernels().clone());
if !txs.is_empty() {
entry.tx = transaction::deaggregate(entry.tx, txs, self.verifier_cache.clone())?;
let tx = transaction::deaggregate(entry.tx, txs)?;
tx.validate(self.verifier_cache.clone())?;
entry.tx = tx;
entry.src.debug_name = "deagg".to_string();
}
}
self.txpool.add_to_pool(entry.clone(), vec![], block_hash)?;
self.txpool.add_to_pool(entry.clone(), vec![], header)?;
// We now need to reconcile the stempool based on the new state of the txpool.
// Some stempool txs may no longer be valid and we need to evict them.
let txpool_tx = self.txpool.aggregate_transaction()?;
self.stempool.reconcile(txpool_tx, block_hash)?;
self.stempool.reconcile(txpool_tx, header)?;
self.adapter.tx_accepted(&entry.tx);
Ok(())
@@ -100,7 +106,7 @@ impl TransactionPool {
src: TxSource,
tx: Transaction,
stem: bool,
block_hash: &Hash,
header: &BlockHeader,
) -> Result<(), PoolError> {
// Quick check to deal with common case of seeing the *same* tx
// broadcast from multiple peers simultaneously.
@@ -129,9 +135,9 @@ impl TransactionPool {
};
if stem {
self.add_to_stempool(entry, block_hash)?;
self.add_to_stempool(entry, header)?;
} else {
self.add_to_txpool(entry, block_hash)?;
self.add_to_txpool(entry, header)?;
}
Ok(())
}
@@ -141,12 +147,12 @@ impl TransactionPool {
pub fn reconcile_block(&mut self, block: &Block) -> Result<(), PoolError> {
// First reconcile the txpool.
self.txpool.reconcile_block(block)?;
self.txpool.reconcile(None, &block.hash())?;
self.txpool.reconcile(None, &block.header)?;
// Then reconcile the stempool, accounting for the txpool txs.
let txpool_tx = self.txpool.aggregate_transaction()?;
self.stempool.reconcile_block(block)?;
self.stempool.reconcile(txpool_tx, &block.hash())?;
self.stempool.reconcile(txpool_tx, &block.header)?;
Ok(())
}
@@ -191,7 +197,7 @@ impl TransactionPool {
/// Returns a vector of transactions from the txpool so we can build a
/// block from them.
pub fn prepare_mineable_transactions(&self) -> Vec<Transaction> {
pub fn prepare_mineable_transactions(&self) -> Result<Vec<Transaction>, PoolError> {
self.txpool.prepare_mineable_transactions()
}
}
+24 -10
View File
@@ -18,9 +18,11 @@
use chrono::prelude::{DateTime, Utc};
use core::consensus;
use core::core::committed;
use core::core::hash::Hash;
use core::core::transaction::{self, Transaction};
use core::core::BlockHeader;
use core::core::{BlockHeader, BlockSums};
use keychain;
/// Dandelion relay timer
const DANDELION_RELAY_SECS: u64 = 600;
@@ -161,6 +163,10 @@ pub struct TxSource {
pub enum PoolError {
/// An invalid pool entry caused by underlying tx validation error
InvalidTx(transaction::Error),
/// Underlying keychain error.
Keychain(keychain::Error),
/// Underlying "committed" error.
Committed(committed::Error),
/// Attempt to add a transaction to the pool with lock_height
/// greater than height of current block
ImmatureTransaction,
@@ -186,17 +192,20 @@ impl From<transaction::Error> for PoolError {
}
}
impl From<keychain::Error> for PoolError {
fn from(e: keychain::Error) -> PoolError {
PoolError::Keychain(e)
}
}
impl From<committed::Error> for PoolError {
fn from(e: committed::Error) -> PoolError {
PoolError::Committed(e)
}
}
/// Interface that the pool requires from a blockchain implementation.
pub trait BlockChain: Sync + Send {
/// Validate a vec of txs against known chain state at specific block
/// after applying the pre_tx to the chain state.
fn validate_raw_txs(
&self,
txs: Vec<transaction::Transaction>,
pre_tx: Option<transaction::Transaction>,
block_hash: &Hash,
) -> Result<Vec<transaction::Transaction>, PoolError>;
/// Verify any coinbase outputs being spent
/// have matured sufficiently.
fn verify_coinbase_maturity(&self, tx: &transaction::Transaction) -> Result<(), PoolError>;
@@ -205,7 +214,12 @@ pub trait BlockChain: Sync + Send {
/// have matured sufficiently.
fn verify_tx_lock_height(&self, tx: &transaction::Transaction) -> Result<(), PoolError>;
fn validate_tx(&self, tx: &Transaction, header: &BlockHeader) -> Result<(), PoolError>;
fn chain_head(&self) -> Result<BlockHeader, PoolError>;
fn get_block_header(&self, hash: &Hash) -> Result<BlockHeader, PoolError>;
fn get_block_sums(&self, hash: &Hash) -> Result<BlockSums, PoolError>;
}
/// Bridge between the transaction pool and the rest of the system. Handles
+21 -65
View File
@@ -27,12 +27,8 @@ pub mod common;
use std::sync::{Arc, RwLock};
use core::core::{Block, BlockHeader};
use chain::txhashset;
use chain::types::Tip;
use core::core::hash::Hashed;
use core::core::verifier_cache::LruVerifierCache;
use core::core::{Block, BlockHeader, Transaction};
use core::pow::Difficulty;
use keychain::{ExtKeychain, Keychain};
@@ -47,53 +43,35 @@ fn test_transaction_pool_block_building() {
let db_root = ".grin_block_building".to_string();
clean_output_dir(db_root.clone());
let chain = ChainAdapter::init(db_root.clone()).unwrap();
let mut chain = ChainAdapter::init(db_root.clone()).unwrap();
let verifier_cache = Arc::new(RwLock::new(LruVerifierCache::new()));
// Initialize the chain/txhashset with an initial block
// so we have a non-empty UTXO set.
let add_block = |height, txs| {
let add_block = |prev_header: BlockHeader, txs: Vec<Transaction>, chain: &mut ChainAdapter| {
let height = prev_header.height + 1;
let key_id = keychain.derive_key_id(height as u32).unwrap();
let reward = libtx::reward::output(&keychain, &key_id, 0, height).unwrap();
let mut block =
Block::new(&BlockHeader::default(), txs, Difficulty::one(), reward).unwrap();
let fee = txs.iter().map(|x| x.fee()).sum();
let reward = libtx::reward::output(&keychain, &key_id, fee, height).unwrap();
let block = Block::new(&prev_header, txs, Difficulty::one(), reward).unwrap();
let mut txhashset = chain.txhashset.write().unwrap();
let mut batch = chain.store.batch().unwrap();
txhashset::extending(&mut txhashset, &mut batch, |extension| {
extension.apply_block(&block)?;
// Now set the roots and sizes as necessary on the block header.
let roots = extension.roots();
block.header.output_root = roots.output_root;
block.header.range_proof_root = roots.rproof_root;
block.header.kernel_root = roots.kernel_root;
let sizes = extension.sizes();
block.header.output_mmr_size = sizes.0;
block.header.kernel_mmr_size = sizes.2;
Ok(())
}).unwrap();
let tip = Tip::from_block(&block.header);
batch.save_block_header(&block.header).unwrap();
batch.save_head(&tip).unwrap();
batch.commit().unwrap();
chain.update_db_for_block(&block);
block.header
};
let header = add_block(1, vec![]);
// Initialize a new pool with our chain adapter.
let pool = RwLock::new(test_setup(Arc::new(chain.clone()), verifier_cache));
let header = add_block(BlockHeader::default(), vec![], &mut chain);
// Now create tx to spend that first coinbase (now matured).
// Provides us with some useful outputs to test with.
let initial_tx = test_transaction_spending_coinbase(&keychain, &header, vec![10, 20, 30, 40]);
// Mine that initial tx so we can spend it with multiple txs
let header = add_block(2, vec![initial_tx]);
let header = add_block(header, vec![initial_tx], &mut chain);
// Initialize a new pool with our chain adapter.
let pool = RwLock::new(test_setup(Arc::new(chain.clone()), verifier_cache));
let root_tx_1 = test_transaction(&keychain, vec![10, 20], vec![24]);
let root_tx_2 = test_transaction(&keychain, vec![30], vec![28]);
@@ -107,21 +85,21 @@ fn test_transaction_pool_block_building() {
// Add the three root txs to the pool.
write_pool
.add_to_pool(test_source(), root_tx_1, false, &header.hash())
.add_to_pool(test_source(), root_tx_1, false, &header)
.unwrap();
write_pool
.add_to_pool(test_source(), root_tx_2, false, &header.hash())
.add_to_pool(test_source(), root_tx_2, false, &header)
.unwrap();
write_pool
.add_to_pool(test_source(), root_tx_3, false, &header.hash())
.add_to_pool(test_source(), root_tx_3, false, &header)
.unwrap();
// Now add the two child txs to the pool.
write_pool
.add_to_pool(test_source(), child_tx_1.clone(), false, &header.hash())
.add_to_pool(test_source(), child_tx_1.clone(), false, &header)
.unwrap();
write_pool
.add_to_pool(test_source(), child_tx_2.clone(), false, &header.hash())
.add_to_pool(test_source(), child_tx_2.clone(), false, &header)
.unwrap();
assert_eq!(write_pool.total_size(), 5);
@@ -129,41 +107,19 @@ fn test_transaction_pool_block_building() {
let txs = {
let read_pool = pool.read().unwrap();
read_pool.prepare_mineable_transactions()
read_pool.prepare_mineable_transactions().unwrap()
};
// children should have been aggregated into parents
assert_eq!(txs.len(), 3);
let mut block = {
let block = {
let key_id = keychain.derive_key_id(2).unwrap();
let fees = txs.iter().map(|tx| tx.fee()).sum();
let reward = libtx::reward::output(&keychain, &key_id, fees, 0).unwrap();
Block::new(&header, txs, Difficulty::one(), reward)
}.unwrap();
{
let mut batch = chain.store.batch().unwrap();
let mut txhashset = chain.txhashset.write().unwrap();
txhashset::extending(&mut txhashset, &mut batch, |extension| {
extension.apply_block(&block)?;
// Now set the roots and sizes as necessary on the block header.
let roots = extension.roots();
block.header.output_root = roots.output_root;
block.header.range_proof_root = roots.rproof_root;
block.header.kernel_root = roots.kernel_root;
let sizes = extension.sizes();
block.header.output_mmr_size = sizes.0;
block.header.kernel_mmr_size = sizes.2;
Ok(())
}).unwrap();
let tip = Tip::from_block(&block.header);
batch.save_block_header(&block.header).unwrap();
batch.save_head(&tip).unwrap();
batch.commit().unwrap();
}
chain.update_db_for_block(&block);
// Now reconcile the transaction pool with the new block
// and check the resulting contents of the pool are what we expect.
+17 -90
View File
@@ -27,15 +27,9 @@ pub mod common;
use std::sync::{Arc, RwLock};
use core::core::hash::Hashed;
use core::core::{Block, BlockHeader};
use chain::txhashset;
use chain::types::Tip;
use common::{
clean_output_dir, test_setup, test_source, test_transaction,
test_transaction_spending_coinbase, ChainAdapter,
};
use common::*;
use core::core::verifier_cache::LruVerifierCache;
use core::pow::Difficulty;
use keychain::{ExtKeychain, Keychain};
@@ -47,84 +41,41 @@ fn test_transaction_pool_block_reconciliation() {
let db_root = ".grin_block_reconciliation".to_string();
clean_output_dir(db_root.clone());
let chain = ChainAdapter::init(db_root.clone()).unwrap();
let chain = Arc::new(ChainAdapter::init(db_root.clone()).unwrap());
let verifier_cache = Arc::new(RwLock::new(LruVerifierCache::new()));
// Initialize the chain/txhashset with an initial block
// so we have a non-empty UTXO set.
// Initialize a new pool with our chain adapter.
let pool = RwLock::new(test_setup(chain.clone(), verifier_cache.clone()));
let header = {
let height = 1;
let key_id = keychain.derive_key_id(height as u32).unwrap();
let reward = libtx::reward::output(&keychain, &key_id, 0, height).unwrap();
let mut block =
Block::new(&BlockHeader::default(), vec![], Difficulty::one(), reward).unwrap();
let block = Block::new(&BlockHeader::default(), vec![], Difficulty::one(), reward).unwrap();
let mut batch = chain.store.batch().unwrap();
let mut txhashset = chain.txhashset.write().unwrap();
txhashset::extending(&mut txhashset, &mut batch, |extension| {
extension.apply_block(&block)?;
// Now set the roots and sizes as necessary on the block header.
let roots = extension.roots();
block.header.output_root = roots.output_root;
block.header.range_proof_root = roots.rproof_root;
block.header.kernel_root = roots.kernel_root;
let sizes = extension.sizes();
block.header.output_mmr_size = sizes.0;
block.header.kernel_mmr_size = sizes.2;
Ok(())
}).unwrap();
let tip = Tip::from_block(&block.header);
batch.save_block_header(&block.header).unwrap();
batch.save_head(&tip).unwrap();
batch.commit().unwrap();
chain.update_db_for_block(&block);
block.header
};
// Initialize a new pool with our chain adapter.
let pool = RwLock::new(test_setup(Arc::new(chain.clone()), verifier_cache.clone()));
// Now create tx to spend that first coinbase (now matured).
// Provides us with some useful outputs to test with.
let initial_tx = test_transaction_spending_coinbase(&keychain, &header, vec![10, 20, 30, 40]);
let header = {
let block = {
let key_id = keychain.derive_key_id(2).unwrap();
let fees = initial_tx.fee();
let reward = libtx::reward::output(&keychain, &key_id, fees, 0).unwrap();
let mut block = Block::new(&header, vec![initial_tx], Difficulty::one(), reward).unwrap();
let block = Block::new(&header, vec![initial_tx], Difficulty::one(), reward).unwrap();
let mut batch = chain.store.batch().unwrap();
{
let mut txhashset = chain.txhashset.write().unwrap();
txhashset::extending(&mut txhashset, &mut batch, |extension| {
extension.apply_block(&block)?;
chain.update_db_for_block(&block);
// Now set the roots and sizes as necessary on the block header.
let roots = extension.roots();
block.header.output_root = roots.output_root;
block.header.range_proof_root = roots.rproof_root;
block.header.kernel_root = roots.kernel_root;
let sizes = extension.sizes();
block.header.output_mmr_size = sizes.0;
block.header.kernel_mmr_size = sizes.2;
Ok(())
}).unwrap();
}
let tip = Tip::from_block(&block.header);
batch.save_block_header(&block.header).unwrap();
batch.save_head(&tip).unwrap();
batch.commit().unwrap();
block.header
block
};
let header = block.header;
// Preparation: We will introduce three root pool transactions.
// 1. A transaction that should be invalidated because it is exactly
// contained in the block.
@@ -181,7 +132,7 @@ fn test_transaction_pool_block_reconciliation() {
for tx in &txs_to_add {
write_pool
.add_to_pool(test_source(), tx.clone(), false, &header.hash())
.add_to_pool(test_source(), tx.clone(), false, &header)
.unwrap();
}
@@ -198,6 +149,7 @@ fn test_transaction_pool_block_reconciliation() {
let block_tx_3 = test_transaction(&keychain, vec![8], vec![5, 1]);
// - Output conflict w/ 8
let block_tx_4 = test_transaction(&keychain, vec![40], vec![9, 31]);
let block_txs = vec![block_tx_1, block_tx_2, block_tx_3, block_tx_4];
// Now apply this block.
@@ -205,34 +157,9 @@ fn test_transaction_pool_block_reconciliation() {
let key_id = keychain.derive_key_id(3).unwrap();
let fees = block_txs.iter().map(|tx| tx.fee()).sum();
let reward = libtx::reward::output(&keychain, &key_id, fees, 0).unwrap();
let mut block = Block::new(&header, block_txs, Difficulty::one(), reward).unwrap();
{
let mut batch = chain.store.batch().unwrap();
let mut txhashset = chain.txhashset.write().unwrap();
txhashset::extending(&mut txhashset, &mut batch, |extension| {
extension.apply_block(&block)?;
// Now set the roots and sizes as necessary on the block header.
let roots = extension.roots();
block.header.output_root = roots.output_root;
block.header.range_proof_root = roots.rproof_root;
block.header.kernel_root = roots.kernel_root;
let sizes = extension.sizes();
block.header.output_mmr_size = sizes.0;
block.header.kernel_mmr_size = sizes.2;
Ok(())
}).unwrap();
batch.commit().unwrap();
}
let tip = Tip::from_block(&block.header);
let batch = chain.store.batch().unwrap();
batch.save_block_header(&block.header).unwrap();
batch.save_head(&tip).unwrap();
batch.commit().unwrap();
let block = Block::new(&header, block_txs, Difficulty::one(), reward).unwrap();
chain.update_db_for_block(&block);
block
};
+12 -9
View File
@@ -27,10 +27,10 @@ pub mod common;
use std::sync::{Arc, RwLock};
use common::{test_setup, test_source, test_transaction};
use common::*;
use core::core::hash::Hash;
use core::core::verifier_cache::LruVerifierCache;
use core::core::{BlockHeader, Transaction};
use core::core::{BlockHeader, BlockSums, Transaction};
use keychain::{ExtKeychain, Keychain};
use pool::types::{BlockChain, PoolError};
@@ -48,12 +48,15 @@ impl BlockChain for CoinbaseMaturityErrorChainAdapter {
unimplemented!();
}
fn validate_raw_txs(
&self,
_txs: Vec<Transaction>,
_pre_tx: Option<Transaction>,
_block_hash: &Hash,
) -> Result<Vec<Transaction>, PoolError> {
fn get_block_header(&self, _hash: &Hash) -> Result<BlockHeader, PoolError> {
unimplemented!();
}
fn get_block_sums(&self, _hash: &Hash) -> Result<BlockSums, PoolError> {
unimplemented!();
}
fn validate_tx(&self, _tx: &Transaction, _header: &BlockHeader) -> Result<(), PoolError> {
unimplemented!();
}
@@ -82,7 +85,7 @@ fn test_coinbase_maturity() {
{
let mut write_pool = pool.write().unwrap();
let tx = test_transaction(&keychain, vec![50], vec![49]);
match write_pool.add_to_pool(test_source(), tx.clone(), true, &Hash::default()) {
match write_pool.add_to_pool(test_source(), tx.clone(), true, &BlockHeader::default()) {
Err(PoolError::ImmatureCoinbase) => {}
_ => panic!("Expected an immature coinbase error here."),
}
+80 -28
View File
@@ -26,16 +26,16 @@ extern crate grin_wallet as wallet;
extern crate chrono;
extern crate rand;
use std::collections::HashSet;
use std::fs;
use std::sync::{Arc, RwLock};
use core::core::hash::Hash;
use core::core::hash::{Hash, Hashed};
use core::core::verifier_cache::VerifierCache;
use core::core::{BlockHeader, Transaction};
use core::core::{Block, BlockHeader, BlockSums, Committed, Transaction};
use chain::store::ChainStore;
use chain::txhashset;
use chain::txhashset::TxHashSet;
use chain::types::Tip;
use pool::*;
use keychain::Keychain;
@@ -43,11 +43,12 @@ use wallet::libtx;
use pool::types::*;
use pool::TransactionPool;
use util::secp::pedersen::Commitment;
#[derive(Clone)]
pub struct ChainAdapter {
pub txhashset: Arc<RwLock<TxHashSet>>,
pub store: Arc<ChainStore>,
pub utxo: Arc<RwLock<HashSet<Commitment>>>,
}
impl ChainAdapter {
@@ -57,13 +58,54 @@ impl ChainAdapter {
let chain_store =
ChainStore::new(db_env).map_err(|e| format!("failed to init chain_store, {:?}", e))?;
let store = Arc::new(chain_store);
let txhashset = TxHashSet::open(target_dir.clone(), store.clone(), None)
.map_err(|e| format!("failed to init txhashset, {}", e))?;
let utxo = Arc::new(RwLock::new(HashSet::new()));
Ok(ChainAdapter {
txhashset: Arc::new(RwLock::new(txhashset)),
store: store.clone(),
})
Ok(ChainAdapter { store, utxo })
}
pub fn update_db_for_block(&self, block: &Block) {
let header = &block.header;
let batch = self.store.batch().unwrap();
let tip = Tip::from_block(&header);
batch.save_block_header(&header).unwrap();
batch.save_head(&tip).unwrap();
// Retrieve previous block_sums from the db.
let prev_sums = if let Ok(prev_sums) = batch.get_block_sums(&header.previous) {
prev_sums
} else {
BlockSums::default()
};
// Overage is based purely on the new block.
// Previous block_sums have taken all previous overage into account.
let overage = header.overage();
// Offset on the other hand is the total kernel offset from the new block.
let offset = header.total_kernel_offset();
// Verify the kernel sums for the block_sums with the new block applied.
let (utxo_sum, kernel_sum) = (prev_sums, block as &Committed)
.verify_kernel_sums(overage, offset)
.unwrap();
let block_sums = BlockSums {
utxo_sum,
kernel_sum,
};
batch.save_block_sums(&header.hash(), &block_sums).unwrap();
batch.commit().unwrap();
{
let mut utxo = self.utxo.write().unwrap();
for x in block.inputs() {
utxo.remove(&x.commitment());
}
for x in block.outputs() {
utxo.insert(x.commitment());
}
}
}
}
@@ -74,24 +116,34 @@ impl BlockChain for ChainAdapter {
.map_err(|_| PoolError::Other(format!("failed to get chain head")))
}
fn validate_raw_txs(
&self,
txs: Vec<Transaction>,
pre_tx: Option<Transaction>,
block_hash: &Hash,
) -> Result<Vec<Transaction>, PoolError> {
let header = self
.store
.get_block_header(&block_hash)
.map_err(|_| PoolError::Other(format!("failed to get header")))?;
fn get_block_header(&self, hash: &Hash) -> Result<BlockHeader, PoolError> {
self.store
.get_block_header(hash)
.map_err(|_| PoolError::Other(format!("failed to get block header")))
}
let mut txhashset = self.txhashset.write().unwrap();
let res = txhashset::extending_readonly(&mut txhashset, |extension| {
extension.rewind(&header)?;
let valid_txs = extension.validate_raw_txs(txs, pre_tx)?;
Ok(valid_txs)
}).map_err(|e| PoolError::Other(format!("Error: test chain adapter: {:?}", e)))?;
Ok(res)
fn get_block_sums(&self, hash: &Hash) -> Result<BlockSums, PoolError> {
self.store
.get_block_sums(hash)
.map_err(|_| PoolError::Other(format!("failed to get block sums")))
}
fn validate_tx(&self, tx: &Transaction, _header: &BlockHeader) -> Result<(), pool::PoolError> {
let utxo = self.utxo.read().unwrap();
for x in tx.outputs() {
if utxo.contains(&x.commitment()) {
return Err(PoolError::Other(format!("output commitment not unique")));
}
}
for x in tx.inputs() {
if !utxo.contains(&x.commitment()) {
return Err(PoolError::Other(format!("not in utxo set")));
}
}
Ok(())
}
// Mocking this check out for these tests.
+22 -54
View File
@@ -27,13 +27,7 @@ pub mod common;
use std::sync::{Arc, RwLock};
use chain::txhashset;
use chain::types::Tip;
use common::{
clean_output_dir, test_setup, test_source, test_transaction,
test_transaction_spending_coinbase, ChainAdapter,
};
use core::core::hash::Hashed;
use common::*;
use core::core::verifier_cache::LruVerifierCache;
use core::core::{transaction, Block, BlockHeader};
use core::pow::Difficulty;
@@ -47,12 +41,13 @@ fn test_the_transaction_pool() {
let db_root = ".grin_transaction_pool".to_string();
clean_output_dir(db_root.clone());
let chain = ChainAdapter::init(db_root.clone()).unwrap();
let chain = Arc::new(ChainAdapter::init(db_root.clone()).unwrap());
let verifier_cache = Arc::new(RwLock::new(LruVerifierCache::new()));
// Initialize the chain/txhashset with a few blocks,
// so we have a non-empty UTXO set.
// Initialize a new pool with our chain adapter.
let pool = RwLock::new(test_setup(chain.clone(), verifier_cache.clone()));
let header = {
let height = 1;
let key_id = keychain.derive_key_id(height as u32).unwrap();
@@ -60,34 +55,11 @@ fn test_the_transaction_pool() {
let mut block =
Block::new(&BlockHeader::default(), vec![], Difficulty::one(), reward).unwrap();
let mut txhashset = chain.txhashset.write().unwrap();
let mut batch = chain.store.batch().unwrap();
txhashset::extending(&mut txhashset, &mut batch, |extension| {
extension.apply_block(&block)?;
// Now set the roots and sizes as necessary on the block header.
let roots = extension.roots();
block.header.output_root = roots.output_root;
block.header.range_proof_root = roots.rproof_root;
block.header.kernel_root = roots.kernel_root;
let sizes = extension.sizes();
block.header.output_mmr_size = sizes.0;
block.header.kernel_mmr_size = sizes.2;
Ok(())
}).unwrap();
let tip = Tip::from_block(&block.header);
batch.save_block_header(&block.header).unwrap();
batch.save_head(&tip).unwrap();
batch.commit().unwrap();
chain.update_db_for_block(&block);
block.header
};
// Initialize a new pool with our chain adapter.
let pool = RwLock::new(test_setup(Arc::new(chain.clone()), verifier_cache.clone()));
// Now create tx to spend a coinbase, giving us some useful outputs for testing
// with.
let initial_tx = {
@@ -102,7 +74,7 @@ fn test_the_transaction_pool() {
{
let mut write_pool = pool.write().unwrap();
write_pool
.add_to_pool(test_source(), initial_tx, false, &header.hash())
.add_to_pool(test_source(), initial_tx, false, &header)
.unwrap();
assert_eq!(write_pool.total_size(), 1);
}
@@ -121,14 +93,14 @@ fn test_the_transaction_pool() {
// First, add a simple tx to the pool in "stem" mode.
write_pool
.add_to_pool(test_source(), tx1.clone(), true, &header.hash())
.add_to_pool(test_source(), tx1.clone(), true, &header)
.unwrap();
assert_eq!(write_pool.total_size(), 1);
assert_eq!(write_pool.stempool.size(), 1);
// Add another tx spending outputs from the previous tx.
write_pool
.add_to_pool(test_source(), tx2.clone(), true, &header.hash())
.add_to_pool(test_source(), tx2.clone(), true, &header)
.unwrap();
assert_eq!(write_pool.total_size(), 1);
assert_eq!(write_pool.stempool.size(), 2);
@@ -141,7 +113,7 @@ fn test_the_transaction_pool() {
let mut write_pool = pool.write().unwrap();
assert!(
write_pool
.add_to_pool(test_source(), tx1.clone(), true, &header.hash())
.add_to_pool(test_source(), tx1.clone(), true, &header)
.is_err()
);
}
@@ -153,7 +125,7 @@ fn test_the_transaction_pool() {
let mut write_pool = pool.write().unwrap();
assert!(
write_pool
.add_to_pool(test_source(), tx1a, true, &header.hash())
.add_to_pool(test_source(), tx1a, true, &header)
.is_err()
);
}
@@ -164,7 +136,7 @@ fn test_the_transaction_pool() {
let mut write_pool = pool.write().unwrap();
assert!(
write_pool
.add_to_pool(test_source(), bad_tx, true, &header.hash())
.add_to_pool(test_source(), bad_tx, true, &header)
.is_err()
);
}
@@ -178,7 +150,7 @@ fn test_the_transaction_pool() {
let mut write_pool = pool.write().unwrap();
assert!(
write_pool
.add_to_pool(test_source(), tx, true, &header.hash())
.add_to_pool(test_source(), tx, true, &header)
.is_err()
);
}
@@ -189,7 +161,7 @@ fn test_the_transaction_pool() {
let tx3 = test_transaction(&keychain, vec![500], vec![497]);
assert!(
write_pool
.add_to_pool(test_source(), tx3, true, &header.hash())
.add_to_pool(test_source(), tx3, true, &header)
.is_err()
);
assert_eq!(write_pool.total_size(), 1);
@@ -207,7 +179,7 @@ fn test_the_transaction_pool() {
.unwrap();
assert_eq!(agg_tx.kernels().len(), 2);
write_pool
.add_to_pool(test_source(), agg_tx, false, &header.hash())
.add_to_pool(test_source(), agg_tx, false, &header)
.unwrap();
assert_eq!(write_pool.total_size(), 2);
}
@@ -222,11 +194,12 @@ fn test_the_transaction_pool() {
let tx4 = test_transaction(&keychain, vec![800], vec![799]);
// tx1 and tx2 are already in the txpool (in aggregated form)
// tx4 is the "new" part of this aggregated tx that we care about
let agg_tx =
transaction::aggregate(vec![tx1.clone(), tx2.clone(), tx4], verifier_cache.clone())
.unwrap();
let agg_tx = transaction::aggregate(vec![tx1.clone(), tx2.clone(), tx4]).unwrap();
agg_tx.validate(verifier_cache.clone()).unwrap();
write_pool
.add_to_pool(test_source(), agg_tx, false, &header.hash())
.add_to_pool(test_source(), agg_tx, false, &header)
.unwrap();
assert_eq!(write_pool.total_size(), 3);
let entry = write_pool.txpool.entries.last().unwrap();
@@ -245,19 +218,14 @@ fn test_the_transaction_pool() {
// check we cannot add a double spend to the stempool
assert!(
write_pool
.add_to_pool(test_source(), double_spend_tx.clone(), true, &header.hash())
.add_to_pool(test_source(), double_spend_tx.clone(), true, &header)
.is_err()
);
// check we cannot add a double spend to the txpool
assert!(
write_pool
.add_to_pool(
test_source(),
double_spend_tx.clone(),
false,
&header.hash()
)
.add_to_pool(test_source(), double_spend_tx.clone(), false, &header)
.is_err()
);
}