Skip to content

Commit

Permalink
Merge #3090
Browse files Browse the repository at this point in the history
3090: fix: tx-pool refreshes caches with the incorrect VM version after hardfork r=quake,driftluo a=yangby-cryptape

### What problem does this PR solve?

Tx-pool refreshes caches with the incorrect VM version after hardfork if current tip block is the last block before hardfork.

#### Problem Summary

After #3058, all scripts will be verified under the environment of current tip block.

So when tip block is the last block before hardfork, the hardfork features are not enabled, that means all scripts will be verified with old features.

But in next block, all scripts should be verified with new hardfork features.

#### PR Summary

- test(hardfork): add a script and a unit test to test the change of the script's cycles after upgrade vm0 to vm1

  For test propose, I need a **fixed-cycles** lock script to `assert` and it should has different cycles in vm0 and vm1.
  The cycles of secp256k1 lock is not stable.

  This unit test also ensures the VM version is correct:
  https://github.com/nervosnetwork/ckb/blob/e8f88fd73451c526f37e0879a5ed41272dbd3020/script/src/types.rs#L57-L58

- fix(test): fix `MockChain` that it always panics in `get_block_epoch(..)` when epoch changed

- fix(hardfork): fix tx-pool that it refreshes caches with the incorrect VM version if it re-org to the last block before hardfork

  When the tip is the last block before hardfork, the transactions in block template should be verified with new hardfork features.

  So, we should always verify the transactions which are not committed, with the features for next block.

- test(hardfork): add a unit test for the tx-pool caches (for #3079)

- fix(hardfork): network should enable ckb2021 features when tip is the last block of ckb2019

### Check List

Tests

- Unit test

### Release note

```release-note
Title Only: Include only the PR title in the release note.
```



Co-authored-by: Boyu Yang <[email protected]>
  • Loading branch information
bors[bot] and yangby-cryptape authored Oct 18, 2021
2 parents 45572a8 + 43dd6b1 commit cab368f
Show file tree
Hide file tree
Showing 11 changed files with 531 additions and 21 deletions.
1 change: 1 addition & 0 deletions chain/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ mod load_input_data_hash_cell;
mod non_contextual_block_txs_verify;
mod reward;
mod truncate;
mod txs_verify_cache;
mod uncle;
mod util;
338 changes: 338 additions & 0 deletions chain/src/tests/txs_verify_cache.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,338 @@
use ckb_app_config::BlockAssemblerConfig;
use ckb_chain_spec::consensus::Consensus;
use ckb_dao_utils::genesis_dao_data;
use ckb_launcher::SharedBuilder;
use ckb_shared::Shared;
use ckb_store::ChainStore;
use ckb_test_chain_utils::always_success_cell;
use ckb_types::{
bytes,
core::{
capacity_bytes, hardfork::HardForkSwitch, BlockNumber, BlockView, Capacity, DepType,
EpochExt, EpochNumberWithFraction, HeaderView, ScriptHashType, TransactionView,
},
packed,
prelude::*,
utilities::difficulty_to_compact,
U256,
};
use ckb_verification_traits::Switch;
use faketime::unix_time_as_millis;
use lazy_static::lazy_static;

use std::{convert::TryInto as _, fs::File, io::Read as _, path::Path, sync::Arc};

use crate::{
chain::{ChainController, ChainService},
tests::util::dummy_network,
};

const CYCLES_IN_VM0: u64 = 696;
const CYCLES_IN_VM1: u64 = 686;

lazy_static! {
static ref LOCK_SCRIPT_CELL: (packed::CellOutput, bytes::Bytes, packed::Script) =
create_lock_script_cell();
}

fn create_lock_script_cell() -> (packed::CellOutput, bytes::Bytes, packed::Script) {
let mut buffer = Vec::new();
{
let file_path =
Path::new(env!("CARGO_MANIFEST_DIR")).join("../script/testdata/mop_adc_lock");
let mut file = File::open(file_path).unwrap();
file.read_to_end(&mut buffer).unwrap();
}

let data: bytes::Bytes = buffer.into();

let (_, _, always_success_script) = always_success_cell();
let cell = packed::CellOutput::new_builder()
.type_(Some(always_success_script.clone()).pack())
.capacity(Capacity::bytes(data.len()).unwrap().pack())
.build();

let script = packed::Script::new_builder()
.hash_type(ScriptHashType::Data.into())
.code_hash(packed::CellOutput::calc_data_hash(&data))
.build();

(cell, data, script)
}

fn lock_script_cell() -> &'static (packed::CellOutput, bytes::Bytes, packed::Script) {
&LOCK_SCRIPT_CELL
}

// Creates a consensus with:
// - Fixed epoch length.
// - Hardfork for feature RFC-0032.
// - A cell of a lock script which has different cycles in different VMs.
fn create_consensus() -> (
Consensus,
Vec<packed::CellDep>,
packed::Script,
TransactionView,
) {
let epoch_length = 10;
let epoch_when_enable_vm1 = 2;
let (always_success_cell, always_success_data, always_success_script) = always_success_cell();
let (lock_script_cell, lock_script_data, lock_script) = lock_script_cell();

let deploy_always_success_tx = TransactionView::new_advanced_builder()
.input(packed::CellInput::new_cellbase_input(0))
.output(always_success_cell.clone())
.output_data(always_success_data.pack())
.witness(always_success_script.clone().into_witness())
.build();
let always_success_cell_dep = {
let always_success_cell_op = packed::OutPoint::new(deploy_always_success_tx.hash(), 0);
packed::CellDep::new_builder()
.out_point(always_success_cell_op)
.dep_type(DepType::Code.into())
.build()
};
let deploy_lock_script_tx = TransactionView::new_advanced_builder()
.cell_dep(always_success_cell_dep)
.input(packed::CellInput::new_cellbase_input(0))
.output(lock_script_cell.clone())
.output_data(lock_script_data.pack())
.witness(lock_script.clone().into_witness())
.build();
let lock_script_cell_dep = {
let lock_script_cell_op = packed::OutPoint::new(deploy_lock_script_tx.hash(), 0);
packed::CellDep::new_builder()
.out_point(lock_script_cell_op)
.dep_type(DepType::Code.into())
.build()
};
let lock_script_via_type = {
let type_hash = always_success_script.calc_script_hash();
packed::Script::new_builder()
.code_hash(type_hash)
.hash_type(ScriptHashType::Type.into())
.build()
};
let input_tx = TransactionView::new_advanced_builder()
.cell_dep(lock_script_cell_dep.clone())
.input(packed::CellInput::new_cellbase_input(0))
.output(
packed::CellOutput::new_builder()
.capacity(capacity_bytes!(1_000_000).pack())
.lock(lock_script_via_type.clone())
.build(),
)
.output_data(Default::default())
.witness(Default::default())
.build();

let dao = genesis_dao_data(vec![&deploy_always_success_tx, &deploy_lock_script_tx]).unwrap();
let genesis = packed::Block::new_advanced_builder()
.timestamp(unix_time_as_millis().pack())
.compact_target(difficulty_to_compact(U256::from(100u64)).pack())
.dao(dao)
.transaction(deploy_always_success_tx)
.transaction(deploy_lock_script_tx)
.transaction(input_tx.clone())
.build();

let hardfork_switch = HardForkSwitch::new_without_any_enabled()
.as_builder()
.rfc_0032(epoch_when_enable_vm1)
.build()
.unwrap();
let mut consensus = Consensus {
permanent_difficulty_in_dummy: true,
hardfork_switch,
genesis_block: genesis,
cellbase_maturity: EpochNumberWithFraction::new(0, 0, 1),
..Default::default()
};
consensus.genesis_epoch_ext.set_length(epoch_length);

(
consensus,
vec![lock_script_cell_dep],
lock_script_via_type,
input_tx,
)
}

fn start_chain(consensus: Consensus, lock_script: &packed::Script) -> (ChainController, Shared) {
let hash_type: ScriptHashType = lock_script.hash_type().try_into().unwrap();
let config = BlockAssemblerConfig {
code_hash: lock_script.code_hash().unpack(),
args: Default::default(),
hash_type: hash_type.into(),
message: Default::default(),
use_binary_version_as_message_prefix: true,
binary_version: "TEST".to_string(),
};
let (shared, mut pack) = SharedBuilder::with_temp_db()
.consensus(consensus)
.block_assembler_config(Some(config))
.build()
.unwrap();

let network = dummy_network(&shared);
pack.take_tx_pool_builder().start(network);

let chain_service = ChainService::new(shared.clone(), pack.take_proposal_table());
let chain_controller = chain_service.start::<&str>(None);
(chain_controller, shared)
}

fn create_cellbase(
number: BlockNumber,
epoch: &EpochExt,
lock_script: &packed::Script,
) -> TransactionView {
TransactionView::new_advanced_builder()
.input(packed::CellInput::new_cellbase_input(number))
.output(
packed::CellOutput::new_builder()
.capacity(epoch.block_reward(number).unwrap().pack())
.lock(lock_script.clone())
.build(),
)
.output_data(Default::default())
.witness(Default::default())
.build()
}

fn generate_block(
parent_header: &HeaderView,
nonce: u128,
epoch: &EpochExt,
lock_script: &packed::Script,
) -> BlockView {
let number = parent_header.number() + 1;
let cellbase = create_cellbase(number, epoch, lock_script);
let dao = genesis_dao_data(vec![&cellbase]).unwrap();
let header = HeaderView::new_advanced_builder()
.parent_hash(parent_header.hash())
.timestamp((parent_header.timestamp() + 10).pack())
.number(number.pack())
.epoch(epoch.number_with_fraction(number).pack())
.compact_target(epoch.compact_target().pack())
.nonce(nonce.pack())
.dao(dao)
.build();
packed::Block::new_advanced_builder()
.header(header)
.transaction(cellbase)
.build_unchecked()
}

fn build_tx(
parent_tx: &TransactionView,
cell_deps: &[packed::CellDep],
lock_script: &packed::Script,
) -> TransactionView {
let input_op = packed::OutPoint::new(parent_tx.hash(), 0);
let input = packed::CellInput::new(input_op, 0);
let output_capacity = Capacity::shannons(parent_tx.output(0).unwrap().capacity().unpack())
.safe_sub(Capacity::bytes(1).unwrap())
.unwrap();
let output = packed::CellOutput::new_builder()
.capacity(output_capacity.pack())
.lock(lock_script.clone())
.build();
let mut tx_builder = TransactionView::new_advanced_builder();
for cell_dep in cell_deps {
tx_builder = tx_builder.cell_dep(cell_dep.clone());
}
tx_builder
.input(input)
.output(output)
.output_data(Default::default())
.build()
}

#[test]
fn refresh_txs_verify_cache_after_hardfork() {
let (consensus, cell_deps, lock_script, input_tx) = create_consensus();
let epoch_when_enable_vm1 = consensus.hardfork_switch.vm_version_1_and_syscalls_2();

let (chain_controller, shared) = start_chain(consensus, &lock_script);

// set to genesis header
let mut parent_header = shared
.store()
.get_block_header(&shared.store().get_block_hash(0).unwrap())
.unwrap();

let tx_pool = shared.tx_pool_controller();

let tx = build_tx(&input_tx, &cell_deps, &lock_script);
tx_pool.submit_local_tx(tx.clone()).unwrap().unwrap();

// at start of the test, the script should be ran with vm0
{
let raw_tx_pool = tx_pool.get_all_entry_info().unwrap();
let mut counter = 0;
loop {
if let Some(tx_entry) = raw_tx_pool.pending.get(&tx.hash()) {
assert_eq!(tx_entry.cycles, CYCLES_IN_VM0);
break;
}
// wait tx_pool if got `None`
counter += 1;
if counter > 100 {
panic!("tx-pool is too slow to refresh caches");
} else {
std::thread::sleep(std::time::Duration::from_millis(100));
}
}
}

for _ in 0..35 {
let epoch = shared
.consensus()
.next_epoch_ext(&parent_header, &shared.store().as_data_provider())
.unwrap()
.epoch();
let block = generate_block(&parent_header, 0, &epoch, &lock_script);

let cycles_expected = if epoch.number() >= epoch_when_enable_vm1 {
CYCLES_IN_VM1
} else {
CYCLES_IN_VM0
};
let mut counter = 0;
loop {
let raw_tx_pool = tx_pool.get_all_entry_info().unwrap();
if let Some(tx_entry) = raw_tx_pool.pending.get(&tx.hash()) {
assert_eq!(
tx_entry.cycles,
cycles_expected,
"block = {}, epoch = {}, cycles should be {}, but got {}",
block.number(),
epoch.number(),
cycles_expected,
tx_entry.cycles,
);
break;
}
// wait tx_pool if got `None`
counter += 1;
if counter > 100 {
panic!("tx-pool is too slow to refresh caches");
} else {
std::thread::sleep(std::time::Duration::from_millis(100));
}
}

chain_controller
.internal_process_block(Arc::new(block.clone()), Switch::ONLY_SCRIPT)
.expect("process block");
parent_header = block.header().to_owned();
}

// at last of the test, the script should be ran with vm1
{
let raw_tx_pool = tx_pool.get_all_entry_info().unwrap();
let tx_entry = raw_tx_pool.pending.get(&tx.hash()).unwrap();
assert_eq!(tx_entry.cycles, CYCLES_IN_VM1);
}
}
2 changes: 1 addition & 1 deletion script/src/ill_transaction_checker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ impl<'a> IllTransactionChecker<'a> {

/// Checks whether the transaction is ill formed.
pub fn check(&self) -> Result<(), ScriptError> {
let epoch_number = self.tx_env.current_epoch_number();
let epoch_number = self.tx_env.epoch_number_without_proposal_window();
let hardfork_switch = self.consensus.hardfork_switch();
// Assume that after ckb2021 is activated, developers will only upload code for vm v1.
if !hardfork_switch.is_vm_version_1_and_syscalls_2_enabled(epoch_number) {
Expand Down
2 changes: 1 addition & 1 deletion script/src/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,7 @@ impl<'a, DL: CellDataProvider + HeaderProvider> TransactionScriptsVerifier<'a, D
// it will cause proposal tx to start a new vm in the blocks before hardfork,
// destroying the assumption that the transaction execution only uses the old vm
// before hardfork, leading to unexpected network splits.
let epoch_number = self.tx_env.current_epoch_number();
let epoch_number = self.tx_env.epoch_number_without_proposal_window();
let hardfork_switch = self.consensus.hardfork_switch();
let is_vm_version_1_and_syscalls_2_enabled =
hardfork_switch.is_vm_version_1_and_syscalls_2_enabled(epoch_number);
Expand Down
Loading

0 comments on commit cab368f

Please sign in to comment.