From d1c0bbece4ac1bebdac968dbe494936010b160c2 Mon Sep 17 00:00:00 2001 From: Boyu Yang Date: Mon, 24 May 2021 03:03:05 +0800 Subject: [PATCH] feat(hardfork): allow unknown block versions and transactions versions --- rpc/src/module/chain.rs | 5 +- spec/src/consensus.rs | 96 +++++----- spec/src/hardfork.rs | 6 +- test/src/main.rs | 2 + test/src/specs/hardfork/v2021/mod.rs | 2 + test/src/specs/hardfork/v2021/version.rs | 181 ++++++++++++++++++ test/src/specs/rpc/get_block_template.rs | 2 +- test/src/util/check.rs | 28 ++- test/template/specs/integration.toml | 1 + tx-pool/src/block_assembler/mod.rs | 24 ++- tx-pool/src/process.rs | 5 + util/types/src/constants.rs | 8 - util/types/src/core/advanced_builders.rs | 20 +- util/types/src/core/error.rs | 12 +- util/types/src/core/hardfork.rs | 16 ++ util/types/src/lib.rs | 1 - verification/src/header_verifier.rs | 38 ++-- verification/src/tests/header_verifier.rs | 64 +++++-- .../src/tests/transaction_verifier.rs | 69 +++++-- verification/src/transaction_verifier.rs | 48 +++-- 20 files changed, 485 insertions(+), 143 deletions(-) create mode 100644 test/src/specs/hardfork/v2021/version.rs delete mode 100644 util/types/src/constants.rs diff --git a/rpc/src/module/chain.rs b/rpc/src/module/chain.rs index aac5f8e5ed..8361c1911d 100644 --- a/rpc/src/module/chain.rs +++ b/rpc/src/module/chain.rs @@ -1615,8 +1615,9 @@ impl ChainRpc for ChainRpcImpl { } fn get_consensus(&self) -> Result { - let consensus = self.shared.consensus().clone(); - Ok(consensus.into()) + let consensus = self.shared.consensus(); + let epoch_number = self.shared.snapshot().tip_header().epoch().number(); + Ok(consensus.to_json(epoch_number)) } fn get_block_median_time(&self, block_hash: H256) -> Result> { diff --git a/spec/src/consensus.rs b/spec/src/consensus.rs index 2bf066b662..ab54d5f98a 100644 --- a/spec/src/consensus.rs +++ b/spec/src/consensus.rs @@ -14,7 +14,6 @@ use ckb_resource::Resource; use ckb_traits::{BlockEpoch, EpochProvider}; use ckb_types::{ bytes::Bytes, - constants::{BLOCK_VERSION, TX_VERSION}, core::{ hardfork::HardForkSwitch, BlockBuilder, BlockNumber, BlockView, Capacity, Cycle, EpochExt, EpochNumber, EpochNumberWithFraction, HeaderView, Ratio, TransactionBuilder, @@ -261,8 +260,6 @@ impl ConsensusBuilder { secp256k1_blake160_sighash_all_type_hash: None, secp256k1_blake160_multisig_all_type_hash: None, genesis_epoch_ext, - block_version: BLOCK_VERSION, - tx_version: TX_VERSION, type_id_code_hash: TYPE_ID_CODE_HASH, proposer_reward_ratio: PROPOSER_REWARD_RATIO, max_block_proposals_limit: MAX_BLOCK_PROPOSALS_LIMIT, @@ -513,10 +510,6 @@ pub struct Consensus { pub max_block_cycles: Cycle, /// Maximum number of bytes to use for the entire block pub max_block_bytes: u64, - /// The block version number supported - pub block_version: Version, - /// The tx version number supported - pub tx_version: Version, /// The "TYPE_ID" in hex pub type_id_code_hash: H256, /// The Limit to the number of proposals per block @@ -694,13 +687,13 @@ impl Consensus { } /// The current block version - pub fn block_version(&self) -> Version { - self.block_version + pub fn block_version(&self, _epoch_number: EpochNumber) -> Version { + 0 } /// The current transaction version - pub fn tx_version(&self) -> Version { - self.tx_version + pub fn tx_version(&self, _epoch_number: EpochNumber) -> Version { + 0 } /// The "TYPE_ID" in hex @@ -930,6 +923,46 @@ impl Consensus { pub fn hardfork_switch(&self) -> &HardForkSwitch { &self.hardfork_switch } + + /// Convert to a JSON type with an input epoch number as the tip epoch number. + pub fn to_json(&self, epoch_number: EpochNumber) -> ckb_jsonrpc_types::Consensus { + ckb_jsonrpc_types::Consensus { + id: self.id.clone(), + genesis_hash: self.genesis_hash.unpack(), + dao_type_hash: self.dao_type_hash().map(|h| h.unpack()), + secp256k1_blake160_sighash_all_type_hash: self + .secp256k1_blake160_sighash_all_type_hash() + .map(|h| h.unpack()), + secp256k1_blake160_multisig_all_type_hash: self + .secp256k1_blake160_multisig_all_type_hash() + .map(|h| h.unpack()), + initial_primary_epoch_reward: self.initial_primary_epoch_reward.into(), + secondary_epoch_reward: self.secondary_epoch_reward.into(), + max_uncles_num: (self.max_uncles_num as u64).into(), + orphan_rate_target: self.orphan_rate_target().to_owned(), + epoch_duration_target: self.epoch_duration_target.into(), + tx_proposal_window: ckb_jsonrpc_types::ProposalWindow { + closest: self.tx_proposal_window.0.into(), + farthest: self.tx_proposal_window.1.into(), + }, + proposer_reward_ratio: RationalU256::new_raw( + self.proposer_reward_ratio.numer().into(), + self.proposer_reward_ratio.denom().into(), + ), + cellbase_maturity: self.cellbase_maturity.into(), + median_time_block_count: (self.median_time_block_count as u64).into(), + max_block_cycles: self.max_block_cycles.into(), + max_block_bytes: self.max_block_bytes.into(), + block_version: self.block_version(epoch_number).into(), + tx_version: self.tx_version(epoch_number).into(), + type_id_code_hash: self.type_id_code_hash().to_owned(), + max_block_proposals_limit: self.max_block_proposals_limit.into(), + primary_epoch_reward_halving_interval: self + .primary_epoch_reward_halving_interval + .into(), + permanent_difficulty_in_dummy: self.permanent_difficulty_in_dummy, + } + } } /// Trait for consensus provider. @@ -961,47 +994,6 @@ impl NextBlockEpoch { } } -impl From for ckb_jsonrpc_types::Consensus { - fn from(consensus: Consensus) -> Self { - Self { - id: consensus.id, - genesis_hash: consensus.genesis_hash.unpack(), - dao_type_hash: consensus.dao_type_hash.map(|h| h.unpack()), - secp256k1_blake160_sighash_all_type_hash: consensus - .secp256k1_blake160_sighash_all_type_hash - .map(|h| h.unpack()), - secp256k1_blake160_multisig_all_type_hash: consensus - .secp256k1_blake160_multisig_all_type_hash - .map(|h| h.unpack()), - initial_primary_epoch_reward: consensus.initial_primary_epoch_reward.into(), - secondary_epoch_reward: consensus.secondary_epoch_reward.into(), - max_uncles_num: (consensus.max_uncles_num as u64).into(), - orphan_rate_target: consensus.orphan_rate_target, - epoch_duration_target: consensus.epoch_duration_target.into(), - tx_proposal_window: ckb_jsonrpc_types::ProposalWindow { - closest: consensus.tx_proposal_window.0.into(), - farthest: consensus.tx_proposal_window.1.into(), - }, - proposer_reward_ratio: RationalU256::new_raw( - consensus.proposer_reward_ratio.numer().into(), - consensus.proposer_reward_ratio.denom().into(), - ), - cellbase_maturity: consensus.cellbase_maturity.into(), - median_time_block_count: (consensus.median_time_block_count as u64).into(), - max_block_cycles: consensus.max_block_cycles.into(), - max_block_bytes: consensus.max_block_bytes.into(), - block_version: consensus.block_version.into(), - tx_version: consensus.tx_version.into(), - type_id_code_hash: consensus.type_id_code_hash, - max_block_proposals_limit: consensus.max_block_proposals_limit.into(), - primary_epoch_reward_halving_interval: consensus - .primary_epoch_reward_halving_interval - .into(), - permanent_difficulty_in_dummy: consensus.permanent_difficulty_in_dummy, - } - } -} - // most simple and efficient way for now fn u256_low_u64(u: U256) -> u64 { u.0[0] diff --git a/spec/src/hardfork.rs b/spec/src/hardfork.rs index 091e298969..6a4aae808f 100644 --- a/spec/src/hardfork.rs +++ b/spec/src/hardfork.rs @@ -16,6 +16,8 @@ pub struct HardForkConfig { pub rfc_pr_0221: Option, /// Ref: [CKB RFC xxxx](https://github.com/nervosnetwork/rfcs/tree/master/rfcs/xxxx-rfc-title) pub rfc_pr_0223: Option, + /// Ref: [CKB RFC xxxx](https://github.com/nervosnetwork/rfcs/tree/master/rfcs/xxxx-rfc-title) + pub rfc_pr_0230: Option, } macro_rules! check_default { @@ -60,7 +62,8 @@ impl HardForkConfig { ) -> Result { let builder = builder .rfc_pr_0221(check_default!(self, rfc_pr_0221, ckb2021)) - .rfc_pr_0223(check_default!(self, rfc_pr_0223, ckb2021)); + .rfc_pr_0223(check_default!(self, rfc_pr_0223, ckb2021)) + .rfc_pr_0230(check_default!(self, rfc_pr_0230, ckb2021)); Ok(builder) } @@ -71,6 +74,7 @@ impl HardForkConfig { HardForkSwitch::new_builder() .rfc_pr_0221(self.rfc_pr_0221.unwrap_or(default)) .rfc_pr_0223(self.rfc_pr_0223.unwrap_or(default)) + .rfc_pr_0230(self.rfc_pr_0230.unwrap_or(default)) .build() } } diff --git a/test/src/main.rs b/test/src/main.rs index fd41842e4c..5a37742095 100644 --- a/test/src/main.rs +++ b/test/src/main.rs @@ -491,6 +491,8 @@ fn all_specs() -> Vec> { // Test hard fork features Box::new(CheckAbsoluteEpochSince), Box::new(CheckRelativeEpochSince), + Box::new(CheckBlockVersion), + Box::new(CheckTxVersion), ]; specs.shuffle(&mut thread_rng()); specs diff --git a/test/src/specs/hardfork/v2021/mod.rs b/test/src/specs/hardfork/v2021/mod.rs index 42bdaf1706..d820330f6a 100644 --- a/test/src/specs/hardfork/v2021/mod.rs +++ b/test/src/specs/hardfork/v2021/mod.rs @@ -1,3 +1,5 @@ mod since; +mod version; pub use since::{CheckAbsoluteEpochSince, CheckRelativeEpochSince}; +pub use version::{CheckBlockVersion, CheckTxVersion}; diff --git a/test/src/specs/hardfork/v2021/version.rs b/test/src/specs/hardfork/v2021/version.rs new file mode 100644 index 0000000000..f2451b8d36 --- /dev/null +++ b/test/src/specs/hardfork/v2021/version.rs @@ -0,0 +1,181 @@ +use crate::util::{ + check::{assert_epoch_should_be, assert_submit_block_fail, assert_submit_block_ok}, + mining::{mine, mine_until_epoch, mine_until_out_bootstrap_period}, +}; +use crate::utils::assert_send_transaction_fail; +use crate::{Node, Spec}; +use ckb_logger::info; +use ckb_types::{ + core::{BlockView, TransactionView, Version}, + packed, + prelude::*, +}; + +const GENESIS_EPOCH_LENGTH: u64 = 10; + +const ERROR_BLOCK_VERSION: &str = "Invalid: Header(Version(BlockVersionError("; +const ERROR_TX_VERSION: &str = + "TransactionFailedToVerify: Verification failed Transaction(MismatchedVersion"; + +pub struct CheckBlockVersion; +pub struct CheckTxVersion; + +impl Spec for CheckBlockVersion { + fn run(&self, nodes: &mut Vec) { + let node = &nodes[0]; + let epoch_length = GENESIS_EPOCH_LENGTH; + + mine_until_out_bootstrap_period(node); + + assert_epoch_should_be(node, 1, 2, epoch_length); + { + info!("CKB v2019, submit block with version 1 is failed"); + let block = create_block_with_version(node, 1); + assert_submit_block_fail(node, &block, ERROR_BLOCK_VERSION); + } + assert_epoch_should_be(node, 1, 2, epoch_length); + { + info!("CKB v2019, submit block with version 0 is passed"); + let block = create_block_with_version(node, 0); + assert_submit_block_ok(node, &block); + } + assert_epoch_should_be(node, 1, 3, epoch_length); + mine_until_epoch(node, 1, epoch_length - 2, epoch_length); + { + info!("CKB v2019, submit block with version 1 is failed (boundary)"); + let block = create_block_with_version(node, 1); + assert_submit_block_fail(node, &block, ERROR_BLOCK_VERSION); + } + assert_epoch_should_be(node, 1, epoch_length - 2, epoch_length); + { + info!("CKB v2019, submit block with version 0 is passed (boundary)"); + let block = create_block_with_version(node, 0); + assert_submit_block_ok(node, &block); + } + assert_epoch_should_be(node, 1, epoch_length - 1, epoch_length); + { + info!("CKB v2021, submit block with version 1 is passed (boundary)"); + let block = create_block_with_version(node, 1); + assert_submit_block_ok(node, &block); + } + assert_epoch_should_be(node, 2, 0, epoch_length); + { + info!("CKB v2021, submit block with version 0 is passed (boundary)"); + let block = create_block_with_version(node, 0); + assert_submit_block_ok(node, &block); + } + assert_epoch_should_be(node, 2, 1, epoch_length); + } + + fn modify_chain_spec(&self, spec: &mut ckb_chain_spec::ChainSpec) { + spec.params.permanent_difficulty_in_dummy = Some(true); + spec.params.genesis_epoch_length = Some(GENESIS_EPOCH_LENGTH); + if let Some(mut switch) = spec.params.hardfork.as_mut() { + switch.rfc_pr_0230 = Some(2); + } + } +} + +impl Spec for CheckTxVersion { + fn run(&self, nodes: &mut Vec) { + let node = &nodes[0]; + let epoch_length = GENESIS_EPOCH_LENGTH; + + mine_until_out_bootstrap_period(node); + + assert_epoch_should_be(node, 1, 2, epoch_length); + { + let input_cell_hash = &node.get_tip_block().transactions()[0].hash(); + + info!("CKB v2019, submit transaction with version 1 is failed"); + let tx = create_transaction_with_version(node, input_cell_hash.clone(), 0, 1); + assert_send_transaction_fail(node, &tx, ERROR_TX_VERSION); + + info!("CKB v2019, submit block with version 0 is passed"); + let tx = create_transaction_with_version(node, input_cell_hash.clone(), 0, 0); + let res = node.rpc_client().send_transaction_result(tx.data().into()); + assert!(res.is_ok(), "result: {:?}", res.unwrap_err()); + } + mine_until_epoch(node, 1, epoch_length - 4, epoch_length); + { + let input_cell_hash = &node.get_tip_block().transactions()[0].hash(); + + info!("CKB v2019, submit transaction with version 1 is failed (boundary)"); + let tx = create_transaction_with_version(node, input_cell_hash.clone(), 0, 1); + assert_send_transaction_fail(node, &tx, ERROR_TX_VERSION); + + info!("CKB v2019, submit block with version 0 is passed (boundary)"); + let tx = create_transaction_with_version(node, input_cell_hash.clone(), 0, 0); + let res = node.rpc_client().send_transaction_result(tx.data().into()); + assert!(res.is_ok(), "result: {:?}", res.unwrap_err()); + } + mine(node, 1); + assert_epoch_should_be(node, 1, epoch_length - 3, epoch_length); + { + let input_cell_hash = &node.get_tip_block().transactions()[0].hash(); + info!("CKB v2021, submit transaction with version 1 is passed (boundary)"); + let tx = create_transaction_with_version(node, input_cell_hash.clone(), 0, 1); + let res = node.rpc_client().send_transaction_result(tx.data().into()); + assert!(res.is_ok(), "result: {:?}", res.unwrap_err()); + + let input_cell_hash = &tx.hash(); + info!("CKB v2021, submit block with version 0 is passed (boundary)"); + let tx = create_transaction_with_version(node, input_cell_hash.clone(), 0, 0); + let res = node.rpc_client().send_transaction_result(tx.data().into()); + assert!(res.is_ok(), "result: {:?}", res.unwrap_err()); + + let input_cell_hash = &tx.hash(); + info!("CKB v2021, submit transaction with version 100 is passed (boundary)"); + let tx = create_transaction_with_version(node, input_cell_hash.clone(), 0, 100); + let res = node.rpc_client().send_transaction_result(tx.data().into()); + assert!(res.is_ok(), "result: {:?}", res.unwrap_err()); + } + } + + fn modify_chain_spec(&self, spec: &mut ckb_chain_spec::ChainSpec) { + spec.params.permanent_difficulty_in_dummy = Some(true); + spec.params.genesis_epoch_length = Some(GENESIS_EPOCH_LENGTH); + if let Some(mut switch) = spec.params.hardfork.as_mut() { + switch.rfc_pr_0230 = Some(2); + } + } +} + +fn create_block_with_version(node: &Node, version: Version) -> BlockView { + node.new_block_builder(None, None, None) + .version(version.pack()) + .build() +} + +fn create_transaction_with_version( + node: &Node, + hash: packed::Byte32, + index: u32, + version: Version, +) -> TransactionView { + let always_success_cell_dep = node.always_success_cell_dep(); + let always_success_script = node.always_success_script(); + + let input_cell = node + .rpc_client() + .get_transaction(hash.clone()) + .unwrap() + .transaction + .inner + .outputs[index as usize] + .to_owned(); + + let cell_input = packed::CellInput::new(packed::OutPoint::new(hash, index), 0); + let cell_output = packed::CellOutput::new_builder() + .capacity((input_cell.capacity.value() - 1).pack()) + .lock(always_success_script) + .build(); + + TransactionView::new_advanced_builder() + .version(version.pack()) + .cell_dep(always_success_cell_dep) + .input(cell_input) + .output(cell_output) + .output_data(Default::default()) + .build() +} diff --git a/test/src/specs/rpc/get_block_template.rs b/test/src/specs/rpc/get_block_template.rs index 4b6070e695..50b58842b6 100644 --- a/test/src/specs/rpc/get_block_template.rs +++ b/test/src/specs/rpc/get_block_template.rs @@ -12,7 +12,7 @@ impl Spec for RpcGetBlockTemplate { let node0 = &nodes[0]; let default_bytes_limit = node0.consensus().max_block_bytes; let default_cycles_limit = node0.consensus().max_block_cycles; - let default_block_version = node0.consensus().block_version; + let default_block_version = node0.consensus().block_version(0); let epoch_length = node0.consensus().genesis_epoch_ext().length(); // get block template when tip block is genesis diff --git a/test/src/util/check.rs b/test/src/util/check.rs index 827c2b425a..a52e6bb4bb 100644 --- a/test/src/util/check.rs +++ b/test/src/util/check.rs @@ -1,6 +1,6 @@ use crate::Node; use ckb_jsonrpc_types::Status; -use ckb_types::core::{EpochNumberWithFraction, HeaderView, TransactionView}; +use ckb_types::core::{BlockView, EpochNumberWithFraction, HeaderView, TransactionView}; pub fn is_transaction_pending(node: &Node, transaction: &TransactionView) -> bool { node.rpc_client() @@ -39,3 +39,29 @@ pub fn assert_epoch_should_be(node: &Node, number: u64, index: u64, length: u64) tip_epoch, target_epoch ); } + +pub fn assert_submit_block_fail(node: &Node, block: &BlockView, message: &str) { + let result = node + .rpc_client() + .submit_block("".to_owned(), block.data().into()); + assert!( + result.is_err(), + "expect error \"{}\" but got \"Ok(())\"", + message, + ); + let error = result.expect_err(&format!("block is invalid since {}", message)); + let error_string = error.to_string(); + assert!( + error_string.contains(message), + "expect error \"{}\" but got \"{}\"", + message, + error_string, + ); +} + +pub fn assert_submit_block_ok(node: &Node, block: &BlockView) { + let result = node + .rpc_client() + .submit_block("".to_owned(), block.data().into()); + assert!(result.is_ok(), "expect \"Ok(())\" but got \"{:?}\"", result,); +} diff --git a/test/template/specs/integration.toml b/test/template/specs/integration.toml index 15b3abf35c..be25857be0 100644 --- a/test/template/specs/integration.toml +++ b/test/template/specs/integration.toml @@ -71,6 +71,7 @@ genesis_epoch_length = 1000 [params.hardfork] rfc_pr_0221 = 9_223_372_036_854_775_807 rfc_pr_0223 = 9_223_372_036_854_775_807 +rfc_pr_0230 = 9_223_372_036_854_775_807 [pow] func = "Dummy" diff --git a/tx-pool/src/block_assembler/mod.rs b/tx-pool/src/block_assembler/mod.rs index 2872dec7af..0b28d2bf8a 100644 --- a/tx-pool/src/block_assembler/mod.rs +++ b/tx-pool/src/block_assembler/mod.rs @@ -13,8 +13,8 @@ use ckb_store::ChainStore; use ckb_types::{ bytes::Bytes, core::{ - BlockNumber, Capacity, Cycle, EpochExt, HeaderView, TransactionBuilder, TransactionView, - UncleBlockView, Version, + BlockNumber, Capacity, Cycle, EpochExt, EpochNumber, HeaderView, TransactionBuilder, + TransactionView, UncleBlockView, Version, }, packed::{self, Byte32, CellInput, CellOutput, CellbaseWitness, ProposalShortId, Transaction}, prelude::*, @@ -73,16 +73,22 @@ impl BlockAssembler { bytes_limit: Option, proposals_limit: Option, max_version: Option, + epoch_number: EpochNumber, ) -> (u64, u64, Version) { - let bytes_limit = bytes_limit - .min(Some(consensus.max_block_bytes())) - .unwrap_or_else(|| consensus.max_block_bytes()); + let bytes_limit = { + let default_bytes_limit = consensus.max_block_bytes(); + bytes_limit + .min(Some(default_bytes_limit)) + .unwrap_or(default_bytes_limit) + }; + let default_proposals_limit = consensus.max_block_proposals_limit(); let proposals_limit = proposals_limit - .min(Some(consensus.max_block_proposals_limit())) - .unwrap_or_else(|| consensus.max_block_proposals_limit()); + .min(Some(default_proposals_limit)) + .unwrap_or(default_proposals_limit); + let default_block_version = consensus.block_version(epoch_number); let version = max_version - .min(Some(consensus.block_version())) - .unwrap_or_else(|| consensus.block_version()); + .min(Some(default_block_version)) + .unwrap_or(default_block_version); (bytes_limit, proposals_limit, version) } diff --git a/tx-pool/src/process.rs b/tx-pool/src/process.rs index 2e32f47c1f..de025e5994 100644 --- a/tx-pool/src/process.rs +++ b/tx-pool/src/process.rs @@ -296,11 +296,16 @@ impl TxPoolService { let snapshot = self.snapshot(); let consensus = snapshot.consensus(); let cycles_limit = consensus.max_block_cycles(); + let epoch_number_of_next_block = snapshot + .tip_header() + .epoch() + .minimum_epoch_number_after_n_blocks(1); let (bytes_limit, proposals_limit, version) = BlockAssembler::transform_params( consensus, bytes_limit, proposals_limit, max_version, + epoch_number_of_next_block, ); if let Some(cache) = self diff --git a/util/types/src/constants.rs b/util/types/src/constants.rs deleted file mode 100644 index 44d5527897..0000000000 --- a/util/types/src/constants.rs +++ /dev/null @@ -1,8 +0,0 @@ -//! All Constants. - -use crate::core::Version; - -/// Current transaction version. -pub const TX_VERSION: Version = 0; -/// Current block version. -pub const BLOCK_VERSION: Version = 0; diff --git a/util/types/src/core/advanced_builders.rs b/util/types/src/core/advanced_builders.rs index 4c0e3e7324..6024c7208d 100644 --- a/util/types/src/core/advanced_builders.rs +++ b/util/types/src/core/advanced_builders.rs @@ -1,7 +1,7 @@ //! Advanced builders for Transaction(View), Header(View) and Block(View). use crate::{ - constants, core, packed, + core, packed, prelude::*, utilities::{merkle_root, DIFF_TWO}, }; @@ -16,7 +16,7 @@ use crate::{ /// /// [`TransactionView`]: struct.TransactionView.html /// [`packed::TransactionBuilder`]: ../packed/struct.TransactionBuilder.html -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct TransactionBuilder { pub(crate) version: packed::Uint32, pub(crate) cell_deps: Vec, @@ -69,24 +69,10 @@ pub struct BlockBuilder { * Implement std traits. */ -impl ::std::default::Default for TransactionBuilder { - fn default() -> Self { - Self { - version: constants::TX_VERSION.pack(), - cell_deps: Default::default(), - header_deps: Default::default(), - inputs: Default::default(), - outputs: Default::default(), - witnesses: Default::default(), - outputs_data: Default::default(), - } - } -} - impl ::std::default::Default for HeaderBuilder { fn default() -> Self { Self { - version: constants::BLOCK_VERSION.pack(), + version: Default::default(), parent_hash: Default::default(), timestamp: Default::default(), number: Default::default(), diff --git a/util/types/src/core/error.rs b/util/types/src/core/error.rs index 24d19cbcdd..bb86de3854 100644 --- a/util/types/src/core/error.rs +++ b/util/types/src/core/error.rs @@ -159,6 +159,15 @@ pub enum TransactionError { actual: Version, }, + /// The transaction version is too low, it's deprecated. + #[error("DeprecatedVersion: minimum {}, got {}", minimum, actual)] + DeprecatedVersion { + /// The minimum supported transaction version. + minimum: Version, + /// The actual transaction version. + actual: Version, + }, + /// The transaction size exceeds limit. #[error("ExceededMaximumBlockBytes: expected transaction serialized size ({actual}) < block size limit ({limit})")] ExceededMaximumBlockBytes { @@ -186,7 +195,8 @@ impl TransactionError { TransactionError::Immature { .. } | TransactionError::CellbaseImmaturity { .. } - | TransactionError::MismatchedVersion { .. } => false, + | TransactionError::MismatchedVersion { .. } + | TransactionError::DeprecatedVersion { .. } => false, } } } diff --git a/util/types/src/core/hardfork.rs b/util/types/src/core/hardfork.rs index 64631e4294..e56a056398 100644 --- a/util/types/src/core/hardfork.rs +++ b/util/types/src/core/hardfork.rs @@ -96,6 +96,7 @@ macro_rules! define_methods { pub struct HardForkSwitch { rfc_pr_0221: EpochNumber, rfc_pr_0223: EpochNumber, + rfc_pr_0230: EpochNumber, } /// Builder for [`HardForkSwitch`]. @@ -114,6 +115,10 @@ pub struct HardForkSwitchBuilder { /// /// Ref: [CKB RFC xxxx](https://github.com/nervosnetwork/rfcs/tree/master/rfcs/xxxx-rfc-title) pub rfc_pr_0223: Option, + /// Allow unknown block versions and transactions versions. + /// + /// Ref: [CKB RFC xxxx](https://github.com/nervosnetwork/rfcs/tree/master/rfcs/xxxx-rfc-title) + pub rfc_pr_0230: Option, } impl HardForkSwitch { @@ -127,6 +132,7 @@ impl HardForkSwitch { Self::new_builder() .rfc_pr_0221(self.rfc_pr_0221()) .rfc_pr_0223(self.rfc_pr_0223()) + .rfc_pr_0230(self.rfc_pr_0230()) } /// Creates a new instance that all hard fork features are disabled forever. @@ -135,6 +141,7 @@ impl HardForkSwitch { Self::new_builder() .disable_rfc_pr_0221() .disable_rfc_pr_0223() + .disable_rfc_pr_0230() .build() .unwrap() } @@ -154,6 +161,13 @@ define_methods!( disable_rfc_pr_0223, "RFC PR 0223" ); +define_methods!( + rfc_pr_0230, + allow_unknown_versions, + is_allow_unknown_versions_enabled, + disable_rfc_pr_0230, + "RFC PR 0230" +); impl HardForkSwitchBuilder { /// Build a new [`HardForkSwitch`]. @@ -172,9 +186,11 @@ impl HardForkSwitchBuilder { } let rfc_pr_0221 = try_find!(rfc_pr_0221); let rfc_pr_0223 = try_find!(rfc_pr_0223); + let rfc_pr_0230 = try_find!(rfc_pr_0230); Ok(HardForkSwitch { rfc_pr_0221, rfc_pr_0223, + rfc_pr_0230, }) } } diff --git a/util/types/src/lib.rs b/util/types/src/lib.rs index 1e2fc209ad..7849f7cf19 100644 --- a/util/types/src/lib.rs +++ b/util/types/src/lib.rs @@ -14,7 +14,6 @@ mod generated; pub use generated::packed; pub mod core; -pub mod constants; mod conversion; mod extension; pub mod utilities; diff --git a/verification/src/header_verifier.rs b/verification/src/header_verifier.rs index ee34525dc1..ba93bd43ba 100644 --- a/verification/src/header_verifier.rs +++ b/verification/src/header_verifier.rs @@ -6,7 +6,7 @@ use ckb_chain_spec::consensus::Consensus; use ckb_error::Error; use ckb_pow::PowEngine; use ckb_traits::HeaderProvider; -use ckb_types::core::{HeaderView, Version}; +use ckb_types::core::HeaderView; use ckb_verification_traits::Verifier; use faketime::unix_time_as_millis; @@ -31,7 +31,7 @@ impl<'a, DL: HeaderProvider> HeaderVerifier<'a, DL> { impl<'a, DL: HeaderProvider> Verifier for HeaderVerifier<'a, DL> { type Target = HeaderView; fn verify(&self, header: &Self::Target) -> Result<(), Error> { - VersionVerifier::new(header, self.consensus.block_version()).verify()?; + VersionVerifier::new(header, self.consensus).verify()?; // POW check first PowVerifier::new(header, self.consensus.pow_engine().as_ref()).verify()?; let parent = self @@ -53,26 +53,36 @@ impl<'a, DL: HeaderProvider> Verifier for HeaderVerifier<'a, DL> { pub struct VersionVerifier<'a> { header: &'a HeaderView, - block_version: Version, + consensus: &'a Consensus, } impl<'a> VersionVerifier<'a> { - pub fn new(header: &'a HeaderView, block_version: Version) -> Self { - VersionVerifier { - header, - block_version, - } + pub fn new(header: &'a HeaderView, consensus: &'a Consensus) -> Self { + VersionVerifier { header, consensus } } pub fn verify(&self) -> Result<(), Error> { - if self.header.version() != self.block_version { - return Err(BlockVersionError { - expected: self.block_version, - actual: self.header.version(), + let epoch_number = self.header.epoch().number(); + let target = self.consensus.block_version(epoch_number); + let actual = self.header.version(); + let failed = if self + .consensus + .hardfork_switch() + .is_allow_unknown_versions_enabled(epoch_number) + { + actual < target + } else { + actual != target + }; + if failed { + Err(BlockVersionError { + expected: target, + actual, } - .into()); + .into()) + } else { + Ok(()) } - Ok(()) } } diff --git a/verification/src/tests/header_verifier.rs b/verification/src/tests/header_verifier.rs index 3a1cbf8f32..5eaed40491 100644 --- a/verification/src/tests/header_verifier.rs +++ b/verification/src/tests/header_verifier.rs @@ -1,9 +1,14 @@ use crate::header_verifier::{NumberVerifier, PowVerifier, TimestampVerifier, VersionVerifier}; use crate::{BlockVersionError, NumberError, PowError, TimestampError, ALLOWED_FUTURE_BLOCKTIME}; +use ckb_chain_spec::consensus::ConsensusBuilder; use ckb_error::assert_error_eq; use ckb_pow::PowEngine; use ckb_test_chain_utils::{MockMedianTime, MOCK_MEDIAN_TIME_COUNT}; -use ckb_types::{constants::BLOCK_VERSION, core::HeaderBuilder, packed::Header, prelude::*}; +use ckb_types::{ + core::{hardfork::HardForkSwitch, EpochNumberWithFraction, HeaderBuilder}, + packed::Header, + prelude::*, +}; use faketime::unix_time_as_millis; fn mock_median_time_context() -> MockMedianTime { @@ -14,18 +19,53 @@ fn mock_median_time_context() -> MockMedianTime { #[test] pub fn test_version() { - let header = HeaderBuilder::default() - .version((BLOCK_VERSION + 1).pack()) + let fork_at = 10; + let default_block_version = ConsensusBuilder::default().build().block_version(fork_at); + let epoch = EpochNumberWithFraction::new(fork_at, 0, 10); + let header1 = HeaderBuilder::default() + .version(default_block_version.pack()) + .epoch(epoch.pack()) .build(); - let verifier = VersionVerifier::new(&header, BLOCK_VERSION); - - assert_error_eq!( - verifier.verify().unwrap_err(), - BlockVersionError { - expected: BLOCK_VERSION, - actual: BLOCK_VERSION + 1 - } - ); + let header2 = HeaderBuilder::default() + .version((default_block_version + 1).pack()) + .epoch(epoch.pack()) + .build(); + { + let hardfork_switch = HardForkSwitch::new_without_any_enabled() + .as_builder() + .rfc_pr_0230(fork_at + 1) + .build() + .unwrap(); + let consensus = ConsensusBuilder::default() + .hardfork_switch(hardfork_switch) + .build(); + let result = VersionVerifier::new(&header1, &consensus).verify(); + assert!(result.is_ok(), "result = {:?}", result); + + let result = VersionVerifier::new(&header2, &consensus).verify(); + assert_error_eq!( + result.unwrap_err(), + BlockVersionError { + expected: default_block_version, + actual: default_block_version + 1 + } + ); + } + { + let hardfork_switch = HardForkSwitch::new_without_any_enabled() + .as_builder() + .rfc_pr_0230(fork_at) + .build() + .unwrap(); + let consensus = ConsensusBuilder::default() + .hardfork_switch(hardfork_switch) + .build(); + let result = VersionVerifier::new(&header1, &consensus).verify(); + assert!(result.is_ok(), "result = {:?}", result); + + let result = VersionVerifier::new(&header2, &consensus).verify(); + assert!(result.is_ok(), "result = {:?}", result); + } } #[cfg(not(disable_faketime))] diff --git a/verification/src/tests/transaction_verifier.rs b/verification/src/tests/transaction_verifier.rs index bda3972923..f3ea06ed4b 100644 --- a/verification/src/tests/transaction_verifier.rs +++ b/verification/src/tests/transaction_verifier.rs @@ -10,7 +10,6 @@ use ckb_test_chain_utils::{MockMedianTime, MOCK_MEDIAN_TIME_COUNT}; use ckb_traits::HeaderProvider; use ckb_types::{ bytes::Bytes, - constants::TX_VERSION, core::{ capacity_bytes, cell::{CellMetaBuilder, ResolvedTransaction}, @@ -40,18 +39,66 @@ pub fn test_empty() { #[test] pub fn test_version() { - let transaction = TransactionBuilder::default() - .version((TX_VERSION + 1).pack()) + let fork_at = 10; + let default_tx_version = ConsensusBuilder::default().build().tx_version(fork_at); + let tx1 = TransactionBuilder::default() + .version(default_tx_version.pack()) .build(); - let verifier = VersionVerifier::new(&transaction, TX_VERSION); - - assert_error_eq!( - verifier.verify().unwrap_err(), - TransactionError::MismatchedVersion { - expected: 0, - actual: 1 - }, + let rtx1 = create_resolve_tx_with_transaction_info( + &tx1, + MockMedianTime::get_transaction_info(1, EpochNumberWithFraction::new(0, 0, 10), 1), ); + let tx2 = TransactionBuilder::default() + .version((default_tx_version + 1).pack()) + .build(); + let rtx2 = create_resolve_tx_with_transaction_info( + &tx2, + MockMedianTime::get_transaction_info(1, EpochNumberWithFraction::new(0, 0, 10), 1), + ); + let tx_env = { + let epoch = EpochNumberWithFraction::new(fork_at, 0, 10); + let header = HeaderView::new_advanced_builder() + .epoch(epoch.pack()) + .build(); + TxVerifyEnv::new_commit(&header) + }; + + { + let hardfork_switch = HardForkSwitch::new_without_any_enabled() + .as_builder() + .rfc_pr_0230(fork_at + 1) + .build() + .unwrap(); + let consensus = ConsensusBuilder::default() + .hardfork_switch(hardfork_switch) + .build(); + let result = VersionVerifier::new(&rtx1, &consensus, &tx_env).verify(); + assert!(result.is_ok(), "result = {:?}", result); + + let result = VersionVerifier::new(&rtx2, &consensus, &tx_env).verify(); + assert_error_eq!( + result.unwrap_err(), + TransactionError::MismatchedVersion { + expected: default_tx_version, + actual: default_tx_version + 1 + }, + ); + } + { + let hardfork_switch = HardForkSwitch::new_without_any_enabled() + .as_builder() + .rfc_pr_0230(fork_at) + .build() + .unwrap(); + let consensus = ConsensusBuilder::default() + .hardfork_switch(hardfork_switch) + .build(); + let result = VersionVerifier::new(&rtx1, &consensus, &tx_env).verify(); + assert!(result.is_ok(), "result = {:?}", result); + + let result = VersionVerifier::new(&rtx2, &consensus, &tx_env).verify(); + assert!(result.is_ok(), "result = {:?}", result); + } } #[test] diff --git a/verification/src/transaction_verifier.rs b/verification/src/transaction_verifier.rs index 5d1d57dfdb..21a9df1aaa 100644 --- a/verification/src/transaction_verifier.rs +++ b/verification/src/transaction_verifier.rs @@ -10,7 +10,7 @@ use ckb_traits::{CellDataProvider, EpochProvider, HeaderProvider}; use ckb_types::{ core::{ cell::{CellMeta, ResolvedTransaction}, - Capacity, Cycle, EpochNumberWithFraction, ScriptHashType, TransactionView, Version, + Capacity, Cycle, EpochNumberWithFraction, ScriptHashType, TransactionView, }, packed::Byte32, prelude::*, @@ -51,13 +51,11 @@ impl<'a, DL: HeaderProvider + ConsensusProvider> TimeRelativeTransactionVerifier /// /// Basic checks that don't depend on any context /// Contains: -/// - Check for version /// - Check for size /// - Check inputs and output empty /// - Check for duplicate deps /// - Check for whether outputs match data pub struct NonContextualTransactionVerifier<'a> { - pub(crate) version: VersionVerifier<'a>, pub(crate) size: SizeVerifier<'a>, pub(crate) empty: EmptyVerifier<'a>, pub(crate) duplicate_deps: DuplicateDepsVerifier<'a>, @@ -68,7 +66,6 @@ impl<'a> NonContextualTransactionVerifier<'a> { /// Creates a new NonContextualTransactionVerifier pub fn new(tx: &'a TransactionView, consensus: &'a Consensus) -> Self { NonContextualTransactionVerifier { - version: VersionVerifier::new(tx, consensus.tx_version()), size: SizeVerifier::new(tx, consensus.max_block_bytes()), empty: EmptyVerifier::new(tx), duplicate_deps: DuplicateDepsVerifier::new(tx), @@ -78,7 +75,6 @@ impl<'a> NonContextualTransactionVerifier<'a> { /// Perform context-independent verification pub fn verify(&self) -> Result<(), Error> { - self.version.verify()?; self.size.verify()?; self.empty.verify()?; self.duplicate_deps.verify()?; @@ -90,12 +86,14 @@ impl<'a> NonContextualTransactionVerifier<'a> { /// Context-dependent verification checks for transaction /// /// Contains: +/// [`VersionVerifier`](./struct.VersionVerifier.html) /// [`MaturityVerifier`](./struct.MaturityVerifier.html) /// [`SinceVerifier`](./struct.SinceVerifier.html) /// [`CapacityVerifier`](./struct.CapacityVerifier.html) /// [`ScriptVerifier`](./struct.ScriptVerifier.html) /// [`FeeCalculator`](./struct.FeeCalculator.html) pub struct ContextualTransactionVerifier<'a, DL> { + pub(crate) version: VersionVerifier<'a>, pub(crate) maturity: MaturityVerifier<'a>, pub(crate) since: SinceVerifier<'a, DL>, pub(crate) capacity: CapacityVerifier<'a>, @@ -115,6 +113,7 @@ where tx_env: &'a TxVerifyEnv, ) -> Self { ContextualTransactionVerifier { + version: VersionVerifier::new(&rtx, consensus, tx_env), maturity: MaturityVerifier::new(&rtx, tx_env.epoch(), consensus.cellbase_maturity()), script: ScriptVerifier::new(rtx, data_loader), capacity: CapacityVerifier::new(rtx, consensus.dao_type_hash()), @@ -128,6 +127,7 @@ where /// skip script verify will result in the return value cycle always is zero pub fn verify(&self, max_cycles: Cycle, skip_script_verify: bool) -> Result { let timer = Timer::start(); + self.version.verify()?; self.maturity.verify()?; self.capacity.verify()?; self.since.verify()?; @@ -203,23 +203,45 @@ impl<'a, DL: CellDataProvider + HeaderProvider + EpochProvider> FeeCalculator<'a } pub struct VersionVerifier<'a> { - transaction: &'a TransactionView, - tx_version: Version, + rtx: &'a ResolvedTransaction, + consensus: &'a Consensus, + tx_env: &'a TxVerifyEnv, } impl<'a> VersionVerifier<'a> { - pub fn new(transaction: &'a TransactionView, tx_version: Version) -> Self { + pub fn new( + rtx: &'a ResolvedTransaction, + consensus: &'a Consensus, + tx_env: &'a TxVerifyEnv, + ) -> Self { VersionVerifier { - transaction, - tx_version, + rtx, + consensus, + tx_env, } } pub fn verify(&self) -> Result<(), Error> { - if self.transaction.version() != self.tx_version { + let proposal_window = self.consensus.tx_proposal_window(); + let epoch_number = self.tx_env.epoch_number(proposal_window); + let target = self.consensus.tx_version(epoch_number); + let actual = self.rtx.transaction.version(); + if self + .consensus + .hardfork_switch() + .is_allow_unknown_versions_enabled(epoch_number) + { + if actual < target { + return Err((TransactionError::DeprecatedVersion { + minimum: target, + actual, + }) + .into()); + } + } else if actual != target { return Err((TransactionError::MismatchedVersion { - expected: self.tx_version, - actual: self.transaction.version(), + expected: target, + actual, }) .into()); }