-
Notifications
You must be signed in to change notification settings - Fork 236
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
Showing
11 changed files
with
531 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.