From e72d8b58e4641dfa45a848d627fea0e2f417fd0f Mon Sep 17 00:00:00 2001 From: Antioch Peverell Date: Mon, 24 Sep 2018 09:24:10 +0100 Subject: [PATCH] [WIP] txpool (tx validation) using block sums for full validation (#1567) tx validation (txpool) rework/simplify --- api/src/handlers.rs | 11 +- chain/src/chain.rs | 39 +++--- chain/src/pipe.rs | 4 + chain/src/store.rs | 8 ++ chain/src/txhashset.rs | 160 +++++++---------------- chain/tests/test_txhashset.rs | 112 +--------------- core/src/core/block.rs | 19 +-- core/src/core/block_sums.rs | 1 - core/src/core/transaction.rs | 27 +--- core/tests/core.rs | 80 +++++------- keychain/src/types.rs | 28 ++++ pool/src/pool.rs | 176 +++++++++++++++++--------- pool/src/transaction_pool.rs | 32 +++-- pool/src/types.rs | 34 +++-- pool/tests/block_building.rs | 86 +++---------- pool/tests/block_reconciliation.rs | 107 +++------------- pool/tests/coinbase_maturity.rs | 21 +-- pool/tests/common/mod.rs | 108 ++++++++++++---- pool/tests/transaction_pool.rs | 76 ++++------- servers/src/common/adapters.rs | 46 +++---- servers/src/grin/dandelion_monitor.rs | 42 +++--- servers/src/mining/mine_block.rs | 16 +-- 22 files changed, 521 insertions(+), 712 deletions(-) diff --git a/api/src/handlers.rs b/api/src/handlers.rs index fbc589cb2..b2ea5dc5f 100644 --- a/api/src/handlers.rs +++ b/api/src/handlers.rs @@ -148,8 +148,7 @@ impl OutputHandler { .filter(|output| commitments.is_empty() || commitments.contains(&output.commit)) .map(|output| { OutputPrintable::from_output(output, w(&self.chain), Some(&header), include_proof) - }) - .collect(); + }).collect(); Ok(BlockOutputs { header: BlockHeaderInfo::from_header(&header), @@ -737,12 +736,10 @@ impl PoolPushHandler { .and_then(move |wrapper: TxWrapper| { util::from_hex(wrapper.tx_hex) .map_err(|e| ErrorKind::RequestError(format!("Bad request: {}", e)).into()) - }) - .and_then(move |tx_bin| { + }).and_then(move |tx_bin| { ser::deserialize(&mut &tx_bin[..]) .map_err(|e| ErrorKind::RequestError(format!("Bad request: {}", e)).into()) - }) - .and_then(move |tx: Transaction| { + }).and_then(move |tx: Transaction| { let source = pool::TxSource { debug_name: "push-api".to_string(), identifier: "?.?.?.?".to_string(), @@ -760,7 +757,7 @@ impl PoolPushHandler { let mut tx_pool = pool_arc.write().unwrap(); let header = tx_pool.blockchain.chain_head().unwrap(); tx_pool - .add_to_pool(source, tx, !fluff, &header.hash()) + .add_to_pool(source, tx, !fluff, &header) .map_err(|e| { error!(LOGGER, "update_pool: failed with error: {:?}", e); ErrorKind::Internal(format!("Failed to update pool: {:?}", e)).into() diff --git a/chain/src/chain.rs b/chain/src/chain.rs index b897d11f4..78aae1c4a 100644 --- a/chain/src/chain.rs +++ b/chain/src/chain.rs @@ -431,6 +431,7 @@ impl Chain { } } + /// TODO - where do we call this from? And do we need a rewind first? /// For the given commitment find the unspent output and return the /// associated Return an error if the output does not exist or has been /// spent. This querying is done in a way that is consistent with the @@ -445,33 +446,20 @@ impl Chain { } } + pub fn validate_tx(&self, tx: &Transaction, header: &BlockHeader) -> Result<(), Error> { + let mut txhashset = self.txhashset.write().unwrap(); + txhashset::extending_readonly(&mut txhashset, |extension| { + extension.rewind(header)?; + extension.validate_utxo_fast(tx.inputs(), tx.outputs())?; + Ok(()) + }) + } + fn next_block_height(&self) -> Result { let bh = self.head_header()?; Ok(bh.height + 1) } - /// Validate a vec of "raw" transactions against a known chain state - /// at the block with the specified block hash. - /// Specifying a "pre_tx" if we need to adjust the state, for example when - /// validating the txs in the stempool we adjust the state based on the - /// txpool. - pub fn validate_raw_txs( - &self, - txs: Vec, - pre_tx: Option, - block_hash: &Hash, - ) -> Result, Error> { - // Get header so we can rewind chain state correctly. - let header = self.store.get_block_header(block_hash)?; - - let mut txhashset = self.txhashset.write().unwrap(); - txhashset::extending_readonly(&mut txhashset, |extension| { - extension.rewind(&header)?; - let valid_txs = extension.validate_raw_txs(txs, pre_tx)?; - Ok(valid_txs) - }) - } - /// Verify we are not attempting to spend a coinbase output /// that has not yet sufficiently matured. pub fn verify_coinbase_maturity(&self, tx: &Transaction) -> Result<(), Error> { @@ -876,6 +864,13 @@ impl Chain { .map_err(|e| ErrorKind::StoreErr(e, "chain get header".to_owned()).into()) } + /// Get block_sums by header hash. + pub fn get_block_sums(&self, h: &Hash) -> Result { + self.store + .get_block_sums(h) + .map_err(|e| ErrorKind::StoreErr(e, "chain get block_sums".to_owned()).into()) + } + /// Gets the block header at the provided height pub fn get_header_by_height(&self, height: u64) -> Result { self.store diff --git a/chain/src/pipe.rs b/chain/src/pipe.rs index 700a30858..b4b2db8e2 100644 --- a/chain/src/pipe.rs +++ b/chain/src/pipe.rs @@ -519,6 +519,10 @@ fn verify_coinbase_maturity(block: &Block, ext: &mut txhashset::Extension) -> Re /// based on block_sums of previous block, accounting for the inputs|outputs|kernels /// of the new block. fn verify_block_sums(b: &Block, ext: &mut txhashset::Extension) -> Result<(), Error> { + // First check all our inputs exist in the current UTXO set. + // And that we are not introducing any duplicate outputs in the UTXO set. + ext.validate_utxo_fast(b.inputs(), b.outputs())?; + // Retrieve the block_sums for the previous block. let block_sums = ext.batch.get_block_sums(&b.header.previous)?; diff --git a/chain/src/store.rs b/chain/src/store.rs index 483cea478..089869d01 100644 --- a/chain/src/store.rs +++ b/chain/src/store.rs @@ -90,6 +90,14 @@ impl ChainStore { self.db.exists(&to_key(BLOCK_PREFIX, &mut h.to_vec())) } + pub fn get_block_sums(&self, bh: &Hash) -> Result { + option_to_not_found( + self.db + .get_ser(&to_key(BLOCK_SUMS_PREFIX, &mut bh.to_vec())), + &format!("Block sums for block: {}", bh), + ) + } + pub fn get_block_header(&self, h: &Hash) -> Result { { let mut header_cache = self.header_cache.write().unwrap(); diff --git a/chain/src/txhashset.rs b/chain/src/txhashset.rs index 5675d549d..d9c6dbf59 100644 --- a/chain/src/txhashset.rs +++ b/chain/src/txhashset.rs @@ -29,9 +29,7 @@ use core::core::committed::Committed; use core::core::hash::{Hash, Hashed}; use core::core::merkle_proof::MerkleProof; use core::core::pmmr::{self, PMMR}; -use core::core::{ - Block, BlockHeader, Input, Output, OutputFeatures, OutputIdentifier, Transaction, TxKernel, -}; +use core::core::{Block, BlockHeader, Input, Output, OutputFeatures, OutputIdentifier, TxKernel}; use core::global; use core::ser::{PMMRIndexHashable, PMMRable}; @@ -439,115 +437,6 @@ impl<'a> Extension<'a> { } } - // Rewind the MMR backend to undo applying a raw tx to the txhashset extension. - // This is used during txpool validation to undo an invalid tx. - fn rewind_raw_tx( - &mut self, - output_pos: u64, - kernel_pos: u64, - rewind_rm_pos: &Bitmap, - ) -> Result<(), Error> { - self.rewind_to_pos(output_pos, kernel_pos, rewind_rm_pos)?; - Ok(()) - } - - /// Apply a "raw" transaction to the txhashset. - /// We will never commit a txhashset extension that includes raw txs. - /// But we can use this when validating txs in the tx pool. - /// If we can add a tx to the tx pool and then successfully add the - /// aggregated tx from the tx pool to the current chain state (via a - /// txhashset extension) then we know the tx pool is valid (including the - /// new tx). - pub fn apply_raw_tx(&mut self, tx: &Transaction) -> Result<(), Error> { - // This should *never* be called on a writeable extension... - assert!( - self.rollback, - "applied raw_tx to writeable txhashset extension" - ); - - // Checkpoint the MMR positions before we apply the new tx, - // anything goes wrong we will rewind to these positions. - let output_pos = self.output_pmmr.unpruned_size(); - let kernel_pos = self.kernel_pmmr.unpruned_size(); - - // Build bitmap of output pos spent (as inputs) by this tx for rewind. - let rewind_rm_pos = tx - .inputs() - .iter() - .filter_map(|x| self.batch.get_output_pos(&x.commitment()).ok()) - .map(|x| x as u32) - .collect(); - - for ref output in tx.outputs() { - match self.apply_output(output) { - Ok(pos) => { - self.batch.save_output_pos(&output.commitment(), pos)?; - } - Err(e) => { - self.rewind_raw_tx(output_pos, kernel_pos, &rewind_rm_pos)?; - return Err(e); - } - } - } - - for ref input in tx.inputs() { - if let Err(e) = self.apply_input(input) { - self.rewind_raw_tx(output_pos, kernel_pos, &rewind_rm_pos)?; - return Err(e); - } - } - - for ref kernel in tx.kernels() { - if let Err(e) = self.apply_kernel(kernel) { - self.rewind_raw_tx(output_pos, kernel_pos, &rewind_rm_pos)?; - return Err(e); - } - } - - Ok(()) - } - - /// Validate a vector of "raw" transactions against the current chain state. - /// We support rewind on a "dirty" txhashset - so we can apply each tx in - /// turn, rewinding if any particular tx is not valid and continuing - /// through the vec of txs provided. This allows us to efficiently apply - /// all the txs, filtering out those that are not valid and returning the - /// final vec of txs that were successfully validated against the txhashset. - /// - /// Note: We also pass in a "pre_tx". This tx is applied to and validated - /// before we start applying the vec of txs. We use this when validating - /// txs in the stempool as we need to account for txs in the txpool as - /// well (new_tx + stempool + txpool + txhashset). So we aggregate the - /// contents of the txpool into a single aggregated tx and pass it in here - /// as the "pre_tx" so we apply it to the txhashset before we start - /// validating the stempool txs. - /// This is optional and we pass in None when validating the txpool txs - /// themselves. - /// - pub fn validate_raw_txs( - &mut self, - txs: Vec, - pre_tx: Option, - ) -> Result, Error> { - let mut valid_txs = vec![]; - - // First apply the "pre_tx" to account for any state that need adding to - // the chain state before we can validate our vec of txs. - // This is the aggregate tx from the txpool if we are validating the stempool. - if let Some(tx) = pre_tx { - self.apply_raw_tx(&tx)?; - } - - // Now validate each tx, rewinding any tx (and only that tx) - // if it fails to validate successfully. - for tx in txs { - if self.apply_raw_tx(&tx).is_ok() { - valid_txs.push(tx); - } - } - Ok(valid_txs) - } - /// Verify we are not attempting to spend any coinbase outputs /// that have not sufficiently matured. pub fn verify_coinbase_maturity( @@ -589,9 +478,25 @@ impl<'a> Extension<'a> { Ok(()) } - /// Apply a new set of blocks on top the existing sum trees. Blocks are - /// applied in order of the provided Vec. If pruning is enabled, inputs also - /// prune MMR data. + // Inputs _must_ spend unspent outputs. + // Outputs _must not_ introduce duplicate commitments. + pub fn validate_utxo_fast( + &mut self, + inputs: &Vec, + outputs: &Vec, + ) -> Result<(), Error> { + for out in outputs { + self.validate_utxo_output(out)?; + } + + for input in inputs { + self.validate_utxo_input(input)?; + } + + Ok(()) + } + + /// Apply a new block to the existing state. pub fn apply_block(&mut self, b: &Block) -> Result<(), Error> { for out in b.outputs() { let pos = self.apply_output(out)?; @@ -610,6 +515,18 @@ impl<'a> Extension<'a> { Ok(()) } + // TODO - Is this sufficient? + fn validate_utxo_input(&mut self, input: &Input) -> Result<(), Error> { + let commit = input.commitment(); + let pos_res = self.batch.get_output_pos(&commit); + if let Ok(pos) = pos_res { + if let Some(_) = self.output_pmmr.get_data(pos) { + return Ok(()); + } + } + Err(ErrorKind::AlreadySpent(commit).into()) + } + fn apply_input(&mut self, input: &Input) -> Result<(), Error> { let commit = input.commitment(); let pos_res = self.batch.get_output_pos(&commit); @@ -648,6 +565,19 @@ impl<'a> Extension<'a> { Ok(()) } + /// TODO - Is this sufficient? + fn validate_utxo_output(&mut self, out: &Output) -> Result<(), Error> { + let commit = out.commitment(); + if let Ok(pos) = self.batch.get_output_pos(&commit) { + if let Some(out_mmr) = self.output_pmmr.get_data(pos) { + if out_mmr.commitment() == commit { + return Err(ErrorKind::DuplicateCommitment(commit).into()); + } + } + } + Ok(()) + } + fn apply_output(&mut self, out: &Output) -> Result<(u64), Error> { let commit = out.commitment(); diff --git a/chain/tests/test_txhashset.rs b/chain/tests/test_txhashset.rs index 8ddf7ab08..5cd8548cc 100644 --- a/chain/tests/test_txhashset.rs +++ b/chain/tests/test_txhashset.rs @@ -17,7 +17,6 @@ extern crate grin_core as core; extern crate grin_keychain as keychain; extern crate grin_store as store; extern crate grin_util as util; -extern crate grin_wallet as wallet; use std::collections::HashSet; use std::fs::{self, File, OpenOptions}; @@ -27,113 +26,13 @@ use std::sync::Arc; use chain::store::ChainStore; use chain::txhashset; -use chain::types::Tip; -use core::core::{Block, BlockHeader}; -use core::pow::Difficulty; -use keychain::{ExtKeychain, Keychain}; +use core::core::BlockHeader; use util::file; -use wallet::libtx::{build, reward}; fn clean_output_dir(dir_name: &str) { let _ = fs::remove_dir_all(dir_name); } -#[test] -fn test_some_raw_txs() { - let db_root = format!(".grin_txhashset_raw_txs"); - clean_output_dir(&db_root); - - let db_env = Arc::new(store::new_env(db_root.clone())); - - let chain_store = ChainStore::new(db_env).unwrap(); - let store = Arc::new(chain_store); - // open the txhashset, creating a new one if necessary - let mut txhashset = txhashset::TxHashSet::open(db_root.clone(), store.clone(), None).unwrap(); - - let keychain = ExtKeychain::from_random_seed().unwrap(); - let key_id1 = keychain.derive_key_id(1).unwrap(); - let key_id2 = keychain.derive_key_id(2).unwrap(); - let key_id3 = keychain.derive_key_id(3).unwrap(); - let key_id4 = keychain.derive_key_id(4).unwrap(); - let key_id5 = keychain.derive_key_id(5).unwrap(); - let key_id6 = keychain.derive_key_id(6).unwrap(); - - // Create a simple block with a single coinbase output - // so we have something to spend. - let prev_header = BlockHeader::default(); - let reward_out = reward::output(&keychain, &key_id1, 0, prev_header.height).unwrap(); - let block = Block::new(&prev_header, vec![], Difficulty::one(), reward_out).unwrap(); - - // Now apply this initial block to the (currently empty) MMRs. - // Note: this results in an output MMR with a single leaf node. - // We need to be careful with pruning while processing the txs below - // as we cannot prune a tree with a single node in it (no sibling or parent). - let mut batch = store.batch().unwrap(); - txhashset::extending(&mut txhashset, &mut batch, |extension| { - extension.apply_block(&block) - }).unwrap(); - - // Make sure we setup the head in the store based on block we just accepted. - let head = Tip::from_block(&block.header); - batch.save_head(&head).unwrap(); - batch.commit().unwrap(); - - let coinbase_reward = 60_000_000_000; - - // tx1 spends the original coinbase output from the block - let tx1 = build::transaction( - vec![ - build::coinbase_input(coinbase_reward, key_id1.clone()), - build::output(100, key_id2.clone()), - build::output(150, key_id3.clone()), - ], - &keychain, - ).unwrap(); - - // tx2 attempts to "double spend" the coinbase output from the block (conflicts - // with tx1) - let tx2 = build::transaction( - vec![ - build::coinbase_input(coinbase_reward, key_id1.clone()), - build::output(100, key_id4.clone()), - ], - &keychain, - ).unwrap(); - - // tx3 spends one output from tx1 - let tx3 = build::transaction( - vec![ - build::input(100, key_id2.clone()), - build::output(90, key_id5.clone()), - ], - &keychain, - ).unwrap(); - - // tx4 spends the other output from tx1 and the output from tx3 - let tx4 = build::transaction( - vec![ - build::input(150, key_id3.clone()), - build::input(90, key_id5.clone()), - build::output(220, key_id6.clone()), - ], - &keychain, - ).unwrap(); - - // Now validate the txs against the txhashset (via a readonly extension). - // Note: we use a single txhashset extension and we can continue to - // apply txs successfully after a failure. - let _ = txhashset::extending_readonly(&mut txhashset, |extension| { - // Note: we pass in an increasing "height" here so we can rollback - // each tx individually as necessary, while maintaining a long lived - // txhashset extension. - assert!(extension.apply_raw_tx(&tx1).is_ok()); - assert!(extension.apply_raw_tx(&tx2).is_err()); - assert!(extension.apply_raw_tx(&tx3).is_ok()); - assert!(extension.apply_raw_tx(&tx4).is_ok()); - Ok(()) - }); -} - #[test] fn test_unexpected_zip() { let db_root = format!(".grin_txhashset_zip"); @@ -180,8 +79,7 @@ fn write_file(db_root: String) { .join("txhashset") .join("kernel") .join("strange0"), - ) - .unwrap(); + ).unwrap(); OpenOptions::new() .create(true) .write(true) @@ -196,8 +94,7 @@ fn write_file(db_root: String) { .join("txhashset") .join("strange_dir") .join("strange2"), - ) - .unwrap(); + ).unwrap(); fs::create_dir( Path::new(&db_root) .join("txhashset") @@ -213,8 +110,7 @@ fn write_file(db_root: String) { .join("strange_dir") .join("strange_subdir") .join("strange3"), - ) - .unwrap(); + ).unwrap(); } fn txhashset_contains_expected_files(dirname: String, path_buf: PathBuf) -> bool { diff --git a/core/src/core/block.rs b/core/src/core/block.rs index 063055779..81eb375ca 100644 --- a/core/src/core/block.rs +++ b/core/src/core/block.rs @@ -26,7 +26,7 @@ use consensus::{self, reward, REWARD}; use core::committed::{self, Committed}; use core::compact_block::{CompactBlock, CompactBlockBody}; use core::hash::{Hash, HashWriter, Hashed, ZERO_HASH}; -use core::verifier_cache::{LruVerifierCache, VerifierCache}; +use core::verifier_cache::VerifierCache; use core::{ transaction, Commitment, Input, KernelFeatures, Output, OutputFeatures, Transaction, TransactionBody, TxKernel, @@ -415,15 +415,8 @@ impl Block { difficulty: Difficulty, reward_output: (Output, TxKernel), ) -> Result { - let verifier_cache = Arc::new(RwLock::new(LruVerifierCache::new())); - let mut block = Block::with_reward( - prev, - txs, - reward_output.0, - reward_output.1, - difficulty, - verifier_cache, - )?; + let mut block = + Block::with_reward(prev, txs, reward_output.0, reward_output.1, difficulty)?; // Now set the pow on the header so block hashing works as expected. { @@ -497,12 +490,10 @@ impl Block { reward_out: Output, reward_kern: TxKernel, difficulty: Difficulty, - verifier: Arc>, ) -> Result { // A block is just a big transaction, aggregate as such. - // Note that aggregation also runs transaction validation - // and duplicate commitment checks. - let mut agg_tx = transaction::aggregate(txs, verifier)?; + let mut agg_tx = transaction::aggregate(txs)?; + // Now add the reward output and reward kernel to the aggregate tx. // At this point the tx is technically invalid, // but the tx body is valid if we account for the reward (i.e. as a block). diff --git a/core/src/core/block_sums.rs b/core/src/core/block_sums.rs index 0d69b1f89..8f0fed4d4 100644 --- a/core/src/core/block_sums.rs +++ b/core/src/core/block_sums.rs @@ -59,7 +59,6 @@ impl Default for BlockSums { } } -/// WAT? /// It's a tuple but we can verify the "full" kernel sums on it. /// This means we can take a previous block_sums, apply a new block to it /// and verify the full kernel sums (full UTXO and kernel sets). diff --git a/core/src/core/transaction.rs b/core/src/core/transaction.rs index bd4e9720e..becd6e223 100644 --- a/core/src/core/transaction.rs +++ b/core/src/core/transaction.rs @@ -404,7 +404,7 @@ impl TransactionBody { } /// Total fee for a TransactionBody is the sum of fees of all kernels. - pub fn fee(&self) -> u64 { + fn fee(&self) -> u64 { self.kernels .iter() .fold(0, |acc, ref x| acc.saturating_add(x.fee)) @@ -748,7 +748,8 @@ impl Transaction { self.body.fee() } - fn overage(&self) -> i64 { + /// Total overage across all kernels. + pub fn overage(&self) -> i64 { self.body.overage() } @@ -822,10 +823,7 @@ pub fn cut_through(inputs: &mut Vec, outputs: &mut Vec) -> Result } /// Aggregate a vec of txs into a multi-kernel tx with cut_through. -pub fn aggregate( - mut txs: Vec, - verifier: Arc>, -) -> Result { +pub fn aggregate(mut txs: Vec) -> Result { // convenience short-circuiting if txs.is_empty() { return Ok(Transaction::empty()); @@ -867,22 +865,12 @@ pub fn aggregate( // * sum of all kernel offsets let tx = Transaction::new(inputs, outputs, kernels).with_offset(total_kernel_offset); - // Now validate the aggregate tx to ensure we have not built something invalid. - // The resulting tx could be invalid for a variety of reasons - - // * tx too large (too many inputs|outputs|kernels) - // * cut-through may have invalidated the sums - tx.validate(verifier)?; - Ok(tx) } /// Attempt to deaggregate a multi-kernel transaction based on multiple /// transactions -pub fn deaggregate( - mk_tx: Transaction, - txs: Vec, - verifier: Arc>, -) -> Result { +pub fn deaggregate(mk_tx: Transaction, txs: Vec) -> Result { let mut inputs: Vec = vec![]; let mut outputs: Vec = vec![]; let mut kernels: Vec = vec![]; @@ -891,7 +879,7 @@ pub fn deaggregate( // transaction let mut kernel_offsets = vec![]; - let tx = aggregate(txs, verifier.clone())?; + let tx = aggregate(txs)?; for mk_input in mk_tx.body.inputs { if !tx.body.inputs.contains(&mk_input) && !inputs.contains(&mk_input) { @@ -941,9 +929,6 @@ pub fn deaggregate( // Build a new tx from the above data. let tx = Transaction::new(inputs, outputs, kernels).with_offset(total_kernel_offset); - - // Now validate the resulting tx to ensure we have not built something invalid. - tx.validate(verifier)?; Ok(tx) } diff --git a/core/tests/core.rs b/core/tests/core.rs index 13bfae9d2..da4ed5a75 100644 --- a/core/tests/core.rs +++ b/core/tests/core.rs @@ -137,7 +137,7 @@ fn transaction_cut_through() { let vc = verifier_cache(); // now build a "cut_through" tx from tx1 and tx2 - let tx3 = aggregate(vec![tx1, tx2], vc.clone()).unwrap(); + let tx3 = aggregate(vec![tx1, tx2]).unwrap(); assert!(tx3.validate(vc.clone()).is_ok()); } @@ -157,22 +157,19 @@ fn multi_kernel_transaction_deaggregation() { assert!(tx3.validate(vc.clone()).is_ok()); assert!(tx4.validate(vc.clone()).is_ok()); - let tx1234 = aggregate( - vec![tx1.clone(), tx2.clone(), tx3.clone(), tx4.clone()], - vc.clone(), - ).unwrap(); - let tx12 = aggregate(vec![tx1.clone(), tx2.clone()], vc.clone()).unwrap(); - let tx34 = aggregate(vec![tx3.clone(), tx4.clone()], vc.clone()).unwrap(); + let tx1234 = aggregate(vec![tx1.clone(), tx2.clone(), tx3.clone(), tx4.clone()]).unwrap(); + let tx12 = aggregate(vec![tx1.clone(), tx2.clone()]).unwrap(); + let tx34 = aggregate(vec![tx3.clone(), tx4.clone()]).unwrap(); assert!(tx1234.validate(vc.clone()).is_ok()); assert!(tx12.validate(vc.clone()).is_ok()); assert!(tx34.validate(vc.clone()).is_ok()); - let deaggregated_tx34 = deaggregate(tx1234.clone(), vec![tx12.clone()], vc.clone()).unwrap(); + let deaggregated_tx34 = deaggregate(tx1234.clone(), vec![tx12.clone()]).unwrap(); assert!(deaggregated_tx34.validate(vc.clone()).is_ok()); assert_eq!(tx34, deaggregated_tx34); - let deaggregated_tx12 = deaggregate(tx1234.clone(), vec![tx34.clone()], vc.clone()).unwrap(); + let deaggregated_tx12 = deaggregate(tx1234.clone(), vec![tx34.clone()]).unwrap(); assert!(deaggregated_tx12.validate(vc.clone()).is_ok()); assert_eq!(tx12, deaggregated_tx12); @@ -190,13 +187,13 @@ fn multi_kernel_transaction_deaggregation_2() { assert!(tx2.validate(vc.clone()).is_ok()); assert!(tx3.validate(vc.clone()).is_ok()); - let tx123 = aggregate(vec![tx1.clone(), tx2.clone(), tx3.clone()], vc.clone()).unwrap(); - let tx12 = aggregate(vec![tx1.clone(), tx2.clone()], vc.clone()).unwrap(); + let tx123 = aggregate(vec![tx1.clone(), tx2.clone(), tx3.clone()]).unwrap(); + let tx12 = aggregate(vec![tx1.clone(), tx2.clone()]).unwrap(); assert!(tx123.validate(vc.clone()).is_ok()); assert!(tx12.validate(vc.clone()).is_ok()); - let deaggregated_tx3 = deaggregate(tx123.clone(), vec![tx12.clone()], vc.clone()).unwrap(); + let deaggregated_tx3 = deaggregate(tx123.clone(), vec![tx12.clone()]).unwrap(); assert!(deaggregated_tx3.validate(vc.clone()).is_ok()); assert_eq!(tx3, deaggregated_tx3); } @@ -213,14 +210,14 @@ fn multi_kernel_transaction_deaggregation_3() { assert!(tx2.validate(vc.clone()).is_ok()); assert!(tx3.validate(vc.clone()).is_ok()); - let tx123 = aggregate(vec![tx1.clone(), tx2.clone(), tx3.clone()], vc.clone()).unwrap(); - let tx13 = aggregate(vec![tx1.clone(), tx3.clone()], vc.clone()).unwrap(); - let tx2 = aggregate(vec![tx2.clone()], vc.clone()).unwrap(); + let tx123 = aggregate(vec![tx1.clone(), tx2.clone(), tx3.clone()]).unwrap(); + let tx13 = aggregate(vec![tx1.clone(), tx3.clone()]).unwrap(); + let tx2 = aggregate(vec![tx2.clone()]).unwrap(); assert!(tx123.validate(vc.clone()).is_ok()); assert!(tx2.validate(vc.clone()).is_ok()); - let deaggregated_tx13 = deaggregate(tx123.clone(), vec![tx2.clone()], vc.clone()).unwrap(); + let deaggregated_tx13 = deaggregate(tx123.clone(), vec![tx2.clone()]).unwrap(); assert!(deaggregated_tx13.validate(vc.clone()).is_ok()); assert_eq!(tx13, deaggregated_tx13); } @@ -241,22 +238,18 @@ fn multi_kernel_transaction_deaggregation_4() { assert!(tx4.validate(vc.clone()).is_ok()); assert!(tx5.validate(vc.clone()).is_ok()); - let tx12345 = aggregate( - vec![ - tx1.clone(), - tx2.clone(), - tx3.clone(), - tx4.clone(), - tx5.clone(), - ], - vc.clone(), - ).unwrap(); + let tx12345 = aggregate(vec![ + tx1.clone(), + tx2.clone(), + tx3.clone(), + tx4.clone(), + tx5.clone(), + ]).unwrap(); assert!(tx12345.validate(vc.clone()).is_ok()); let deaggregated_tx5 = deaggregate( tx12345.clone(), vec![tx1.clone(), tx2.clone(), tx3.clone(), tx4.clone()], - vc.clone(), ).unwrap(); assert!(deaggregated_tx5.validate(vc.clone()).is_ok()); assert_eq!(tx5, deaggregated_tx5); @@ -278,26 +271,19 @@ fn multi_kernel_transaction_deaggregation_5() { assert!(tx4.validate(vc.clone()).is_ok()); assert!(tx5.validate(vc.clone()).is_ok()); - let tx12345 = aggregate( - vec![ - tx1.clone(), - tx2.clone(), - tx3.clone(), - tx4.clone(), - tx5.clone(), - ], - vc.clone(), - ).unwrap(); - let tx12 = aggregate(vec![tx1.clone(), tx2.clone()], vc.clone()).unwrap(); - let tx34 = aggregate(vec![tx3.clone(), tx4.clone()], vc.clone()).unwrap(); + let tx12345 = aggregate(vec![ + tx1.clone(), + tx2.clone(), + tx3.clone(), + tx4.clone(), + tx5.clone(), + ]).unwrap(); + let tx12 = aggregate(vec![tx1.clone(), tx2.clone()]).unwrap(); + let tx34 = aggregate(vec![tx3.clone(), tx4.clone()]).unwrap(); assert!(tx12345.validate(vc.clone()).is_ok()); - let deaggregated_tx5 = deaggregate( - tx12345.clone(), - vec![tx12.clone(), tx34.clone()], - vc.clone(), - ).unwrap(); + let deaggregated_tx5 = deaggregate(tx12345.clone(), vec![tx12.clone(), tx34.clone()]).unwrap(); assert!(deaggregated_tx5.validate(vc.clone()).is_ok()); assert_eq!(tx5, deaggregated_tx5); } @@ -314,16 +300,16 @@ fn basic_transaction_deaggregation() { assert!(tx2.validate(vc.clone()).is_ok()); // now build a "cut_through" tx from tx1 and tx2 - let tx3 = aggregate(vec![tx1.clone(), tx2.clone()], vc.clone()).unwrap(); + let tx3 = aggregate(vec![tx1.clone(), tx2.clone()]).unwrap(); assert!(tx3.validate(vc.clone()).is_ok()); - let deaggregated_tx1 = deaggregate(tx3.clone(), vec![tx2.clone()], vc.clone()).unwrap(); + let deaggregated_tx1 = deaggregate(tx3.clone(), vec![tx2.clone()]).unwrap(); assert!(deaggregated_tx1.validate(vc.clone()).is_ok()); assert_eq!(tx1, deaggregated_tx1); - let deaggregated_tx2 = deaggregate(tx3.clone(), vec![tx1.clone()], vc.clone()).unwrap(); + let deaggregated_tx2 = deaggregate(tx3.clone(), vec![tx1.clone()]).unwrap(); assert!(deaggregated_tx2.validate(vc.clone()).is_ok()); assert_eq!(tx2, deaggregated_tx2); diff --git a/keychain/src/types.rs b/keychain/src/types.rs index 2feb1ef46..58cd710e6 100644 --- a/keychain/src/types.rs +++ b/keychain/src/types.rs @@ -14,6 +14,7 @@ use rand::thread_rng; use std::cmp::min; +use std::ops::Add; /// Keychain trait and its main supporting types. The Identifier is a /// semi-opaque structure (just bytes) to track keys within the Keychain. /// BlindingFactor is a useful wrapper around a private key to help with @@ -28,6 +29,7 @@ use util::secp::constants::SECRET_KEY_SIZE; use util::secp::key::{PublicKey, SecretKey}; use util::secp::pedersen::Commitment; use util::secp::{self, Message, Secp256k1, Signature}; +use util::static_secp_instance; // Size of an identifier in bytes pub const IDENTIFIER_SIZE: usize = 10; @@ -177,6 +179,32 @@ impl AsRef<[u8]> for BlindingFactor { } } +impl Add for BlindingFactor { + type Output = Result; + + // Convenient (and robust) way to add two blinding_factors together. + // Handles "zero" blinding_factors correctly. + // + // let bf = (bf1 + bf2)?; + // + fn add(self, other: BlindingFactor) -> Self::Output { + let secp = static_secp_instance(); + let secp = secp.lock().unwrap(); + let keys = vec![self, other] + .into_iter() + .filter(|x| *x != BlindingFactor::zero()) + .filter_map(|x| x.secret_key(&secp).ok()) + .collect::>(); + + if keys.is_empty() { + Ok(BlindingFactor::zero()) + } else { + let sum = secp.blind_sum(keys, vec![])?; + Ok(BlindingFactor::from_secret_key(sum)) + } + } +} + impl BlindingFactor { pub fn from_secret_key(skey: secp::key::SecretKey) -> BlindingFactor { BlindingFactor::from_slice(&skey.as_ref()) diff --git a/pool/src/pool.rs b/pool/src/pool.rs index eed202079..64ebde2e7 100644 --- a/pool/src/pool.rs +++ b/pool/src/pool.rs @@ -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 { - let header = self.blockchain.chain_head().unwrap(); - + pub fn prepare_mineable_transactions(&self) -> Result, 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 { @@ -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, extra_tx: Option, - block_hash: &Hash, + header: &BlockHeader, ) -> Result, PoolError> { - let entries = &mut self - .entries - .iter_mut() - .filter(|x| x.state == from_state) - .collect::>(); + 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 = 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 { + self.entries + .iter() + .filter(|x| x.state == state) + .map(|x| x.tx.clone()) + .collect::>() + } - // 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, 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, - 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 { + 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, + extra_tx: Option, + header: &BlockHeader, + block_sums: &BlockSums, + ) -> Result, 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, + header: &BlockHeader, + ) -> Result { + // 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, - 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(()) } diff --git a/pool/src/transaction_pool.rs b/pool/src/transaction_pool.rs index ab1b3d97e..8ede35797 100644 --- a/pool/src/transaction_pool.rs +++ b/pool/src/transaction_pool.rs @@ -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 { + pub fn prepare_mineable_transactions(&self) -> Result, PoolError> { self.txpool.prepare_mineable_transactions() } } diff --git a/pool/src/types.rs b/pool/src/types.rs index b17abb46d..fa5c441f7 100644 --- a/pool/src/types.rs +++ b/pool/src/types.rs @@ -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 for PoolError { } } +impl From for PoolError { + fn from(e: keychain::Error) -> PoolError { + PoolError::Keychain(e) + } +} + +impl From 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, - pre_tx: Option, - block_hash: &Hash, - ) -> Result, 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; + + fn get_block_header(&self, hash: &Hash) -> Result; + fn get_block_sums(&self, hash: &Hash) -> Result; } /// Bridge between the transaction pool and the rest of the system. Handles diff --git a/pool/tests/block_building.rs b/pool/tests/block_building.rs index 41c1a9d89..b9b2ffd59 100644 --- a/pool/tests/block_building.rs +++ b/pool/tests/block_building.rs @@ -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, 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. diff --git a/pool/tests/block_reconciliation.rs b/pool/tests/block_reconciliation.rs index d7c68c83e..c32a8ac38 100644 --- a/pool/tests/block_reconciliation.rs +++ b/pool/tests/block_reconciliation.rs @@ -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 }; diff --git a/pool/tests/coinbase_maturity.rs b/pool/tests/coinbase_maturity.rs index ccc1cd2ac..5c155ffd0 100644 --- a/pool/tests/coinbase_maturity.rs +++ b/pool/tests/coinbase_maturity.rs @@ -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, - _pre_tx: Option, - _block_hash: &Hash, - ) -> Result, PoolError> { + fn get_block_header(&self, _hash: &Hash) -> Result { + unimplemented!(); + } + + fn get_block_sums(&self, _hash: &Hash) -> Result { + 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."), } diff --git a/pool/tests/common/mod.rs b/pool/tests/common/mod.rs index 19b29cdd3..385532547 100644 --- a/pool/tests/common/mod.rs +++ b/pool/tests/common/mod.rs @@ -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>, pub store: Arc, + pub utxo: Arc>>, } 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, - pre_tx: Option, - block_hash: &Hash, - ) -> Result, 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 { + 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 { + 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. diff --git a/pool/tests/transaction_pool.rs b/pool/tests/transaction_pool.rs index 7eebb3311..87ae03058 100644 --- a/pool/tests/transaction_pool.rs +++ b/pool/tests/transaction_pool.rs @@ -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() ); } diff --git a/servers/src/common/adapters.rs b/servers/src/common/adapters.rs index 64c1c82d0..21a111ca6 100644 --- a/servers/src/common/adapters.rs +++ b/servers/src/common/adapters.rs @@ -27,7 +27,7 @@ use common::types::{self, ChainValidationMode, ServerConfig, SyncState, SyncStat use core::core::hash::{Hash, Hashed}; use core::core::transaction::Transaction; use core::core::verifier_cache::VerifierCache; -use core::core::{BlockHeader, CompactBlock}; +use core::core::{BlockHeader, BlockSums, CompactBlock}; use core::pow::Difficulty; use core::{core, global}; use p2p; @@ -46,7 +46,7 @@ fn wo(weak_one: &OneTime>) -> Arc { w(weak_one.borrow().deref()) } -/// Implementation of the NetAdapter for the blockchain. Gets notified when new +/// Implementation of the NetAdapter for the . Gets notified when new /// blocks and transactions are received and forwards to the chain and pool /// implementations. pub struct NetToChainAdapter { @@ -80,7 +80,7 @@ impl p2p::ChainAdapter for NetToChainAdapter { }; let tx_hash = tx.hash(); - let block_hash = w(&self.chain).head_header().unwrap().hash(); + let header = w(&self.chain).head_header().unwrap(); debug!( LOGGER, @@ -93,7 +93,7 @@ impl p2p::ChainAdapter for NetToChainAdapter { let res = { let mut tx_pool = self.tx_pool.write().unwrap(); - tx_pool.add_to_pool(source, tx, stem, &block_hash) + tx_pool.add_to_pool(source, tx, stem, &header) }; if let Err(e) = res { @@ -617,7 +617,7 @@ impl NetToChainAdapter { } /// Implementation of the ChainAdapter for the network. Gets notified when the -/// blockchain accepted a new block, asking the pool to update its state and +/// accepted a new block, asking the pool to update its state and /// the network to broadcast the block pub struct ChainToPoolAndNetAdapter { sync_state: Arc, @@ -708,7 +708,7 @@ impl PoolToNetAdapter { } } -/// Implements the view of the blockchain required by the TransactionPool to +/// Implements the view of the required by the TransactionPool to /// operate. Mostly needed to break any direct lifecycle or implementation /// dependency between the pool and the chain. #[derive(Clone)] @@ -732,25 +732,27 @@ impl PoolToChainAdapter { impl pool::BlockChain for PoolToChainAdapter { fn chain_head(&self) -> Result { - wo(&self.chain).head_header().map_err(|e| { - pool::PoolError::Other(format!( - "Chain adapter failed to retrieve chain head: {:?}", - e - )) - }) + wo(&self.chain) + .head_header() + .map_err(|_| pool::PoolError::Other(format!("failed to get head_header"))) } - fn validate_raw_txs( - &self, - txs: Vec, - pre_tx: Option, - block_hash: &Hash, - ) -> Result<(Vec), pool::PoolError> { + fn get_block_header(&self, hash: &Hash) -> Result { wo(&self.chain) - .validate_raw_txs(txs, pre_tx, block_hash) - .map_err(|e| { - pool::PoolError::Other(format!("Chain adapter failed to validate_raw_txs: {:?}", e)) - }) + .get_block_header(hash) + .map_err(|_| pool::PoolError::Other(format!("failed to get block_header"))) + } + + fn get_block_sums(&self, hash: &Hash) -> Result { + wo(&self.chain) + .get_block_sums(hash) + .map_err(|_| pool::PoolError::Other(format!("failed to get block_sums"))) + } + + fn validate_tx(&self, tx: &Transaction, header: &BlockHeader) -> Result<(), pool::PoolError> { + wo(&self.chain) + .validate_tx(tx, header) + .map_err(|_| pool::PoolError::Other(format!("failed to validate tx"))) } fn verify_coinbase_maturity(&self, tx: &Transaction) -> Result<(), pool::PoolError> { diff --git a/servers/src/grin/dandelion_monitor.rs b/servers/src/grin/dandelion_monitor.rs index 0d94a7d39..d804d49bf 100644 --- a/servers/src/grin/dandelion_monitor.rs +++ b/servers/src/grin/dandelion_monitor.rs @@ -93,12 +93,15 @@ fn process_stem_phase( let header = tx_pool.blockchain.chain_head()?; let txpool_tx = tx_pool.txpool.aggregate_transaction()?; - let stem_txs = tx_pool.stempool.select_valid_transactions( - PoolEntryState::ToStem, - PoolEntryState::Stemmed, - txpool_tx, - &header.hash(), - )?; + let stem_txs = tx_pool + .stempool + .get_transactions_in_state(PoolEntryState::ToStem); + let stem_txs = tx_pool + .stempool + .select_valid_transactions(stem_txs, txpool_tx, &header)?; + tx_pool + .stempool + .transition_to_state(&stem_txs, PoolEntryState::Stemmed); if stem_txs.len() > 0 { debug!( @@ -107,7 +110,8 @@ fn process_stem_phase( stem_txs.len() ); - let agg_tx = transaction::aggregate(stem_txs, verifier_cache.clone())?; + let agg_tx = transaction::aggregate(stem_txs)?; + agg_tx.validate(verifier_cache.clone())?; let res = tx_pool.adapter.stem_tx_accepted(&agg_tx); if res.is_err() { @@ -121,7 +125,7 @@ fn process_stem_phase( identifier: "?.?.?.?".to_string(), }; - tx_pool.add_to_pool(src, agg_tx, false, &header.hash())?; + tx_pool.add_to_pool(src, agg_tx, false, &header)?; } } Ok(()) @@ -136,12 +140,15 @@ fn process_fluff_phase( let header = tx_pool.blockchain.chain_head()?; let txpool_tx = tx_pool.txpool.aggregate_transaction()?; - let stem_txs = tx_pool.stempool.select_valid_transactions( - PoolEntryState::ToFluff, - PoolEntryState::Fluffed, - txpool_tx, - &header.hash(), - )?; + let stem_txs = tx_pool + .stempool + .get_transactions_in_state(PoolEntryState::ToFluff); + let stem_txs = tx_pool + .stempool + .select_valid_transactions(stem_txs, txpool_tx, &header)?; + tx_pool + .stempool + .transition_to_state(&stem_txs, PoolEntryState::Fluffed); if stem_txs.len() > 0 { debug!( @@ -150,14 +157,15 @@ fn process_fluff_phase( stem_txs.len() ); - let agg_tx = transaction::aggregate(stem_txs, verifier_cache.clone())?; + let agg_tx = transaction::aggregate(stem_txs)?; + agg_tx.validate(verifier_cache.clone())?; let src = TxSource { debug_name: "fluff".to_string(), identifier: "?.?.?.?".to_string(), }; - tx_pool.add_to_pool(src, agg_tx, false, &header.hash())?; + tx_pool.add_to_pool(src, agg_tx, false, &header)?; } Ok(()) } @@ -238,7 +246,7 @@ fn process_expired_entries( debug_name: "embargo_expired".to_string(), identifier: "?.?.?.?".to_string(), }; - match tx_pool.add_to_pool(src, entry.tx, false, &header.hash()) { + match tx_pool.add_to_pool(src, entry.tx, false, &header) { Ok(_) => debug!( LOGGER, "dand_mon: embargo expired, fluffed tx successfully." diff --git a/servers/src/mining/mine_block.rs b/servers/src/mining/mine_block.rs index c7509954e..40fb4a6da 100644 --- a/servers/src/mining/mine_block.rs +++ b/servers/src/mining/mine_block.rs @@ -109,7 +109,12 @@ fn build_block( let difficulty = consensus::next_difficulty(diff_iter).unwrap(); // extract current transaction from the pool - let txs = tx_pool.read().unwrap().prepare_mineable_transactions(); + // TODO - we have a lot of unwrap() going on in this fn... + let txs = tx_pool + .read() + .unwrap() + .prepare_mineable_transactions() + .unwrap(); // build the coinbase and the block itself let fees = txs.iter().map(|tx| tx.fee()).sum(); @@ -121,14 +126,7 @@ fn build_block( }; let (output, kernel, block_fees) = get_coinbase(wallet_listener_url, block_fees)?; - let mut b = core::Block::with_reward( - &head, - txs, - output, - kernel, - difficulty.clone(), - verifier_cache.clone(), - )?; + let mut b = core::Block::with_reward(&head, txs, output, kernel, difficulty.clone())?; // making sure we're not spending time mining a useless block b.validate(