Skip to content

Commit

Permalink
feat(hardfork): allow unknown block versions and transactions versions
Browse files Browse the repository at this point in the history
  • Loading branch information
yangby-cryptape committed May 25, 2021
1 parent 4d75c5c commit d1c0bbe
Show file tree
Hide file tree
Showing 20 changed files with 485 additions and 143 deletions.
5 changes: 3 additions & 2 deletions rpc/src/module/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1615,8 +1615,9 @@ impl ChainRpc for ChainRpcImpl {
}

fn get_consensus(&self) -> Result<Consensus> {
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<Option<Timestamp>> {
Expand Down
96 changes: 44 additions & 52 deletions spec/src/consensus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -961,47 +994,6 @@ impl NextBlockEpoch {
}
}

impl From<Consensus> 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]
Expand Down
6 changes: 5 additions & 1 deletion spec/src/hardfork.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ pub struct HardForkConfig {
pub rfc_pr_0221: Option<EpochNumber>,
/// Ref: [CKB RFC xxxx](https://github.com/nervosnetwork/rfcs/tree/master/rfcs/xxxx-rfc-title)
pub rfc_pr_0223: Option<EpochNumber>,
/// Ref: [CKB RFC xxxx](https://github.com/nervosnetwork/rfcs/tree/master/rfcs/xxxx-rfc-title)
pub rfc_pr_0230: Option<EpochNumber>,
}

macro_rules! check_default {
Expand Down Expand Up @@ -60,7 +62,8 @@ impl HardForkConfig {
) -> Result<HardForkSwitchBuilder, String> {
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)
}

Expand All @@ -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()
}
}
2 changes: 2 additions & 0 deletions test/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,8 @@ fn all_specs() -> Vec<Box<dyn Spec>> {
// Test hard fork features
Box::new(CheckAbsoluteEpochSince),
Box::new(CheckRelativeEpochSince),
Box::new(CheckBlockVersion),
Box::new(CheckTxVersion),
];
specs.shuffle(&mut thread_rng());
specs
Expand Down
2 changes: 2 additions & 0 deletions test/src/specs/hardfork/v2021/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
mod since;
mod version;

pub use since::{CheckAbsoluteEpochSince, CheckRelativeEpochSince};
pub use version::{CheckBlockVersion, CheckTxVersion};
181 changes: 181 additions & 0 deletions test/src/specs/hardfork/v2021/version.rs
Original file line number Diff line number Diff line change
@@ -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<Node>) {
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<Node>) {
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()
}
2 changes: 1 addition & 1 deletion test/src/specs/rpc/get_block_template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit d1c0bbe

Please sign in to comment.