use crate::chain::ChainController;
use crate::tests::util::{
create_always_success_tx, create_cellbase, create_multi_outputs_transaction,
create_transaction, create_transaction_with_out_point, dao_data, start_chain, MockChain,
MockStore,
};
use ckb_chain_spec::consensus::{Consensus, ConsensusBuilder};
use ckb_dao_utils::genesis_dao_data;
use ckb_error::assert_error_eq;
use ckb_shared::shared::Shared;
use ckb_store::ChainStore;
use ckb_types::core::error::OutPointError;
use ckb_types::prelude::*;
use ckb_types::{
bytes::Bytes,
core::{
capacity_bytes,
cell::{CellMeta, CellProvider, CellStatus},
BlockBuilder, BlockView, Capacity, HeaderView, TransactionBuilder, TransactionInfo,
},
packed::{CellInput, CellOutputBuilder, OutPoint, Script},
utilities::{compact_to_difficulty, difficulty_to_compact},
U256,
};
use ckb_verification_traits::Switch;
use std::sync::Arc;
#[test]
fn repeat_process_block() {
let (chain_controller, shared, parent) = start_chain(None);
let mock_store = MockStore::new(&parent, shared.store());
let mut chain = MockChain::new(parent, shared.consensus());
chain.gen_empty_block_with_nonce(100u128, &mock_store);
let block = Arc::new(chain.blocks().last().unwrap().clone());
assert!(chain_controller
.process_block(Arc::clone(&block))
.expect("process block ok"));
assert_eq!(
shared
.store()
.get_block_ext(&block.header().hash())
.unwrap()
.verified,
Some(true)
);
assert!(!chain_controller
.process_block(Arc::clone(&block))
.expect("process block ok"));
assert_eq!(
shared
.store()
.get_block_ext(&block.header().hash())
.unwrap()
.verified,
Some(true)
);
}
#[test]
fn test_genesis_transaction_spend() {
let tx = TransactionBuilder::default()
.witness(Script::default().into_witness())
.input(CellInput::new(OutPoint::null(), 0))
.outputs(vec![
CellOutputBuilder::default()
.capacity(capacity_bytes!(100_000_000).pack())
.build();
100
])
.outputs_data(vec![Bytes::new(); 100].pack())
.build();
let always_success_tx = create_always_success_tx();
let mut root_hash = tx.hash();
let genesis_tx_hash = root_hash.clone();
let dao = genesis_dao_data(vec![&tx, &always_success_tx]).unwrap();
let genesis_block = BlockBuilder::default()
.transaction(tx)
.transaction(always_success_tx)
.compact_target(difficulty_to_compact(U256::from(1000u64)).pack())
.dao(dao)
.build();
let consensus = ConsensusBuilder::default()
.genesis_block(genesis_block)
.build();
let (chain_controller, shared, parent) = start_chain(Some(consensus));
let end = 21;
let mock_store = MockStore::new(&parent, shared.store());
let mut chain = MockChain::new(parent, shared.consensus());
for i in 1..end {
let tx = create_transaction(&root_hash, i as u8);
root_hash = tx.hash();
chain.gen_block_with_commit_txs(vec![tx], &mock_store, false);
}
for block in &chain.blocks()[0..10] {
assert!(chain_controller
.internal_process_block(Arc::new(block.clone()), Switch::DISABLE_ALL)
.is_ok());
}
assert_eq!(
shared
.snapshot()
.as_ref()
.cell(&OutPoint::new(genesis_tx_hash, 0), false),
CellStatus::Unknown
);
}
#[test]
fn test_transaction_spend_in_same_block() {
let (chain_controller, shared, parent) = start_chain(None);
let mock_store = MockStore::new(&parent, shared.store());
let mut chain = MockChain::new(parent, shared.consensus());
chain.gen_empty_block(&mock_store);
let last_cellbase = &shared.consensus().genesis_block().transactions()[1];
let last_cellbase_hash = last_cellbase.hash();
let tx1 = create_multi_outputs_transaction(last_cellbase, vec![0], 2, vec![1]);
let tx1_hash = tx1.hash();
let tx2 = create_multi_outputs_transaction(&tx1, vec![0], 2, vec![2]);
let tx2_hash = tx2.hash();
let tx2_output = tx2.outputs().get(0).expect("outputs index 0");
let tx2_output_data = tx2.outputs_data().get(0).expect("outputs_data index 0");
let txs = vec![tx1, tx2];
for hash in &[&tx1_hash, &tx2_hash] {
assert_eq!(
shared
.snapshot()
.as_ref()
.cell(&OutPoint::new(hash.to_owned().to_owned(), 0), false),
CellStatus::Unknown
);
}
chain.gen_block_with_proposal_txs(txs.clone(), &mock_store);
chain.gen_empty_block(&mock_store);
chain.gen_block_with_commit_txs(txs, &mock_store, false);
let (parent_hash4, parent_number4) = {
chain
.blocks()
.last()
.map(|block| (block.header().hash(), block.header().number()))
.unwrap()
};
for block in chain.blocks() {
chain_controller
.internal_process_block(Arc::new(block.clone()), Switch::DISABLE_EPOCH)
.expect("process block ok");
}
assert_eq!(
shared
.snapshot()
.as_ref()
.cell(&OutPoint::new(last_cellbase_hash, 0), false),
CellStatus::Unknown
);
assert_eq!(
shared
.snapshot()
.as_ref()
.cell(&OutPoint::new(tx1_hash, 0), false),
CellStatus::Unknown
);
let epoch = mock_store
.store()
.get_block_epoch_index(&parent_hash4)
.and_then(|index| mock_store.store().get_epoch_ext(&index))
.unwrap();
assert_eq!(
shared
.snapshot()
.as_ref()
.cell(&OutPoint::new(tx2_hash.clone(), 0), false),
CellStatus::live_cell(CellMeta {
cell_output: tx2_output,
data_bytes: tx2_output_data.len() as u64,
out_point: OutPoint::new(tx2_hash, 0),
transaction_info: Some(TransactionInfo::new(
parent_number4,
epoch.number_with_fraction(parent_number4),
parent_hash4,
2
)),
mem_cell_data: None,
mem_cell_data_hash: None,
})
);
}
#[test]
fn test_transaction_conflict_in_same_block() {
let (chain_controller, shared, parent) = start_chain(None);
let mock_store = MockStore::new(&parent, shared.store());
let mut chain = MockChain::new(parent, shared.consensus());
chain.gen_empty_block(&mock_store);
let last_cellbase = &shared.consensus().genesis_block().transactions()[1];
let tx1 = create_transaction(&last_cellbase.hash(), 1);
let tx1_hash = tx1.hash();
let tx2 = create_transaction(&tx1_hash, 2);
let tx3 = create_transaction(&tx1_hash, 3);
let txs = vec![tx1, tx2, tx3];
chain.gen_block_with_proposal_txs(txs.clone(), &mock_store);
chain.gen_empty_block(&mock_store);
chain.gen_block_with_commit_txs(txs, &mock_store, true);
for block in chain.blocks().iter().take(3) {
chain_controller
.process_block(Arc::new(block.clone()))
.expect("process block ok");
}
assert_error_eq!(
OutPointError::Dead(OutPoint::new(tx1_hash, 0)),
chain_controller
.process_block(Arc::new(chain.blocks()[3].clone()))
.unwrap_err(),
);
}
#[test]
fn test_transaction_conflict_in_different_blocks() {
let (chain_controller, shared, parent) = start_chain(None);
let mock_store = MockStore::new(&parent, shared.store());
let mut chain = MockChain::new(parent, shared.consensus());
chain.gen_empty_block(&mock_store);
let last_cellbase = &shared.consensus().genesis_block().transactions()[1];
let tx1 = create_multi_outputs_transaction(last_cellbase, vec![0], 2, vec![1]);
let tx1_hash = tx1.hash();
let tx2 = create_multi_outputs_transaction(&tx1, vec![0], 2, vec![1]);
let tx3 = create_multi_outputs_transaction(&tx1, vec![0], 2, vec![2]);
chain.gen_block_with_proposal_txs(vec![tx1.clone(), tx2.clone(), tx3.clone()], &mock_store);
chain.gen_empty_block(&mock_store);
chain.gen_block_with_commit_txs(vec![tx1, tx2], &mock_store, false);
chain.gen_block_with_commit_txs(vec![tx3], &mock_store, false);
for block in chain.blocks().iter().take(4) {
chain_controller
.process_block(Arc::new(block.clone()))
.expect("process block ok");
}
assert_error_eq!(
OutPointError::Unknown(OutPoint::new(tx1_hash, 0)),
chain_controller
.process_block(Arc::new(chain.blocks()[4].clone()))
.unwrap_err(),
);
}
#[test]
fn test_invalid_out_point_index_in_same_block() {
let (chain_controller, shared, parent) = start_chain(None);
let mock_store = MockStore::new(&parent, shared.store());
let mut chain = MockChain::new(parent, shared.consensus());
chain.gen_empty_block(&mock_store);
let last_cellbase = &shared.consensus().genesis_block().transactions()[1];
let tx1 = create_transaction(&last_cellbase.hash(), 1);
let tx1_hash = tx1.hash();
let tx2 = create_transaction(&tx1_hash, 2);
let tx3 = create_transaction_with_out_point(OutPoint::new(tx1_hash.clone(), 1), 3);
let txs = vec![tx1, tx2, tx3];
chain.gen_block_with_proposal_txs(txs.clone(), &mock_store);
chain.gen_empty_block(&mock_store);
chain.gen_block_with_commit_txs(txs, &mock_store, true);
for block in chain.blocks().iter().take(3) {
chain_controller
.process_block(Arc::new(block.clone()))
.expect("process block ok");
}
assert_error_eq!(
OutPointError::Unknown(OutPoint::new(tx1_hash, 1)),
chain_controller
.process_block(Arc::new(chain.blocks()[3].clone()))
.unwrap_err(),
);
}
#[test]
fn test_invalid_out_point_index_in_different_blocks() {
let (chain_controller, shared, parent) = start_chain(None);
let mock_store = MockStore::new(&parent, shared.store());
let mut chain = MockChain::new(parent, shared.consensus());
chain.gen_empty_block_with_nonce(100u128, &mock_store);
let last_cellbase = &shared.consensus().genesis_block().transactions()[1];
let tx1 = create_transaction(&last_cellbase.hash(), 1);
let tx1_hash = tx1.hash();
let tx2 = create_transaction(&tx1_hash, 2);
let tx3 = create_transaction_with_out_point(OutPoint::new(tx1_hash.clone(), 1), 3);
chain.gen_block_with_proposal_txs(vec![tx1.clone(), tx2.clone(), tx3.clone()], &mock_store);
chain.gen_empty_block_with_nonce(100u128, &mock_store);
chain.gen_block_with_commit_txs(vec![tx1, tx2], &mock_store, false);
chain.gen_block_with_commit_txs(vec![tx3], &mock_store, true);
for block in chain.blocks().iter().take(4) {
chain_controller
.process_block(Arc::new(block.clone()))
.expect("process block ok");
}
assert_error_eq!(
OutPointError::Unknown(OutPoint::new(tx1_hash, 1)),
chain_controller
.process_block(Arc::new(chain.blocks()[4].clone()))
.unwrap_err(),
);
}
#[test]
fn test_genesis_transaction_fetch() {
let tx = TransactionBuilder::default()
.witness(Script::default().into_witness())
.input(CellInput::new(OutPoint::null(), 0))
.outputs(vec![
CellOutputBuilder::default()
.capacity(capacity_bytes!(100_000_000).pack())
.lock(Script::default())
.build();
100
])
.outputs_data(vec![Bytes::new(); 100].pack())
.build();
let dao = genesis_dao_data(vec![&tx]).unwrap();
let root_hash = tx.hash();
let genesis_block = BlockBuilder::default()
.dao(dao)
.transaction(tx)
.compact_target(difficulty_to_compact(U256::from(1000u64)).pack())
.build();
let consensus = ConsensusBuilder::default()
.genesis_block(genesis_block)
.build();
let (_chain_controller, shared, _parent) = start_chain(Some(consensus));
let out_point = OutPoint::new(root_hash, 0);
let state = shared.snapshot().as_ref().cell(&out_point, false);
assert!(state.is_live());
}
#[test]
fn test_chain_fork_by_total_difficulty() {
let (chain_controller, shared, parent) = start_chain(None);
let final_number = 20;
let mock_store = MockStore::new(&parent, shared.store());
let mut chain1 = MockChain::new(parent.clone(), shared.consensus());
let mut chain2 = MockChain::new(parent, shared.consensus());
for _ in 0..final_number {
chain1.gen_empty_block_with_diff(100u64, &mock_store);
}
for i in 0..final_number {
let j = if i > 10 { 110 } else { 99 };
chain2.gen_empty_block_with_diff(j, &mock_store);
}
assert!(chain2.total_difficulty() > chain1.total_difficulty());
for block in chain1.blocks() {
chain_controller
.internal_process_block(Arc::new(block.clone()), Switch::DISABLE_ALL)
.expect("process block ok");
}
for block in chain2.blocks() {
chain_controller
.internal_process_block(Arc::new(block.clone()), Switch::DISABLE_ALL)
.expect("process block ok");
}
assert_eq!(
shared.store().get_block_hash(8),
chain2.blocks().get(7).map(|b| b.header().hash())
);
}
#[test]
fn test_chain_fork_by_first_received() {
let (chain_controller, shared, parent) = start_chain(None);
let final_number = 20;
let mock_store = MockStore::new(&parent, shared.store());
let mut chain1 = MockChain::new(parent.clone(), shared.consensus());
let mut chain2 = MockChain::new(parent.clone(), shared.consensus());
let mut chain3 = MockChain::new(parent, shared.consensus());
for _ in 0..final_number {
chain1.gen_empty_block_with_diff(100u64, &mock_store);
}
for _ in 0..(final_number * 2) {
chain2.gen_empty_block_with_diff(50u64, &mock_store);
}
for _ in 0..(final_number * 5) {
chain3.gen_empty_block_with_diff(20u64, &mock_store);
}
for chain in vec![chain1.clone(), chain2.clone(), chain3.clone()] {
for block in chain.blocks() {
chain_controller
.internal_process_block(Arc::new(block.clone()), Switch::DISABLE_ALL)
.expect("process block ok");
}
}
assert_eq!(chain1.total_difficulty(), chain2.total_difficulty());
assert_eq!(chain1.total_difficulty(), chain3.total_difficulty());
assert_eq!(
shared.store().get_block_hash(8),
chain1.blocks().get(7).map(|b| b.header().hash())
);
assert_eq!(
shared.store().get_block_hash(19),
chain1.blocks().get(18).map(|b| b.header().hash())
);
}
fn prepare_context_chain(
consensus: Consensus,
orphan_count: u64,
timestep: u64,
) -> (ChainController, Shared, HeaderView) {
let (chain_controller, shared, genesis) = start_chain(Some(consensus));
let final_number = shared.consensus().genesis_epoch_ext().length();
let mut chain1: Vec<BlockView> = Vec::new();
let mut chain2: Vec<BlockView> = Vec::new();
let mut parent = genesis.clone();
let mock_store = MockStore::new(&parent, shared.store());
for _ in 1..final_number - 1 {
let epoch = shared
.consensus()
.next_epoch_ext(&parent, &shared.store().as_data_provider())
.unwrap()
.epoch();
let transactions = vec![create_cellbase(&mock_store, shared.consensus(), &parent)];
let dao = dao_data(
shared.consensus(),
&parent,
&transactions,
&mock_store,
false,
);
let new_block = BlockBuilder::default()
.parent_hash(parent.hash())
.number((parent.number() + 1).pack())
.epoch(epoch.number_with_fraction(parent.number() + 1).pack())
.timestamp((parent.timestamp() + timestep).pack())
.compact_target(epoch.compact_target().pack())
.transactions(transactions)
.dao(dao)
.build();
chain_controller
.internal_process_block(Arc::new(new_block.clone()), Switch::DISABLE_ALL)
.expect("process block ok");
chain1.push(new_block.clone());
mock_store.insert_block(&new_block, &epoch);
parent = new_block.header().clone();
}
parent = genesis.clone();
for i in 1..final_number {
let epoch = shared
.consensus()
.next_epoch_ext(&parent, &shared.store().as_data_provider())
.unwrap()
.epoch();
let mut uncles = vec![];
if i < orphan_count {
uncles.push(chain1[i as usize].as_uncle());
}
let transactions = vec![create_cellbase(&mock_store, shared.consensus(), &parent)];
let dao = dao_data(
shared.consensus(),
&parent,
&transactions,
&mock_store,
false,
);
let new_block = BlockBuilder::default()
.parent_hash(parent.hash())
.uncles(uncles)
.number((parent.number() + 1).pack())
.epoch(epoch.number_with_fraction(parent.number() + 1).pack())
.timestamp((parent.timestamp() + timestep).pack())
.compact_target(epoch.compact_target().pack())
.transactions(transactions)
.dao(dao)
.build();
chain_controller
.internal_process_block(Arc::new(new_block.clone()), Switch::DISABLE_ALL)
.expect("process block ok");
chain2.push(new_block.clone());
mock_store.insert_block(&new_block, &epoch);
parent = new_block.header().clone();
}
(chain_controller, shared, genesis)
}
#[test]
fn test_epoch_hash_rate_dampening() {
let cellbase = TransactionBuilder::default()
.witness(Script::default().into_witness())
.input(CellInput::new(OutPoint::null(), 0))
.build();
let dao = genesis_dao_data(vec![&cellbase]).unwrap();
let genesis_block = BlockBuilder::default()
.compact_target(difficulty_to_compact(U256::from(1000u64)).pack())
.transaction(cellbase)
.dao(dao)
.build();
let mut consensus = ConsensusBuilder::default()
.genesis_block(genesis_block.clone())
.build();
consensus.genesis_epoch_ext.set_length(400);
consensus
.genesis_epoch_ext
.set_previous_epoch_hash_rate(U256::from(10u64));
let (_chain_controller, shared, _genesis) = prepare_context_chain(consensus, 26, 20_000);
{
let snapshot = shared.snapshot();
let tip = snapshot.tip_header().clone();
let total_uncles_count = snapshot
.get_block_ext(&tip.hash())
.unwrap()
.total_uncles_count;
assert_eq!(total_uncles_count, 25);
let epoch = snapshot
.consensus()
.next_epoch_ext(&tip, &snapshot.as_data_provider())
.unwrap()
.epoch();
assert_eq!(
epoch.previous_epoch_hash_rate(),
&U256::from(20u64),
"previous_epoch_hash_rate {}",
epoch.previous_epoch_hash_rate()
);
}
let mut consensus = ConsensusBuilder::default()
.genesis_block(genesis_block)
.build();
consensus.genesis_epoch_ext.set_length(400);
consensus
.genesis_epoch_ext
.set_previous_epoch_hash_rate(U256::from(200u64));
let (_chain_controller, shared, _genesis) = prepare_context_chain(consensus, 26, 20_000);
{
let snapshot = shared.snapshot();
let tip = snapshot.tip_header().clone();
let total_uncles_count = snapshot
.get_block_ext(&tip.hash())
.unwrap()
.total_uncles_count;
assert_eq!(total_uncles_count, 25);
let epoch = snapshot
.consensus()
.next_epoch_ext(&tip, &snapshot.as_data_provider())
.unwrap()
.epoch();
assert_eq!(
epoch.previous_epoch_hash_rate(),
&U256::from(100u64),
"previous_epoch_hash_rate {}",
epoch.previous_epoch_hash_rate()
);
}
}
#[test]
fn test_orphan_rate_estimation_overflow() {
let cellbase = TransactionBuilder::default()
.witness(Script::default().into_witness())
.input(CellInput::new(OutPoint::null(), 0))
.build();
let dao = genesis_dao_data(vec![&cellbase]).unwrap();
let genesis_block = BlockBuilder::default()
.compact_target(difficulty_to_compact(U256::from(1000u64)).pack())
.transaction(cellbase)
.dao(dao)
.build();
let mut consensus = ConsensusBuilder::default()
.genesis_block(genesis_block)
.build();
consensus.genesis_epoch_ext.set_length(400);
let (_chain_controller, shared, _genesis) = prepare_context_chain(consensus, 151, 2_000_000);
{
let snapshot = shared.snapshot();
let tip = snapshot.tip_header().clone();
let total_uncles_count = snapshot
.get_block_ext(&tip.hash())
.unwrap()
.total_uncles_count;
assert_eq!(total_uncles_count, 150);
let epoch = snapshot
.consensus()
.next_epoch_ext(&tip, &snapshot.as_data_provider())
.unwrap()
.epoch();
assert_eq!(epoch.length(), 300, "epoch length {}", epoch.length());
assert_eq!(
epoch.compact_target(),
difficulty_to_compact(U256::from(46u64)),
"epoch compact_target {}",
compact_to_difficulty(epoch.compact_target())
);
}
}
#[test]
fn test_next_epoch_ext() {
let cellbase = TransactionBuilder::default()
.witness(Script::default().into_witness())
.input(CellInput::new(OutPoint::null(), 0))
.build();
let dao = genesis_dao_data(vec![&cellbase]).unwrap();
let genesis_block = BlockBuilder::default()
.compact_target(difficulty_to_compact(U256::from(1000u64)).pack())
.transaction(cellbase)
.dao(dao)
.build();
let mut consensus = ConsensusBuilder::default()
.genesis_block(genesis_block)
.build();
let remember_primary_reward = consensus.genesis_epoch_ext.primary_reward();
consensus.genesis_epoch_ext.set_length(400);
consensus
.genesis_epoch_ext
.set_primary_reward(remember_primary_reward);
let (_chain_controller, shared, _genesis) =
prepare_context_chain(consensus.clone(), 13, 20_000);
{
let snapshot = shared.snapshot();
let tip = snapshot.tip_header().clone();
let total_uncles_count = snapshot
.get_block_ext(&tip.hash())
.unwrap()
.total_uncles_count;
assert_eq!(total_uncles_count, 12);
let epoch = snapshot
.consensus()
.next_epoch_ext(&tip, &snapshot.as_data_provider())
.unwrap()
.epoch();
assert_eq!(
epoch.previous_epoch_hash_rate(),
&U256::from(51u64),
"previous_epoch_hash_rate {}",
epoch.previous_epoch_hash_rate()
);
assert_eq!(epoch.length(), 604, "epoch length {}", epoch.length());
assert_eq!(
epoch.compact_target(),
difficulty_to_compact(U256::from(1186u64)),
"epoch compact_target {}",
compact_to_difficulty(epoch.compact_target())
);
let consensus = shared.consensus();
let epoch_reward = consensus.primary_epoch_reward(epoch.number());
let block_reward = Capacity::shannons(epoch_reward.as_u64() / epoch.length());
let block_reward_plus_one = Capacity::shannons(block_reward.as_u64() + 1);
let bound = 400 + epoch.remainder_reward().as_u64();
assert_eq!(epoch.block_reward(400).unwrap(), block_reward_plus_one);
assert_eq!(
epoch.block_reward(bound - 1).unwrap(),
block_reward_plus_one
);
assert_eq!(epoch.block_reward(bound).unwrap(), block_reward);
}
let (_chain_controller, shared, _genesis) = prepare_context_chain(consensus.clone(), 6, 20_000);
{
let snapshot = shared.snapshot();
let tip = snapshot.tip_header().clone();
let total_uncles_count = snapshot
.get_block_ext(&tip.hash())
.unwrap()
.total_uncles_count;
assert_eq!(total_uncles_count, 5);
let epoch = snapshot
.consensus()
.next_epoch_ext(&tip, &snapshot.as_data_provider())
.unwrap()
.epoch();
assert_eq!(epoch.length(), 800, "epoch length {}", epoch.length());
assert_eq!(
epoch.compact_target(),
difficulty_to_compact(U256::from(887u64)), "epoch compact_target {}",
compact_to_difficulty(epoch.compact_target())
);
}
let (_chain_controller, shared, _genesis) = prepare_context_chain(consensus, 151, 20_000);
{
let snapshot = shared.snapshot();
let tip = snapshot.tip_header().clone();
let total_uncles_count = snapshot
.get_block_ext(&tip.hash())
.unwrap()
.total_uncles_count;
assert_eq!(total_uncles_count, 150);
let epoch = snapshot
.consensus()
.next_epoch_ext(&tip, &snapshot.as_data_provider())
.unwrap()
.epoch();
assert_eq!(epoch.length(), 300, "epoch length {}", epoch.length());
assert_eq!(
epoch.compact_target(),
difficulty_to_compact(U256::from(2894u64)),
"epoch difficulty {}",
compact_to_difficulty(epoch.compact_target())
);
}
}