diff --git a/Cargo.lock b/Cargo.lock index d6878be97a..1c21024d4c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -351,7 +351,6 @@ dependencies = [ "lru-cache 0.1.0 (git+https://github.com/nervosnetwork/lru-cache)", "numext-fixed-hash 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "numext-fixed-uint 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", "stop-handler 0.7.0-pre", "tempfile 3.0.7 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/chain/Cargo.toml b/chain/Cargo.toml index f6ab75bd31..8126ab43f4 100644 --- a/chain/Cargo.toml +++ b/chain/Cargo.toml @@ -27,4 +27,3 @@ failure = "0.1.5" [dev-dependencies] env_logger = "0.6" tempfile = "3.0" -rand = "0.6" diff --git a/chain/src/chain.rs b/chain/src/chain.rs index 1d9d00eb7e..d91be481dc 100644 --- a/chain/src/chain.rs +++ b/chain/src/chain.rs @@ -1,8 +1,10 @@ use ckb_core::block::Block; -use ckb_core::cell::{resolve_transaction, CellProvider, CellStatus, ResolvedTransaction}; +use ckb_core::cell::{ + resolve_transaction, BlockCellProvider, OverlayCellProvider, ResolvedTransaction, +}; use ckb_core::extras::BlockExt; use ckb_core::service::{Request, DEFAULT_CHANNEL_SIZE, SIGNAL_CHANNEL_SIZE}; -use ckb_core::transaction::{OutPoint, ProposalShortId}; +use ckb_core::transaction::ProposalShortId; use ckb_core::BlockNumber; use ckb_db::batch::Batch; use ckb_notify::NotifyController; @@ -16,7 +18,7 @@ use ckb_verification::{BlockVerifier, TransactionsVerifier, Verifier}; use crossbeam_channel::{self, select, Receiver, Sender}; use failure::Error as FailureError; use faketime::unix_time_as_millis; -use fnv::{FnvHashMap, FnvHashSet}; +use fnv::FnvHashSet; use log::{self, debug, error, log_enabled}; use numext_fixed_hash::H256; use numext_fixed_uint::U256; @@ -196,7 +198,7 @@ impl ChainService { ); if parent_ext.txs_verified == Some(false) { - Err(SharedError::InvalidTransaction)?; + Err(SharedError::InvalidParentBlock)?; } let ext = BlockExt { @@ -425,24 +427,7 @@ impl ChainService { fork: &mut ForkChanges, chain_state: &mut ChainState, ) -> Result { - let skip_verify = !self.verification; - - let mut old_inputs = FnvHashSet::default(); - let mut old_outputs = FnvHashSet::default(); - let mut new_inputs = FnvHashSet::default(); - let mut new_outputs = FnvHashMap::default(); - - let push_new = |b: &Block, - new_inputs: &mut FnvHashSet, - new_outputs: &mut FnvHashMap| { - for tx in b.commit_transactions() { - let input_pts = tx.input_pts(); - let tx_hash = tx.hash(); - let output_len = tx.outputs().len(); - new_inputs.extend(input_pts); - new_outputs.insert(tx_hash, output_len); - } - }; + let mut cell_set_diff = CellSetDiff::default(); let attached_blocks_iter = fork.attached_blocks().iter().rev(); let detached_blocks_iter = fork.detached_blocks().iter().rev(); @@ -451,23 +436,17 @@ impl ChainService { let verified_len = attached_blocks_len - fork.dirty_exts.len(); for b in detached_blocks_iter { - for tx in b.commit_transactions() { - let input_pts = tx.input_pts(); - let tx_hash = tx.hash(); - - old_inputs.extend(input_pts); - old_outputs.insert(tx_hash); - } + cell_set_diff.push_old(b); } - for b in attached_blocks_iter.clone().take(verified_len) { - push_new(b, &mut new_inputs, &mut new_outputs); + for b in attached_blocks_iter.take(verified_len) { + cell_set_diff.push_new(b); } // The verify function let txs_verifier = TransactionsVerifier::new(self.shared.consensus().max_block_cycles()); - let mut found_error = false; + let mut found_error = None; // verify transaction for (ext, b) in fork .dirty_exts @@ -475,63 +454,42 @@ impl ChainService { .zip(fork.attached_blocks.iter()) .rev() { - let cell_resolver = |op: &OutPoint| { - self.shared.cell_at(op, |op| { - if new_inputs.contains(op) { - Some(true) - } else if let Some(x) = new_outputs.get(&op.hash) { - if op.index < (*x as u32) { - Some(false) - } else { - Some(true) + if self.verification { + if found_error.is_none() { + let mut seen_inputs = FnvHashSet::default(); + + // TODO Q impl CellProvider for store, use chain_state as CellProvider here directly. + let cell_set_cp = OverlayCellProvider::new(chain_state.cell_set(), chain_state); + let cell_set_diff_cp = OverlayCellProvider::new(&cell_set_diff, &cell_set_cp); + let block_cp = BlockCellProvider::new(b); + let cell_provider = OverlayCellProvider::new(&block_cp, &cell_set_diff_cp); + + let resolved: Vec = b + .commit_transactions() + .iter() + .map(|x| resolve_transaction(x, &mut seen_inputs, &cell_provider)) + .collect(); + + match txs_verifier.verify( + chain_state.mut_txs_verify_cache(), + &resolved, + self.shared.block_reward(b.header().number()), + ) { + Ok(_) => { + cell_set_diff.push_new(b); + ext.txs_verified = Some(true); } - } else if old_outputs.contains(&op.hash) { - None - } else { - chain_state - .is_dead(op) - .map(|x| x && !old_inputs.contains(op)) - } - }) - }; - - let mut output_indexs = FnvHashMap::default(); - let mut seen_inputs = FnvHashSet::default(); - - for (i, tx) in b.commit_transactions().iter().enumerate() { - output_indexs.insert(tx.hash(), i); - } - - // cellbase verified - let resolved: Vec = b - .commit_transactions() - .iter() - .skip(1) - .map(|x| { - resolve_transaction(x, &mut seen_inputs, |o| { - if let Some(i) = output_indexs.get(&o.hash) { - match b.commit_transactions()[*i].outputs().get(o.index as usize) { - Some(x) => CellStatus::Live(x.clone()), - None => CellStatus::Unknown, - } - } else { - cell_resolver(o) + Err(err) => { + found_error = Some(err); + ext.txs_verified = Some(false); } - }) - }) - .collect(); - - if !found_error - || skip_verify - || txs_verifier - .verify(chain_state.mut_txs_verify_cache(), &resolved) - .is_ok() - { - push_new(b, &mut new_inputs, &mut new_outputs); - ext.txs_verified = Some(true); + } + } else { + ext.txs_verified = Some(false); + } } else { - found_error = true; - ext.txs_verified = Some(false); + cell_set_diff.push_new(b); + ext.txs_verified = Some(true); } } @@ -547,21 +505,11 @@ impl ChainService { .insert_block_ext(batch, &b.header().hash(), ext); } - if found_error { - Err(SharedError::InvalidTransaction)?; + if let Some(err) = found_error { + Err(SharedError::InvalidTransaction(err.to_string()))? + } else { + Ok(cell_set_diff) } - - let old_inputs: Vec = old_inputs.into_iter().collect(); - let old_outputs: Vec = old_outputs.into_iter().collect(); - let new_inputs: Vec = new_inputs.into_iter().collect(); - let new_outputs: Vec<(H256, usize)> = new_outputs.into_iter().collect(); - - Ok(CellSetDiff { - old_inputs, - old_outputs, - new_inputs, - new_outputs, - }) } // TODO: beatify diff --git a/chain/src/tests/basic.rs b/chain/src/tests/basic.rs index 4f59999211..e8c3ab3ab6 100644 --- a/chain/src/tests/basic.rs +++ b/chain/src/tests/basic.rs @@ -5,6 +5,7 @@ use ckb_core::block::BlockBuilder; use ckb_core::cell::CellProvider; use ckb_core::header::HeaderBuilder; use ckb_core::transaction::{CellInput, CellOutput, OutPoint, TransactionBuilder}; +use ckb_shared::error::SharedError; use ckb_traits::ChainProvider; use numext_fixed_hash::H256; use numext_fixed_uint::U256; @@ -32,7 +33,7 @@ fn test_genesis_transaction_spend() { .with_header_builder(HeaderBuilder::default().difficulty(U256::from(1000u64))); let consensus = Consensus::default().set_genesis_block(genesis_block); - let (chain_controller, shared) = start_chain(Some(consensus)); + let (chain_controller, shared) = start_chain(Some(consensus), false); let end = 21; @@ -40,9 +41,15 @@ fn test_genesis_transaction_spend() { let mut parent = shared.block_header(&shared.block_hash(0).unwrap()).unwrap(); for i in 1..end { let difficulty = parent.difficulty().clone(); - let tx = create_transaction(root_hash); + let tx = create_transaction(root_hash, i as u8); root_hash = tx.hash().clone(); - let new_block = gen_block(&parent, i, difficulty + U256::from(1u64), vec![tx], vec![]); + let new_block = gen_block( + &parent, + difficulty + U256::from(1u64), + vec![tx], + vec![], + vec![], + ); blocks1.push(new_block.clone()); parent = new_block.header().clone(); } @@ -54,6 +61,237 @@ fn test_genesis_transaction_spend() { } } +#[test] +fn test_transaction_spend_in_same_block() { + let (chain_controller, shared) = start_chain(None, true); + let mut chain: Vec = Vec::new(); + let mut parent = shared.block_header(&shared.block_hash(0).unwrap()).unwrap(); + { + let difficulty = parent.difficulty().clone(); + let new_block = gen_block( + &parent, + difficulty + U256::from(100u64), + vec![], + vec![], + vec![], + ); + parent = new_block.header().clone(); + chain.push(new_block); + } + + let last_cell_base = &chain.last().unwrap().commit_transactions()[0]; + let tx1 = create_transaction(last_cell_base.hash(), 1); + let tx2 = create_transaction(tx1.hash(), 2); + let txs = vec![tx1, tx2]; + // proposal txs + { + let difficulty = parent.difficulty().clone(); + let new_block = gen_block( + &parent, + difficulty + U256::from(100u64), + vec![], + txs.clone(), + vec![], + ); + parent = new_block.header().clone(); + chain.push(new_block); + } + // empty N+1 block + { + let difficulty = parent.difficulty().clone(); + let new_block = gen_block( + &parent, + difficulty + U256::from(100u64), + vec![], + vec![], + vec![], + ); + parent = new_block.header().clone(); + chain.push(new_block); + } + // commit txs in N+2 block + { + let difficulty = parent.difficulty().clone(); + let new_block = gen_block( + &parent, + difficulty + U256::from(100u64), + txs.clone(), + vec![], + vec![], + ); + chain.push(new_block); + } + for block in &chain { + chain_controller + .process_block(Arc::new(block.clone())) + .expect("process block ok"); + } +} + +#[test] +fn test_transaction_conflict_in_same_block() { + let (chain_controller, shared) = start_chain(None, true); + let mut chain: Vec = Vec::new(); + let mut parent = shared.block_header(&shared.block_hash(0).unwrap()).unwrap(); + { + let difficulty = parent.difficulty().clone(); + let new_block = gen_block( + &parent, + difficulty + U256::from(100u64), + vec![], + vec![], + vec![], + ); + parent = new_block.header().clone(); + chain.push(new_block); + } + + let last_cell_base = &chain.last().unwrap().commit_transactions()[0]; + let tx1 = create_transaction(last_cell_base.hash(), 1); + let tx2 = create_transaction(tx1.hash(), 2); + let tx3 = create_transaction(tx1.hash(), 3); + let txs = vec![tx1, tx2, tx3]; + // proposal txs + { + let difficulty = parent.difficulty().clone(); + let new_block = gen_block( + &parent, + difficulty + U256::from(100u64), + vec![], + txs.clone(), + vec![], + ); + parent = new_block.header().clone(); + chain.push(new_block); + } + // empty N+1 block + { + let difficulty = parent.difficulty().clone(); + let new_block = gen_block( + &parent, + difficulty + U256::from(100u64), + vec![], + vec![], + vec![], + ); + parent = new_block.header().clone(); + chain.push(new_block); + } + // commit txs in N+2 block + { + let difficulty = parent.difficulty().clone(); + let new_block = gen_block( + &parent, + difficulty + U256::from(100u64), + txs.clone(), + vec![], + vec![], + ); + chain.push(new_block); + } + for block in chain.iter().take(3) { + chain_controller + .process_block(Arc::new(block.clone())) + .expect("process block ok"); + } + assert_eq!( + SharedError::InvalidTransaction("Transactions((2, Conflict))".to_string()), + chain_controller + .process_block(Arc::new(chain[3].clone())) + .unwrap_err() + .downcast() + .unwrap() + ); +} + +#[test] +fn test_transaction_conflict_in_different_blocks() { + let (chain_controller, shared) = start_chain(None, true); + let mut chain: Vec = Vec::new(); + let mut parent = shared.block_header(&shared.block_hash(0).unwrap()).unwrap(); + { + let difficulty = parent.difficulty().clone(); + let new_block = gen_block( + &parent, + difficulty + U256::from(100u64), + vec![], + vec![], + vec![], + ); + parent = new_block.header().clone(); + chain.push(new_block); + } + + let last_cell_base = &chain.last().unwrap().commit_transactions()[0]; + let tx1 = create_transaction(last_cell_base.hash(), 1); + let tx2 = create_transaction(tx1.hash(), 2); + let tx3 = create_transaction(tx1.hash(), 3); + // proposal txs + { + let difficulty = parent.difficulty().clone(); + let new_block = gen_block( + &parent, + difficulty + U256::from(100u64), + vec![], + vec![tx1.clone(), tx2.clone(), tx3.clone()], + vec![], + ); + parent = new_block.header().clone(); + chain.push(new_block); + } + // empty N+1 block + { + let difficulty = parent.difficulty().clone(); + let new_block = gen_block( + &parent, + difficulty + U256::from(100u64), + vec![], + vec![], + vec![], + ); + parent = new_block.header().clone(); + chain.push(new_block); + } + // commit tx1 and tx2 in N+2 block + { + let difficulty = parent.difficulty().clone(); + let new_block = gen_block( + &parent, + difficulty + U256::from(100u64), + vec![tx1.clone(), tx2.clone()], + vec![], + vec![], + ); + parent = new_block.header().clone(); + chain.push(new_block); + } + // commit tx3 in N+3 block + { + let difficulty = parent.difficulty().clone(); + let new_block = gen_block( + &parent, + difficulty + U256::from(100u64), + vec![tx3.clone()], + vec![], + vec![], + ); + chain.push(new_block); + } + for block in chain.iter().take(4) { + chain_controller + .process_block(Arc::new(block.clone())) + .expect("process block ok"); + } + assert_eq!( + SharedError::InvalidTransaction("Transactions((0, Conflict))".to_string()), + chain_controller + .process_block(Arc::new(chain[4].clone())) + .unwrap_err() + .downcast() + .unwrap() + ); +} + #[test] fn test_genesis_transaction_fetch() { let tx = TransactionBuilder::default() @@ -76,25 +314,31 @@ fn test_genesis_transaction_fetch() { .with_header_builder(HeaderBuilder::default().difficulty(U256::from(1000u64))); let consensus = Consensus::default().set_genesis_block(genesis_block); - let (_chain_controller, shared) = start_chain(Some(consensus)); + let (_chain_controller, shared) = start_chain(Some(consensus), false); let out_point = OutPoint::new(root_hash, 0); - let state = shared.cell(&out_point); + let state = shared.chain_state().lock().cell(&out_point); assert!(state.is_live()); } #[test] fn test_chain_fork_by_total_difficulty() { - let (chain_controller, shared) = start_chain(None); + let (chain_controller, shared) = start_chain(None, false); let final_number = 20; let mut chain1: Vec = Vec::new(); let mut chain2: Vec = Vec::new(); let mut parent = shared.block_header(&shared.block_hash(0).unwrap()).unwrap(); - for i in 1..final_number { + for _ in 1..final_number { let difficulty = parent.difficulty().clone(); - let new_block = gen_block(&parent, i, difficulty + U256::from(100u64), vec![], vec![]); + let new_block = gen_block( + &parent, + difficulty + U256::from(100u64), + vec![], + vec![], + vec![], + ); chain1.push(new_block.clone()); parent = new_block.header().clone(); } @@ -105,10 +349,10 @@ fn test_chain_fork_by_total_difficulty() { let j = if i > 10 { 110 } else { 99 }; let new_block = gen_block( &parent, - i + 1000, difficulty + U256::from(j as u32), vec![], vec![], + vec![], ); chain2.push(new_block.clone()); parent = new_block.header().clone(); @@ -133,29 +377,35 @@ fn test_chain_fork_by_total_difficulty() { #[test] fn test_chain_fork_by_hash() { - let (chain_controller, shared) = start_chain(None); + let (chain_controller, shared) = start_chain(None, false); let final_number = 20; let mut chain1: Vec = Vec::new(); let mut chain2: Vec = Vec::new(); let mut parent = shared.block_header(&shared.block_hash(0).unwrap()).unwrap(); - for i in 1..final_number { + for _ in 1..final_number { let difficulty = parent.difficulty().clone(); - let new_block = gen_block(&parent, i, difficulty + U256::from(100u64), vec![], vec![]); + let new_block = gen_block( + &parent, + difficulty + U256::from(100u64), + vec![], + vec![], + vec![], + ); chain1.push(new_block.clone()); parent = new_block.header().clone(); } parent = shared.block_header(&shared.block_hash(0).unwrap()).unwrap(); - for i in 1..final_number { + for _ in 1..final_number { let difficulty = parent.difficulty().clone(); let new_block = gen_block( &parent, - i + 1000, difficulty + U256::from(100u64), vec![], vec![], + vec![], ); chain2.push(new_block.clone()); parent = new_block.header().clone(); @@ -195,29 +445,35 @@ fn test_chain_fork_by_hash() { #[test] fn test_chain_get_ancestor() { - let (chain_controller, shared) = start_chain(None); + let (chain_controller, shared) = start_chain(None, false); let final_number = 20; let mut chain1: Vec = Vec::new(); let mut chain2: Vec = Vec::new(); let mut parent = shared.block_header(&shared.block_hash(0).unwrap()).unwrap(); - for i in 1..final_number { + for _ in 1..final_number { let difficulty = parent.difficulty().clone(); - let new_block = gen_block(&parent, i, difficulty + U256::from(100u64), vec![], vec![]); + let new_block = gen_block( + &parent, + difficulty + U256::from(100u64), + vec![], + vec![], + vec![], + ); chain1.push(new_block.clone()); parent = new_block.header().clone(); } parent = shared.block_header(&shared.block_hash(0).unwrap()).unwrap(); - for i in 1..final_number { + for _ in 1..final_number { let difficulty = parent.difficulty().clone(); let new_block = gen_block( &parent, - i + 1000, difficulty + U256::from(100u64), vec![], vec![], + vec![], ); chain2.push(new_block.clone()); parent = new_block.header().clone(); @@ -258,16 +514,16 @@ fn test_calculate_difficulty() { consensus.pow_time_span = 200; consensus.pow_spacing = 1; - let (chain_controller, shared) = start_chain(Some(consensus.clone())); + let (chain_controller, shared) = start_chain(Some(consensus.clone()), false); let final_number = shared.consensus().difficulty_adjustment_interval(); let mut chain1: Vec = Vec::new(); let mut chain2: Vec = Vec::new(); let mut parent = shared.block_header(&shared.block_hash(0).unwrap()).unwrap(); - for i in 1..final_number - 1 { + for _ in 1..final_number - 1 { let difficulty = shared.calculate_difficulty(&parent).unwrap(); - let new_block = gen_block(&parent, i, difficulty, vec![], vec![]); + let new_block = gen_block(&parent, difficulty, vec![], vec![], vec![]); chain_controller .process_block(Arc::new(new_block.clone())) .expect("process block ok"); @@ -282,7 +538,7 @@ fn test_calculate_difficulty() { if i < 26 { uncles.push(chain1[i as usize].clone().into()); } - let new_block = gen_block(&parent, i + 100, difficulty, vec![], uncles); + let new_block = gen_block(&parent, difficulty, vec![], vec![], uncles); chain_controller .process_block(Arc::new(new_block.clone())) .expect("process block ok"); @@ -297,7 +553,7 @@ fn test_calculate_difficulty() { // 25 * 10 * 1000 / 200 assert_eq!(difficulty, U256::from(1250u64)); - let (chain_controller, shared) = start_chain(Some(consensus.clone())); + let (chain_controller, shared) = start_chain(Some(consensus.clone()), false); let mut chain2: Vec = Vec::new(); for i in 1..final_number - 1 { chain_controller @@ -312,7 +568,7 @@ fn test_calculate_difficulty() { if i < 11 { uncles.push(chain1[i as usize].clone().into()); } - let new_block = gen_block(&parent, i + 100, difficulty, vec![], uncles); + let new_block = gen_block(&parent, difficulty, vec![], vec![], uncles); chain_controller .process_block(Arc::new(new_block.clone())) .expect("process block ok"); @@ -327,7 +583,7 @@ fn test_calculate_difficulty() { // min[10 * 10 * 1000 / 200, 1000] assert_eq!(difficulty, U256::from(1000u64)); - let (chain_controller, shared) = start_chain(Some(consensus.clone())); + let (chain_controller, shared) = start_chain(Some(consensus.clone()), false); let mut chain2: Vec = Vec::new(); for i in 1..final_number - 1 { chain_controller @@ -342,7 +598,7 @@ fn test_calculate_difficulty() { if i < 151 { uncles.push(chain1[i as usize].clone().into()); } - let new_block = gen_block(&parent, i + 100, difficulty, vec![], uncles); + let new_block = gen_block(&parent, difficulty, vec![], vec![], uncles); chain_controller .process_block(Arc::new(new_block.clone())) .expect("process block ok"); diff --git a/chain/src/tests/delay_verify.rs b/chain/src/tests/delay_verify.rs new file mode 100644 index 0000000000..4395ae9fa9 --- /dev/null +++ b/chain/src/tests/delay_verify.rs @@ -0,0 +1,203 @@ +use crate::tests::util::{create_transaction, gen_block, start_chain}; +use ckb_core::block::Block; +use ckb_shared::error::SharedError; +use ckb_traits::ChainProvider; +use numext_fixed_uint::U256; +use std::sync::Arc; + +#[test] +fn test_dead_cell_in_same_block() { + let (chain_controller, shared) = start_chain(None, true); + let final_number = 20; + let switch_fork_number = 10; + + let mut chain1: Vec = Vec::new(); + let mut chain2: Vec = Vec::new(); + + let mut parent = shared.block_header(&shared.block_hash(0).unwrap()).unwrap(); + for _ in 1..final_number { + let difficulty = parent.difficulty().clone(); + let new_block = gen_block( + &parent, + difficulty + U256::from(100u64), + vec![], + vec![], + vec![], + ); + chain1.push(new_block.clone()); + parent = new_block.header().clone(); + } + + parent = shared.block_header(&shared.block_hash(0).unwrap()).unwrap(); + for _ in 1..switch_fork_number { + let difficulty = parent.difficulty().clone(); + let new_block = gen_block( + &parent, + difficulty + U256::from(99u64), + vec![], + vec![], + vec![], + ); + chain2.push(new_block.clone()); + parent = new_block.header().clone(); + } + + let last_cell_base = &chain2.last().unwrap().commit_transactions()[0]; + let tx1 = create_transaction(last_cell_base.hash(), 1); + let tx2 = create_transaction(tx1.hash(), 2); + let tx3 = create_transaction(tx1.hash(), 3); + let txs = vec![tx1, tx2, tx3]; + for i in switch_fork_number..final_number { + let difficulty = parent.difficulty().clone(); + let new_block = if i == switch_fork_number { + gen_block( + &parent, + difficulty + U256::from(20000u64), + vec![], + txs.clone(), + vec![], + ) + } else if i == switch_fork_number + 2 { + gen_block( + &parent, + difficulty + U256::from(20000u64), + txs.clone(), + vec![], + vec![], + ) + } else { + gen_block( + &parent, + difficulty + U256::from(20000u64), + vec![], + vec![], + vec![], + ) + }; + chain2.push(new_block.clone()); + parent = new_block.header().clone(); + } + + for block in &chain1 { + chain_controller + .process_block(Arc::new(block.clone())) + .expect("process block ok"); + } + + for block in chain2.iter().take(switch_fork_number + 1) { + chain_controller + .process_block(Arc::new(block.clone())) + .expect("process block ok"); + } + + assert_eq!( + SharedError::InvalidTransaction("Transactions((2, Conflict))".to_string()), + chain_controller + .process_block(Arc::new(chain2[switch_fork_number + 1].clone())) + .unwrap_err() + .downcast() + .unwrap() + ); +} + +#[test] +fn test_dead_cell_in_different_block() { + let (chain_controller, shared) = start_chain(None, true); + let final_number = 20; + let switch_fork_number = 10; + + let mut chain1: Vec = Vec::new(); + let mut chain2: Vec = Vec::new(); + + let mut parent = shared.block_header(&shared.block_hash(0).unwrap()).unwrap(); + for _ in 1..final_number { + let difficulty = parent.difficulty().clone(); + let new_block = gen_block( + &parent, + difficulty + U256::from(100u64), + vec![], + vec![], + vec![], + ); + chain1.push(new_block.clone()); + parent = new_block.header().clone(); + } + + parent = shared.block_header(&shared.block_hash(0).unwrap()).unwrap(); + for _ in 1..switch_fork_number { + let difficulty = parent.difficulty().clone(); + let new_block = gen_block( + &parent, + difficulty + U256::from(99u64), + vec![], + vec![], + vec![], + ); + chain2.push(new_block.clone()); + parent = new_block.header().clone(); + } + + let last_cell_base = &chain2.last().unwrap().commit_transactions()[0]; + let tx1 = create_transaction(last_cell_base.hash(), 1); + let tx2 = create_transaction(tx1.hash(), 2); + let tx3 = create_transaction(tx1.hash(), 3); + for i in switch_fork_number..final_number { + let difficulty = parent.difficulty().clone(); + let new_block = if i == switch_fork_number { + gen_block( + &parent, + difficulty + U256::from(20000u64), + vec![], + vec![tx1.clone(), tx2.clone(), tx3.clone()], + vec![], + ) + } else if i == switch_fork_number + 2 { + gen_block( + &parent, + difficulty + U256::from(20000u64), + vec![tx1.clone(), tx2.clone()], + vec![], + vec![], + ) + } else if i == switch_fork_number + 3 { + gen_block( + &parent, + difficulty + U256::from(20000u64), + vec![tx3.clone()], + vec![], + vec![], + ) + } else { + gen_block( + &parent, + difficulty + U256::from(20000u64), + vec![], + vec![], + vec![], + ) + }; + chain2.push(new_block.clone()); + parent = new_block.header().clone(); + } + + for block in &chain1 { + chain_controller + .process_block(Arc::new(block.clone())) + .expect("process block ok"); + } + + for block in chain2.iter().take(switch_fork_number + 2) { + chain_controller + .process_block(Arc::new(block.clone())) + .expect("process block ok"); + } + + assert_eq!( + SharedError::InvalidTransaction("Transactions((0, Conflict))".to_string()), + chain_controller + .process_block(Arc::new(chain2[switch_fork_number + 2].clone())) + .unwrap_err() + .downcast() + .unwrap() + ); +} diff --git a/chain/src/tests/find_fork.rs b/chain/src/tests/find_fork.rs index 105c9defe6..4c06cfd62c 100644 --- a/chain/src/tests/find_fork.rs +++ b/chain/src/tests/find_fork.rs @@ -32,15 +32,15 @@ fn test_find_fork_case1() { let mut fork2: Vec = Vec::new(); let mut parent = genesis.clone(); - for i in 0..4 { - let new_block = gen_block(&parent, i, U256::from(100u64), vec![], vec![]); + for _ in 0..4 { + let new_block = gen_block(&parent, U256::from(100u64), vec![], vec![], vec![]); fork1.push(new_block.clone()); parent = new_block.header().clone(); } let mut parent = genesis.clone(); - for i in 0..3 { - let new_block = gen_block(&parent, i, U256::from(90u64), vec![], vec![]); + for _ in 0..3 { + let new_block = gen_block(&parent, U256::from(90u64), vec![], vec![], vec![]); fork2.push(new_block.clone()); parent = new_block.header().clone(); } @@ -58,7 +58,7 @@ fn test_find_fork_case1() { let tip_number = { shared.chain_state().lock().tip_number() }; // fork2 total_difficulty 470 - let new_block = gen_block(&parent, 100, U256::from(200u64), vec![], vec![]); + let new_block = gen_block(&parent, U256::from(200u64), vec![], vec![], vec![]); fork2.push(new_block.clone()); let ext = BlockExt { @@ -104,15 +104,15 @@ fn test_find_fork_case2() { let mut fork2: Vec = Vec::new(); let mut parent = genesis.clone(); - for i in 0..4 { - let new_block = gen_block(&parent, i, U256::from(100u64), vec![], vec![]); + for _ in 0..4 { + let new_block = gen_block(&parent, U256::from(100u64), vec![], vec![], vec![]); fork1.push(new_block.clone()); parent = new_block.header().clone(); } let mut parent = fork1[0].header().clone(); - for i in 0..2 { - let new_block = gen_block(&parent, i, U256::from(90u64), vec![], vec![]); + for _ in 0..2 { + let new_block = gen_block(&parent, U256::from(90u64), vec![], vec![], vec![]); fork2.push(new_block.clone()); parent = new_block.header().clone(); } @@ -132,10 +132,10 @@ fn test_find_fork_case2() { let difficulty = parent.difficulty().clone(); let new_block = gen_block( &parent, - 100, difficulty + U256::from(200u64), vec![], vec![], + vec![], ); fork2.push(new_block.clone()); @@ -182,15 +182,15 @@ fn test_find_fork_case3() { let mut fork2: Vec = Vec::new(); let mut parent = genesis.clone(); - for i in 0..3 { - let new_block = gen_block(&parent, i, U256::from(80u64), vec![], vec![]); + for _ in 0..3 { + let new_block = gen_block(&parent, U256::from(80u64), vec![], vec![], vec![]); fork1.push(new_block.clone()); parent = new_block.header().clone(); } let mut parent = genesis.clone(); - for i in 0..5 { - let new_block = gen_block(&parent, i, U256::from(40u64), vec![], vec![]); + for _ in 0..5 { + let new_block = gen_block(&parent, U256::from(40u64), vec![], vec![], vec![]); fork2.push(new_block.clone()); parent = new_block.header().clone(); } @@ -209,7 +209,7 @@ fn test_find_fork_case3() { println!("case3 tip{}", tip_number); - let new_block = gen_block(&parent, 100, U256::from(100u64), vec![], vec![]); + let new_block = gen_block(&parent, U256::from(100u64), vec![], vec![], vec![]); fork2.push(new_block.clone()); let ext = BlockExt { @@ -254,15 +254,15 @@ fn test_find_fork_case4() { let mut fork2: Vec = Vec::new(); let mut parent = genesis.clone(); - for i in 0..5 { - let new_block = gen_block(&parent, i, U256::from(40u64), vec![], vec![]); + for _ in 0..5 { + let new_block = gen_block(&parent, U256::from(40u64), vec![], vec![], vec![]); fork1.push(new_block.clone()); parent = new_block.header().clone(); } let mut parent = genesis.clone(); - for i in 0..2 { - let new_block = gen_block(&parent, i, U256::from(80u64), vec![], vec![]); + for _ in 0..2 { + let new_block = gen_block(&parent, U256::from(80u64), vec![], vec![], vec![]); fork2.push(new_block.clone()); parent = new_block.header().clone(); } @@ -281,7 +281,7 @@ fn test_find_fork_case4() { println!("case3 tip{}", tip_number); - let new_block = gen_block(&parent, 100, U256::from(100u64), vec![], vec![]); + let new_block = gen_block(&parent, U256::from(100u64), vec![], vec![], vec![]); fork2.push(new_block.clone()); let ext = BlockExt { diff --git a/chain/src/tests/mod.rs b/chain/src/tests/mod.rs index 757d06d3ca..969c0a2f57 100644 --- a/chain/src/tests/mod.rs +++ b/chain/src/tests/mod.rs @@ -1,3 +1,4 @@ mod basic; +mod delay_verify; mod find_fork; mod util; diff --git a/chain/src/tests/util.rs b/chain/src/tests/util.rs index c14c93d8d8..14517b5134 100644 --- a/chain/src/tests/util.rs +++ b/chain/src/tests/util.rs @@ -3,9 +3,8 @@ use ckb_chain_spec::consensus::Consensus; use ckb_core::block::Block; use ckb_core::block::BlockBuilder; use ckb_core::header::{Header, HeaderBuilder}; -use ckb_core::transaction::{ - CellInput, CellOutput, OutPoint, ProposalShortId, Transaction, TransactionBuilder, -}; +use ckb_core::script::Script; +use ckb_core::transaction::{CellInput, CellOutput, OutPoint, Transaction, TransactionBuilder}; use ckb_core::uncle::UncleBlock; use ckb_core::BlockNumber; use ckb_db::memorydb::MemoryKeyValueDB; @@ -16,9 +15,13 @@ use ckb_shared::store::ChainKVStore; use faketime::unix_time_as_millis; use numext_fixed_hash::H256; use numext_fixed_uint::U256; +use std::fs::File; +use std::io::Read; +use std::path::Path; pub(crate) fn start_chain( consensus: Option, + verification: bool, ) -> (ChainController, Shared>) { let builder = SharedBuilder::::new(); let shared = builder @@ -27,7 +30,7 @@ pub(crate) fn start_chain( let notify = NotifyService::default().start::<&str>(None); let chain_service = ChainBuilder::new(shared.clone(), notify) - .verification(false) + .verification(verification) .build(); let chain_controller = chain_service.start::<&str>(None); (chain_controller, shared) @@ -36,43 +39,63 @@ pub(crate) fn start_chain( fn create_cellbase(number: BlockNumber) -> Transaction { TransactionBuilder::default() .input(CellInput::new_cellbase_input(number)) - .output(CellOutput::new(0, vec![], H256::zero(), None)) + .output(CellOutput::new( + 5000, + vec![], + create_script().type_hash(), + None, + )) .build() } pub(crate) fn gen_block( parent_header: &Header, - nonce: u64, difficulty: U256, commit_transactions: Vec, + proposal_transactions: Vec, uncles: Vec, ) -> Block { let number = parent_header.number() + 1; let cellbase = create_cellbase(number); - let header = HeaderBuilder::default() + let header_builder = HeaderBuilder::default() .parent_hash(parent_header.hash().clone()) .timestamp(unix_time_as_millis()) .number(number) .difficulty(difficulty) - .nonce(nonce) - .build(); + .cellbase_id(cellbase.hash()); BlockBuilder::default() - .header(header) .commit_transaction(cellbase) .commit_transactions(commit_transactions) .uncles(uncles) - .proposal_transaction(ProposalShortId::from_slice(&[1; 10]).unwrap()) - .build() + .proposal_transactions( + proposal_transactions + .iter() + .map(|tx| tx.proposal_short_id()) + .collect(), + ) + .with_header_builder(header_builder) } -pub(crate) fn create_transaction(parent: H256) -> Transaction { - let mut output = CellOutput::default(); - output.capacity = 100_000_000 / 100 as u64; - let outputs: Vec = vec![output.clone(); 100]; - +pub(crate) fn create_transaction(parent: H256, unique_data: u8) -> Transaction { + let script = create_script(); TransactionBuilder::default() - .input(CellInput::new(OutPoint::new(parent, 0), Default::default())) - .outputs(outputs) + .output(CellOutput::new( + 5000, + vec![unique_data], + script.type_hash(), + None, + )) + .input(CellInput::new(OutPoint::new(parent, 0), script)) .build() } + +fn create_script() -> Script { + let mut file = File::open( + Path::new(env!("CARGO_MANIFEST_DIR")).join("../nodes_template/spec/cells/always_success"), + ) + .unwrap(); + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer).unwrap(); + Script::new(0, Vec::new(), None, Some(buffer), Vec::new()) +} diff --git a/core/src/cell.rs b/core/src/cell.rs index ba18000dad..4640f406e6 100644 --- a/core/src/cell.rs +++ b/core/src/cell.rs @@ -1,5 +1,8 @@ +use crate::block::Block; use crate::transaction::{CellOutput, OutPoint, Transaction}; -use fnv::FnvHashSet; +use crate::Capacity; +use fnv::{FnvHashMap, FnvHashSet}; +use numext_fixed_hash::H256; use std::iter::Chain; use std::slice; @@ -54,32 +57,83 @@ pub struct ResolvedTransaction { pub trait CellProvider { fn cell(&self, out_point: &OutPoint) -> CellStatus; +} + +pub struct OverlayCellProvider<'a, O, CP> { + overlay: &'a O, + cell_provider: &'a CP, +} - fn cell_at Option>( - &self, - _out_point: &OutPoint, - _is_dead: F, - ) -> CellStatus { - unreachable!() +impl<'a, O, CP> OverlayCellProvider<'a, O, CP> { + pub fn new(overlay: &'a O, cell_provider: &'a CP) -> Self { + OverlayCellProvider { + overlay, + cell_provider, + } } +} - fn resolve_transaction(&self, transaction: &Transaction) -> ResolvedTransaction { - let mut seen_inputs = FnvHashSet::default(); - resolve_transaction(transaction, &mut seen_inputs, |x| self.cell(x)) +impl<'a, O, CP> CellProvider for OverlayCellProvider<'a, O, CP> +where + O: CellProvider, + CP: CellProvider, +{ + fn cell(&self, out_point: &OutPoint) -> CellStatus { + match self.overlay.cell(out_point) { + CellStatus::Live(co) => CellStatus::Live(co), + CellStatus::Dead => CellStatus::Dead, + CellStatus::Unknown => self.cell_provider.cell(out_point), + } } } -pub fn resolve_transaction CellStatus>( +pub struct BlockCellProvider<'a> { + output_indices: FnvHashMap, + block: &'a Block, +} + +impl<'a> BlockCellProvider<'a> { + pub fn new(block: &'a Block) -> Self { + let output_indices = block + .commit_transactions() + .iter() + .enumerate() + .map(|(idx, tx)| (tx.hash(), idx)) + .collect(); + Self { + output_indices, + block, + } + } +} + +impl<'a> CellProvider for BlockCellProvider<'a> { + fn cell(&self, out_point: &OutPoint) -> CellStatus { + if let Some(i) = self.output_indices.get(&out_point.hash) { + match self.block.commit_transactions()[*i] + .outputs() + .get(out_point.index as usize) + { + Some(x) => CellStatus::Live(x.clone()), + None => CellStatus::Unknown, + } + } else { + CellStatus::Unknown + } + } +} + +pub fn resolve_transaction( transaction: &Transaction, seen_inputs: &mut FnvHashSet, - cell: F, + cell_provider: &CP, ) -> ResolvedTransaction { let input_cells = transaction .input_pts() .iter() .map(|input| { if seen_inputs.insert(input.clone()) { - cell(input) + cell_provider.cell(input) } else { CellStatus::Dead } @@ -91,7 +145,7 @@ pub fn resolve_transaction CellStatus>( .iter() .map(|dep| { if seen_inputs.insert(dep.clone()) { - cell(dep) + cell_provider.cell(dep) } else { CellStatus::Dead } @@ -127,6 +181,24 @@ impl ResolvedTransaction { pub fn is_fully_resolved(&self) -> bool { self.cells_iter().all(|state| state.is_live()) } + + pub fn fee(&self) -> Capacity { + self.inputs_capacity() + .saturating_sub(self.transaction.outputs_capacity()) + } + + pub fn inputs_capacity(&self) -> Capacity { + self.input_cells + .iter() + .filter_map(|cell_status| { + if let CellStatus::Live(cell_output) = cell_status { + Some(cell_output.capacity) + } else { + None + } + }) + .sum() + } } #[cfg(test)] @@ -146,14 +218,6 @@ mod tests { None => CellStatus::Unknown, } } - - fn cell_at Option>(&self, o: &OutPoint, _: F) -> CellStatus { - match self.cells.get(o) { - Some(&Some(ref cell_output)) => CellStatus::Live(cell_output.clone()), - Some(&None) => CellStatus::Dead, - None => CellStatus::Unknown, - } - } } #[test] diff --git a/core/src/transaction.rs b/core/src/transaction.rs index d35d1d861c..c0b7b89d05 100644 --- a/core/src/transaction.rs +++ b/core/src/transaction.rs @@ -297,6 +297,10 @@ impl Transaction { pub fn get_output(&self, i: usize) -> Option { self.outputs.get(i).cloned() } + + pub fn outputs_capacity(&self) -> Capacity { + self.outputs.iter().map(|output| output.capacity).sum() + } } #[derive(Default)] diff --git a/rpc/src/module/chain.rs b/rpc/src/module/chain.rs index fe18d3663e..b64c87f046 100644 --- a/rpc/src/module/chain.rs +++ b/rpc/src/module/chain.rs @@ -96,7 +96,12 @@ impl ChainRpc for ChainRpcImpl { } fn get_live_cell(&self, out_point: OutPoint) -> Result { - Ok(self.shared.cell(&(out_point.into())).into()) + Ok(self + .shared + .chain_state() + .lock() + .cell(&(out_point.into())) + .into()) } fn get_tip_block_number(&self) -> Result { diff --git a/shared/src/cell_set.rs b/shared/src/cell_set.rs index 3b1eab26a3..d42d1cf972 100644 --- a/shared/src/cell_set.rs +++ b/shared/src/cell_set.rs @@ -1,15 +1,49 @@ +use ckb_core::block::Block; +use ckb_core::cell::{CellProvider, CellStatus}; use ckb_core::transaction::OutPoint; use ckb_core::transaction_meta::TransactionMeta; -use fnv::FnvHashMap; +use fnv::{FnvHashMap, FnvHashSet}; use numext_fixed_hash::H256; use serde_derive::{Deserialize, Serialize}; #[derive(Default, Debug, Clone)] pub struct CellSetDiff { - pub old_inputs: Vec, - pub old_outputs: Vec, - pub new_inputs: Vec, - pub new_outputs: Vec<(H256, usize)>, + pub old_inputs: FnvHashSet, + pub old_outputs: FnvHashSet, + pub new_inputs: FnvHashSet, + pub new_outputs: FnvHashMap, +} + +impl CellSetDiff { + pub fn push_new(&mut self, block: &Block) { + for tx in block.commit_transactions() { + let input_pts = tx.input_pts(); + let tx_hash = tx.hash(); + let output_len = tx.outputs().len(); + self.new_inputs.extend(input_pts); + self.new_outputs.insert(tx_hash, output_len); + } + } + + pub fn push_old(&mut self, block: &Block) { + for tx in block.commit_transactions() { + let input_pts = tx.input_pts(); + let tx_hash = tx.hash(); + + self.old_inputs.extend(input_pts); + self.old_outputs.insert(tx_hash); + } + } +} + +impl CellProvider for CellSetDiff { + fn cell(&self, out_point: &OutPoint) -> CellStatus { + if self.new_inputs.contains(out_point) { + CellStatus::Dead + } else { + CellStatus::Unknown + } + } } #[derive(Default, Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] @@ -52,28 +86,43 @@ impl CellSet { } } - fn rollback(&mut self, inputs: Vec, outputs: Vec) { - for h in outputs { - self.remove(&h); - } + pub fn update(&mut self, diff: CellSetDiff) { + let CellSetDiff { + old_inputs, + old_outputs, + new_inputs, + new_outputs, + } = diff; - for o in inputs { - self.mark_live(&o); - } - } + old_outputs.iter().for_each(|h| { + self.remove(h); + }); + + old_inputs.iter().for_each(|o| { + self.mark_live(o); + }); - fn forward(&mut self, inputs: Vec, outputs: Vec<(H256, usize)>) { - for (hash, len) in outputs { + new_outputs.into_iter().for_each(|(hash, len)| { self.insert(hash, len); - } + }); - for o in inputs { - self.mark_dead(&o); - } + new_inputs.iter().for_each(|o| { + self.mark_dead(o); + }); } +} - pub fn update(&mut self, diff: CellSetDiff) { - self.rollback(diff.old_inputs, diff.old_outputs); - self.forward(diff.new_inputs, diff.new_outputs); +impl CellProvider for CellSet { + fn cell(&self, out_point: &OutPoint) -> CellStatus { + match self.get(&out_point.hash) { + Some(meta) => { + if meta.is_dead(out_point.index as usize) { + CellStatus::Dead + } else { + CellStatus::Unknown + } + } + None => CellStatus::Unknown, + } } } diff --git a/shared/src/chain_state.rs b/shared/src/chain_state.rs index b351ea94bd..153bf17ed5 100644 --- a/shared/src/chain_state.rs +++ b/shared/src/chain_state.rs @@ -4,7 +4,9 @@ use crate::index::ChainIndex; use crate::tx_pool::{PoolEntry, PoolError, StagingTxResult, TxPool}; use crate::tx_proposal_table::TxProposalTable; use ckb_core::block::Block; -use ckb_core::cell::{CellProvider, CellStatus, ResolvedTransaction}; +use ckb_core::cell::{ + resolve_transaction, CellProvider, CellStatus, OverlayCellProvider, ResolvedTransaction, +}; use ckb_core::header::{BlockNumber, Header}; use ckb_core::transaction::{OutPoint, ProposalShortId, Transaction}; use ckb_core::Cycle; @@ -124,58 +126,10 @@ impl ChainState { } } - fn get_cell_status_from_store(&self, out_point: &OutPoint) -> CellStatus { - let index = out_point.index as usize; - if let Some(f) = self.is_dead(out_point) { - if f { - CellStatus::Dead - } else { - let transaction = self - .store - .get_transaction(&out_point.hash) - .expect("transaction must exist"); - CellStatus::Live(transaction.outputs()[index].clone()) - } - } else { - CellStatus::Unknown - } - } - pub fn resolve_tx_from_pool(&self, tx: &Transaction, tx_pool: &TxPool) -> ResolvedTransaction { - let fetch_cell = |op| match tx_pool.staging.cell(op) { - CellStatus::Unknown => self.get_cell_status_from_store(op), - cs => cs, - }; + let cell_provider = OverlayCellProvider::new(&tx_pool.staging, self); let mut seen_inputs = FnvHashSet::default(); - let inputs = tx.input_pts(); - let input_cells = inputs - .iter() - .map(|input| { - if seen_inputs.insert(input.clone()) { - fetch_cell(input) - } else { - CellStatus::Dead - } - }) - .collect(); - - let dep_cells = tx - .dep_pts() - .iter() - .map(|dep| { - if seen_inputs.insert(dep.clone()) { - fetch_cell(dep) - } else { - CellStatus::Dead - } - }) - .collect(); - - ResolvedTransaction { - transaction: tx.clone(), - input_cells, - dep_cells, - } + resolve_transaction(tx, &mut seen_inputs, &cell_provider) } pub fn verify_rtx( @@ -370,3 +324,15 @@ impl ChainState { self.tx_pool.get_mut() } } + +impl CellProvider for ChainState { + fn cell(&self, out_point: &OutPoint) -> CellStatus { + match self.store.get_transaction(&out_point.hash) { + Some(tx) => match tx.outputs().get(out_point.index as usize) { + Some(output) => CellStatus::Live(output.clone()), + None => CellStatus::Unknown, + }, + None => CellStatus::Unknown, + } + } +} diff --git a/shared/src/error.rs b/shared/src/error.rs index 9d8c32a0f0..e374b686c7 100644 --- a/shared/src/error.rs +++ b/shared/src/error.rs @@ -7,8 +7,10 @@ pub enum SharedError { InvalidInput, #[fail(display = "InvalidOutput")] InvalidOutput, - #[fail(display = "InvalidTransaction")] - InvalidTransaction, + #[fail(display = "InvalidTransaction: {}", _0)] + InvalidTransaction(String), + #[fail(display = "InvalidParentBlock")] + InvalidParentBlock, #[fail(display = "DB error: {}", _0)] DB(DBError), } diff --git a/shared/src/shared.rs b/shared/src/shared.rs index 263b85c729..7942986dec 100644 --- a/shared/src/shared.rs +++ b/shared/src/shared.rs @@ -9,10 +9,9 @@ use crate::tx_proposal_table::TxProposalTable; use crate::{COLUMNS, COLUMN_BLOCK_HEADER}; use ckb_chain_spec::consensus::{Consensus, ProposalWindow}; use ckb_core::block::Block; -use ckb_core::cell::{CellProvider, CellStatus}; use ckb_core::extras::BlockExt; use ckb_core::header::{BlockNumber, Header}; -use ckb_core::transaction::{Capacity, OutPoint, ProposalShortId, Transaction}; +use ckb_core::transaction::{Capacity, ProposalShortId, Transaction}; use ckb_core::uncle::UncleBlock; use ckb_db::{DBConfig, KeyValueDB, MemoryKeyValueDB, RocksDB}; use ckb_traits::{BlockMedianTimeContext, ChainProvider}; @@ -135,14 +134,13 @@ impl Shared { let hash = store.get_block_hash(n).unwrap(); for tx in store.get_block_body(&hash).unwrap() { let inputs = tx.input_pts(); - let tx_hash = tx.hash(); let output_len = tx.outputs().len(); for o in inputs { cell_set.mark_dead(&o); } - cell_set.insert(tx_hash, output_len); + cell_set.insert(tx.hash(), output_len); } } @@ -150,33 +148,6 @@ impl Shared { } } -impl CellProvider for Shared { - fn cell(&self, out_point: &OutPoint) -> CellStatus { - self.cell_at(out_point, |op| self.chain_state.lock().is_dead(op)) - } - - fn cell_at Option>( - &self, - out_point: &OutPoint, - is_dead: F, - ) -> CellStatus { - let index = out_point.index as usize; - if let Some(f) = is_dead(out_point) { - if f { - CellStatus::Dead - } else { - let transaction = self - .store - .get_transaction(&out_point.hash) - .expect("transaction must exist"); - CellStatus::Live(transaction.outputs()[index].clone()) - } - } else { - CellStatus::Unknown - } - } -} - impl ChainProvider for Shared { fn block(&self, hash: &H256) -> Option { self.store.get_block(hash) diff --git a/verification/src/block_verifier.rs b/verification/src/block_verifier.rs index 3f1fd78020..287254767f 100644 --- a/verification/src/block_verifier.rs +++ b/verification/src/block_verifier.rs @@ -2,7 +2,7 @@ use crate::error::{CellbaseError, CommitError, Error, UnclesError}; use crate::header_verifier::HeaderResolver; use crate::{InputVerifier, TransactionVerifier, Verifier}; use ckb_core::block::Block; -use ckb_core::cell::{CellProvider, ResolvedTransaction}; +use ckb_core::cell::ResolvedTransaction; use ckb_core::header::Header; use ckb_core::transaction::{Capacity, CellInput}; use ckb_core::Cycle; @@ -32,7 +32,7 @@ pub struct BlockVerifier

{ impl

BlockVerifier

where - P: ChainProvider + CellProvider + Clone + 'static, + P: ChainProvider + Clone + 'static, { pub fn new(provider: P) -> Self { BlockVerifier { @@ -46,7 +46,7 @@ where } } -impl Verifier for BlockVerifier

{ +impl Verifier for BlockVerifier

{ type Target = Block; fn verify(&self, target: &Block) -> Result<(), Error> { @@ -100,25 +100,7 @@ impl CellbaseVerifier { return Err(Error::Cellbase(CellbaseError::InvalidOutput)); } - let block_reward = self.provider.block_reward(block.header().number()); - let mut fee = 0; - for transaction in block.commit_transactions().iter().skip(1) { - fee += self - .provider - .calculate_transaction_fee(transaction) - .map_err(|e| Error::Chain(format!("{}", e)))?; - } - let total_reward = block_reward + fee; - let output_capacity: Capacity = cellbase_transaction - .outputs() - .iter() - .map(|output| output.capacity) - .sum(); - if output_capacity > total_reward { - Err(Error::Cellbase(CellbaseError::InvalidReward)) - } else { - Ok(()) - } + Ok(()) } } @@ -389,10 +371,20 @@ impl TransactionsVerifier { &self, txs_verify_cache: &mut LruCache, resolved: &[ResolvedTransaction], + block_reward: Capacity, ) -> Result<(), Error> { + // verify cellbase reward + let cellbase = &resolved[0]; + let fee: Capacity = resolved.iter().skip(1).map(|rt| rt.fee()).sum(); + if cellbase.transaction.outputs_capacity() > block_reward + fee { + return Err(Error::Cellbase(CellbaseError::InvalidReward)); + } + // TODO use TransactionScriptsVerifier to verify cellbase script + // make verifiers orthogonal let cycles_set = resolved .par_iter() + .skip(1) .enumerate() .map(|(index, tx)| { if let Some(cycles) = txs_verify_cache.get(&tx.transaction.hash()) { diff --git a/verification/src/tests/block_verifier.rs b/verification/src/tests/block_verifier.rs index 5d7cd96100..7020930f4c 100644 --- a/verification/src/tests/block_verifier.rs +++ b/verification/src/tests/block_verifier.rs @@ -130,110 +130,6 @@ pub fn test_cellbase_with_fee() { assert!(verifier.verify(&block).is_ok()); } -#[test] -pub fn test_cellbase_with_more_reward_than_available() { - let mut transaction_fees = HashMap::>::new(); - let transaction = create_normal_transaction(); - transaction_fees.insert(transaction.hash().clone(), Ok(10)); - - let block = BlockBuilder::default() - .commit_transaction(create_cellbase_transaction_with_capacity(130)) - .commit_transaction(transaction) - .build(); - - let provider = DummyChainProvider { - block_reward: 100, - transaction_fees, - }; - - let verifier = CellbaseVerifier::new(provider); - assert_eq!( - verifier.verify(&block), - Err(VerifyError::Cellbase(CellbaseError::InvalidReward)) - ); -} - -#[test] -pub fn test_cellbase_with_invalid_transaction() { - let mut transaction_fees = HashMap::>::new(); - let transaction = create_normal_transaction(); - transaction_fees.insert(transaction.hash().clone(), Err(SharedError::InvalidOutput)); - - let block = BlockBuilder::default() - .commit_transaction(create_cellbase_transaction_with_capacity(100)) - .commit_transaction(transaction) - .build(); - - let provider = DummyChainProvider { - block_reward: 100, - transaction_fees, - }; - - let verifier = CellbaseVerifier::new(provider); - assert_eq!( - verifier.verify(&block), - Err(VerifyError::Chain(format!( - "{}", - SharedError::InvalidOutput - ))) - ); -} - -#[test] -pub fn test_cellbase_with_two_outputs() { - let mut transaction_fees = HashMap::>::new(); - let transaction = create_normal_transaction(); - transaction_fees.insert(transaction.hash().clone(), Ok(0)); - - let cellbase_transaction = TransactionBuilder::default() - .input(CellInput::new_cellbase_input(0)) - .output(CellOutput::new(100, Vec::new(), H256::default(), None)) - .output(CellOutput::new(50, Vec::new(), H256::default(), None)) - .build(); - - let block = BlockBuilder::default() - .commit_transaction(cellbase_transaction) - .commit_transaction(transaction) - .build(); - - let provider = DummyChainProvider { - block_reward: 150, - transaction_fees, - }; - - let verifier = CellbaseVerifier::new(provider); - assert!(verifier.verify(&block).is_ok()); -} - -#[test] -pub fn test_cellbase_with_two_outputs_and_more_rewards_than_maximum() { - let mut transaction_fees = HashMap::>::new(); - let transaction = create_normal_transaction(); - transaction_fees.insert(transaction.hash().clone(), Ok(0)); - - let cellbase_transaction = TransactionBuilder::default() - .input(CellInput::new_cellbase_input(0)) - .output(CellOutput::new(100, Vec::new(), H256::default(), None)) - .output(CellOutput::new(50, Vec::new(), H256::default(), None)) - .build(); - - let block = BlockBuilder::default() - .commit_transaction(cellbase_transaction) - .commit_transaction(transaction) - .build(); - - let provider = DummyChainProvider { - block_reward: 100, - transaction_fees, - }; - - let verifier = CellbaseVerifier::new(provider); - assert_eq!( - verifier.verify(&block), - Err(VerifyError::Cellbase(CellbaseError::InvalidReward)) - ); -} - #[test] pub fn test_empty_transactions() { let block = BlockBuilder::default().build(); diff --git a/verification/src/tests/dummy.rs b/verification/src/tests/dummy.rs index 07178b50d9..a2aae0d810 100644 --- a/verification/src/tests/dummy.rs +++ b/verification/src/tests/dummy.rs @@ -92,8 +92,4 @@ impl CellProvider for DummyChainProvider { fn cell(&self, _o: &OutPoint) -> CellStatus { panic!("Not implemented!"); } - - fn cell_at Option>(&self, _o: &OutPoint, _: F) -> CellStatus { - panic!("Not implemented!"); - } } diff --git a/verification/src/transaction_verifier.rs b/verification/src/transaction_verifier.rs index 845b66b7b0..4205874a5f 100644 --- a/verification/src/transaction_verifier.rs +++ b/verification/src/transaction_verifier.rs @@ -32,10 +32,9 @@ impl<'a> TransactionVerifier<'a> { self.version.verify()?; self.empty.verify()?; self.null.verify()?; + self.inputs.verify()?; self.capacity.verify()?; self.duplicate_inputs.verify()?; - // InputVerifier should be executed before ScriptVerifier - self.inputs.verify()?; let cycles = self.script.verify(max_cycles)?; Ok(cycles) }