//! Consensus defines various tweakable parameters of a given instance of the CKB system. //! #![allow(clippy::inconsistent_digit_grouping)] use crate::{ calculate_block_reward, versionbits::{ self, Deployment, DeploymentPos, ThresholdState, Versionbits, VersionbitsCache, VersionbitsConditionChecker, VersionbitsIndexer, }, OUTPUT_INDEX_DAO, OUTPUT_INDEX_SECP256K1_BLAKE160_MULTISIG_ALL, OUTPUT_INDEX_SECP256K1_BLAKE160_SIGHASH_ALL, }; use ckb_constant::{ consensus::TAU, hardfork::{mainnet, testnet}, softfork, }; use ckb_dao_utils::genesis_dao_data_with_satoshi_gift; use ckb_pow::{Pow, PowEngine}; use ckb_rational::RationalU256; use ckb_resource::Resource; use ckb_traits::{BlockEpoch, EpochProvider}; use ckb_types::{ bytes::Bytes, constants::{BLOCK_VERSION, TX_VERSION}, core::{ hardfork::HardForks, BlockBuilder, BlockNumber, BlockView, Capacity, Cycle, EpochExt, EpochNumber, EpochNumberWithFraction, HeaderView, Ratio, TransactionBuilder, TransactionView, Version, }, h160, h256, packed::{Byte32, CellInput, CellOutput, Script}, prelude::*, utilities::{compact_to_difficulty, difficulty_to_compact, DIFF_TWO}, H160, H256, U256, }; use std::cmp; use std::collections::HashMap; use std::sync::Arc; // 1.344 billion per year pub(crate) const DEFAULT_SECONDARY_EPOCH_REWARD: Capacity = Capacity::shannons(613_698_63013698); // 4.2 billion per year pub(crate) const INITIAL_PRIMARY_EPOCH_REWARD: Capacity = Capacity::shannons(1_917_808_21917808); const MAX_UNCLE_NUM: usize = 2; /// Default transaction proposal window. pub const TX_PROPOSAL_WINDOW: ProposalWindow = ProposalWindow(2, 10); // Cellbase outputs are "locked" and require 4 epoch confirmations (approximately 16 hours) before // they mature sufficiently to be spendable, // This is to reduce the risk of later txs being reversed if a chain reorganization occurs. pub(crate) const CELLBASE_MATURITY: EpochNumberWithFraction = EpochNumberWithFraction::new_unchecked(4, 0, 1); const MEDIAN_TIME_BLOCK_COUNT: usize = 37; // We choose 1_000 because it is largest number between MIN_EPOCH_LENGTH and MAX_EPOCH_LENGTH that // can divide INITIAL_PRIMARY_EPOCH_REWARD and can be divided by ORPHAN_RATE_TARGET_RECIP. pub(crate) const GENESIS_EPOCH_LENGTH: u64 = 1_000; // o_ideal = 1/40 = 2.5% pub(crate) const DEFAULT_ORPHAN_RATE_TARGET: (u32, u32) = (1, 40); /// max block interval, 48 seconds pub const MAX_BLOCK_INTERVAL: u64 = 48; /// min block interval, 8 seconds pub const MIN_BLOCK_INTERVAL: u64 = 8; /// cycles of a typical two-in-two-out tx. pub const TWO_IN_TWO_OUT_CYCLES: Cycle = 3_500_000; /// bytes of a typical two-in-two-out tx. pub const TWO_IN_TWO_OUT_BYTES: u64 = 597; /// count of two-in-two-out txs a block should capable to package. const TWO_IN_TWO_OUT_COUNT: u64 = 1_000; pub(crate) const DEFAULT_EPOCH_DURATION_TARGET: u64 = 4 * 60 * 60; // 4 hours, unit: second const MILLISECONDS_IN_A_SECOND: u64 = 1000; const MAX_EPOCH_LENGTH: u64 = DEFAULT_EPOCH_DURATION_TARGET / MIN_BLOCK_INTERVAL; // 1800 const MIN_EPOCH_LENGTH: u64 = DEFAULT_EPOCH_DURATION_TARGET / MAX_BLOCK_INTERVAL; // 300 pub(crate) const DEFAULT_PRIMARY_EPOCH_REWARD_HALVING_INTERVAL: EpochNumber = 4 * 365 * 24 * 60 * 60 / DEFAULT_EPOCH_DURATION_TARGET; // every 4 years /// The default maximum allowed size in bytes for a block pub const MAX_BLOCK_BYTES: u64 = TWO_IN_TWO_OUT_BYTES * TWO_IN_TWO_OUT_COUNT; pub(crate) const MAX_BLOCK_CYCLES: u64 = TWO_IN_TWO_OUT_CYCLES * TWO_IN_TWO_OUT_COUNT; /// The default maximum allowed amount of proposals for a block /// /// Default value from 1.5 * TWO_IN_TWO_OUT_COUNT pub const MAX_BLOCK_PROPOSALS_LIMIT: u64 = 1_500; const PROPOSER_REWARD_RATIO: Ratio = Ratio::new(4, 10); // Satoshi's pubkey hash in Bitcoin genesis. pub(crate) const SATOSHI_PUBKEY_HASH: H160 = h160!("0x62e907b15cbf27d5425399ebf6f0fb50ebb88f18"); // Ratio of satoshi cell occupied of capacity, // only affects genesis cellbase's satoshi lock cells. pub(crate) const SATOSHI_CELL_OCCUPIED_RATIO: Ratio = Ratio::new(6, 10); /// The mainnet default activation_threshold pub const LC_MAINNET_ACTIVATION_THRESHOLD: Ratio = Ratio::new(8, 10); /// The testnet default activation_threshold pub const TESTNET_ACTIVATION_THRESHOLD: Ratio = Ratio::new(3, 4); /// The starting block number from which the lock script size of a DAO withdrawing /// cell shall be limited pub(crate) const STARTING_BLOCK_LIMITING_DAO_WITHDRAWING_LOCK: u64 = 10_000_000; /// The struct represent CKB two-step-transaction-confirmation params /// /// [two-step-transaction-confirmation params](https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0020-ckb-consensus-protocol/0020-ckb-consensus-protocol.md#two-step-transaction-confirmation) /// /// The `ProposalWindow` consists of two fields: /// self.0, aka w_close, self.1, aka w_far /// w_close and w_far define the closest and farthest on-chain distance between a transaction’s proposal and commitment. #[derive(Clone, PartialEq, Debug, Eq, Copy)] pub struct ProposalWindow(pub BlockNumber, pub BlockNumber); /// "TYPE_ID" in hex pub const TYPE_ID_CODE_HASH: H256 = h256!("0x545950455f4944"); /// Two protocol parameters w_close and w_far define the closest /// and farthest on-chain distance between a transaction's proposal /// and commitment. /// /// A non-cellbase transaction is committed at height h_c if all of the following conditions are met: /// 1) it is proposed at height h_p of the same chain, where w_close <= h_c − h_p <= w_far ; /// 2) it is in the commitment zone of the main chain block with height h_c ; /// /// ```text /// ProposalWindow (2, 10) /// propose /// \ /// \ /// 13 14 [15 16 17 18 19 20 21 22 23] /// \_______________________/ /// \ /// commit /// ``` /// impl ProposalWindow { /// The w_close parameter pub const fn closest(&self) -> BlockNumber { self.0 } /// The w_far parameter pub const fn farthest(&self) -> BlockNumber { self.1 } /// The proposal window length pub const fn length(&self) -> BlockNumber { self.1 - self.0 + 1 } } /// The Consensus factory, which can be used in order to configure the properties of a new Consensus. pub struct ConsensusBuilder { inner: Consensus, } // Dummy consensus, difficulty can not be zero impl Default for ConsensusBuilder { fn default() -> Self { let input = CellInput::new_cellbase_input(0); // at least issue some shannons to make dao field valid. let output = { let empty_output = CellOutput::new_builder().build(); let occupied = empty_output .occupied_capacity(Capacity::zero()) .expect("default occupied"); empty_output.as_builder().capacity(occupied.pack()).build() }; let witness = Script::default().into_witness(); let cellbase = TransactionBuilder::default() .input(input) .output(output) .output_data(Bytes::new().pack()) .witness(witness) .build(); let epoch_ext = build_genesis_epoch_ext( INITIAL_PRIMARY_EPOCH_REWARD, DIFF_TWO, GENESIS_EPOCH_LENGTH, DEFAULT_EPOCH_DURATION_TARGET, DEFAULT_ORPHAN_RATE_TARGET, ); let primary_issuance = calculate_block_reward(INITIAL_PRIMARY_EPOCH_REWARD, GENESIS_EPOCH_LENGTH); let secondary_issuance = calculate_block_reward(DEFAULT_SECONDARY_EPOCH_REWARD, GENESIS_EPOCH_LENGTH); let dao = genesis_dao_data_with_satoshi_gift( vec![&cellbase], &SATOSHI_PUBKEY_HASH, SATOSHI_CELL_OCCUPIED_RATIO, primary_issuance, secondary_issuance, ) .expect("genesis dao data calculation error!"); let genesis_block = BlockBuilder::default() .compact_target(DIFF_TWO.pack()) .epoch(EpochNumberWithFraction::new_unchecked(0, 0, 0).pack()) .dao(dao) .transaction(cellbase) .build(); ConsensusBuilder::new(genesis_block, epoch_ext) .initial_primary_epoch_reward(INITIAL_PRIMARY_EPOCH_REWARD) } } /// Build the epoch information of genesis block pub fn build_genesis_epoch_ext( epoch_reward: Capacity, compact_target: u32, genesis_epoch_length: BlockNumber, epoch_duration_target: u64, genesis_orphan_rate: (u32, u32), ) -> EpochExt { let block_reward = Capacity::shannons(epoch_reward.as_u64() / genesis_epoch_length); let remainder_reward = Capacity::shannons(epoch_reward.as_u64() % genesis_epoch_length); let genesis_orphan_count = genesis_epoch_length * genesis_orphan_rate.0 as u64 / genesis_orphan_rate.1 as u64; let genesis_hash_rate = compact_to_difficulty(compact_target) * (genesis_epoch_length + genesis_orphan_count) / epoch_duration_target; EpochExt::new_builder() .number(0) .base_block_reward(block_reward) .remainder_reward(remainder_reward) .previous_epoch_hash_rate(genesis_hash_rate) .last_block_hash_in_previous_epoch(Byte32::zero()) .start_number(0) .length(genesis_epoch_length) .compact_target(compact_target) .build() } /// Build the dao data of genesis block pub fn build_genesis_dao_data( txs: Vec<&TransactionView>, satoshi_pubkey_hash: &H160, satoshi_cell_occupied_ratio: Ratio, genesis_primary_issuance: Capacity, genesis_secondary_issuance: Capacity, ) -> Byte32 { genesis_dao_data_with_satoshi_gift( txs, satoshi_pubkey_hash, satoshi_cell_occupied_ratio, genesis_primary_issuance, genesis_secondary_issuance, ) .expect("genesis dao data calculation error!") } impl ConsensusBuilder { /// Generates the base configuration for build a Consensus, from which configuration methods can be chained. pub fn new(genesis_block: BlockView, genesis_epoch_ext: EpochExt) -> Self { let orphan_rate_target = RationalU256::new_raw( U256::from(DEFAULT_ORPHAN_RATE_TARGET.0), U256::from(DEFAULT_ORPHAN_RATE_TARGET.1), ); ConsensusBuilder { inner: Consensus { genesis_hash: genesis_block.header().hash(), genesis_block, id: "main".to_owned(), max_uncles_num: MAX_UNCLE_NUM, initial_primary_epoch_reward: INITIAL_PRIMARY_EPOCH_REWARD, orphan_rate_target, epoch_duration_target: DEFAULT_EPOCH_DURATION_TARGET, secondary_epoch_reward: DEFAULT_SECONDARY_EPOCH_REWARD, tx_proposal_window: TX_PROPOSAL_WINDOW, pow: Pow::Dummy, cellbase_maturity: CELLBASE_MATURITY, median_time_block_count: MEDIAN_TIME_BLOCK_COUNT, max_block_cycles: MAX_BLOCK_CYCLES, max_block_bytes: MAX_BLOCK_BYTES, dao_type_hash: Byte32::default(), 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, satoshi_pubkey_hash: SATOSHI_PUBKEY_HASH, satoshi_cell_occupied_ratio: SATOSHI_CELL_OCCUPIED_RATIO, primary_epoch_reward_halving_interval: DEFAULT_PRIMARY_EPOCH_REWARD_HALVING_INTERVAL, permanent_difficulty_in_dummy: false, hardfork_switch: HardForks::new_mirana(), deployments: HashMap::new(), versionbits_caches: VersionbitsCache::default(), starting_block_limiting_dao_withdrawing_lock: STARTING_BLOCK_LIMITING_DAO_WITHDRAWING_LOCK, }, } } fn get_type_hash(&self, output_index: u64) -> Option { self.inner .genesis_block .transaction(0) .expect("Genesis must have cellbase") .output(output_index as usize) .and_then(|output| output.type_().to_opt()) .map(|type_script| type_script.calc_script_hash()) } /// Build a new Consensus by taking ownership of the `Builder`, and returns a [`Consensus`]. pub fn build(mut self) -> Consensus { debug_assert!( self.inner.genesis_block.difficulty() > U256::zero(), "genesis difficulty should greater than zero" ); debug_assert!( !self.inner.genesis_block.data().transactions().is_empty() && !self .inner .genesis_block .data() .transactions() .get(0) .unwrap() .witnesses() .is_empty(), "genesis block must contain the witness for cellbase" ); debug_assert!( self.inner.initial_primary_epoch_reward != Capacity::zero(), "initial_primary_epoch_reward must be non-zero" ); debug_assert!( self.inner.epoch_duration_target() != 0, "epoch_duration_target must be non-zero" ); debug_assert!( !self.inner.genesis_block.transactions().is_empty() && !self.inner.genesis_block.transactions()[0] .witnesses() .is_empty(), "genesis block must contain the witness for cellbase" ); self.inner.dao_type_hash = self.get_type_hash(OUTPUT_INDEX_DAO).unwrap_or_default(); self.inner.secp256k1_blake160_sighash_all_type_hash = self.get_type_hash(OUTPUT_INDEX_SECP256K1_BLAKE160_SIGHASH_ALL); self.inner.secp256k1_blake160_multisig_all_type_hash = self.get_type_hash(OUTPUT_INDEX_SECP256K1_BLAKE160_MULTISIG_ALL); self.inner .genesis_epoch_ext .set_compact_target(self.inner.genesis_block.compact_target()); self.inner.genesis_hash = self.inner.genesis_block.hash(); self.inner } /// Names the network. pub fn id(mut self, id: String) -> Self { self.inner.id = id; self } /// Sets genesis_block for the new Consensus. pub fn genesis_block(mut self, genesis_block: BlockView) -> Self { self.inner.genesis_block = genesis_block; self } /// Sets initial_primary_epoch_reward for the new Consensus. #[must_use] pub fn initial_primary_epoch_reward(mut self, initial_primary_epoch_reward: Capacity) -> Self { self.inner.initial_primary_epoch_reward = initial_primary_epoch_reward; self } /// Sets orphan_rate_target for the new Consensus. pub fn orphan_rate_target(mut self, orphan_rate_target: (u32, u32)) -> Self { self.inner.orphan_rate_target = RationalU256::new_raw( U256::from(orphan_rate_target.0), U256::from(orphan_rate_target.1), ); self } /// Sets secondary_epoch_reward for the new Consensus. #[must_use] pub fn secondary_epoch_reward(mut self, secondary_epoch_reward: Capacity) -> Self { self.inner.secondary_epoch_reward = secondary_epoch_reward; self } /// Sets max_block_cycles for the new Consensus. #[must_use] pub fn max_block_cycles(mut self, max_block_cycles: Cycle) -> Self { self.inner.max_block_cycles = max_block_cycles; self } /// Sets max_block_bytes for the new Consensus. #[must_use] pub fn max_block_bytes(mut self, max_block_bytes: u64) -> Self { self.inner.max_block_bytes = max_block_bytes; self } /// Sets cellbase_maturity for the new Consensus. #[must_use] pub fn cellbase_maturity(mut self, cellbase_maturity: EpochNumberWithFraction) -> Self { self.inner.cellbase_maturity = cellbase_maturity; self } /// Sets median_time_block_count for the new Consensus. pub fn median_time_block_count(mut self, median_time_block_count: usize) -> Self { self.inner.median_time_block_count = median_time_block_count; self } /// Sets tx_proposal_window for the new Consensus. pub fn tx_proposal_window(mut self, proposal_window: ProposalWindow) -> Self { self.inner.tx_proposal_window = proposal_window; self } /// Sets pow for the new Consensus. pub fn pow(mut self, pow: Pow) -> Self { self.inner.pow = pow; self } /// Sets satoshi_pubkey_hash for the new Consensus. pub fn satoshi_pubkey_hash(mut self, pubkey_hash: H160) -> Self { self.inner.satoshi_pubkey_hash = pubkey_hash; self } /// Sets satoshi_cell_occupied_ratio for the new Consensus. pub fn satoshi_cell_occupied_ratio(mut self, ratio: Ratio) -> Self { self.inner.satoshi_cell_occupied_ratio = ratio; self } /// Sets primary_epoch_reward_halving_interval for the new Consensus. #[must_use] pub fn primary_epoch_reward_halving_interval(mut self, halving_interval: u64) -> Self { self.inner.primary_epoch_reward_halving_interval = halving_interval; self } /// Sets expected epoch_duration_target for the new Consensus. #[must_use] pub fn epoch_duration_target(mut self, target: u64) -> Self { self.inner.epoch_duration_target = target; self } /// Sets permanent_difficulty_in_dummy for the new Consensus. /// /// [dynamic-difficulty-adjustment-mechanism](https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0020-ckb-consensus-protocol/0020-ckb-consensus-protocol.md#dynamic-difficulty-adjustment-mechanism) /// may be a disturbance in dev chain, set permanent_difficulty_in_dummy to true will disable dynamic difficulty adjustment mechanism. keep difficulty unchanged. /// Work only under dummy Pow #[must_use] pub fn permanent_difficulty_in_dummy(mut self, permanent: bool) -> Self { self.inner.permanent_difficulty_in_dummy = permanent; self } /// Sets max_block_proposals_limit for the new Consensus. #[must_use] pub fn max_block_proposals_limit(mut self, max_block_proposals_limit: u64) -> Self { self.inner.max_block_proposals_limit = max_block_proposals_limit; self } /// Sets a hard fork switch for the new Consensus. pub fn hardfork_switch(mut self, hardfork_switch: HardForks) -> Self { self.inner.hardfork_switch = hardfork_switch; self } /// Sets a soft fork deployments for the new Consensus. pub fn softfork_deployments(mut self, deployments: HashMap) -> Self { self.inner.versionbits_caches = VersionbitsCache::new(deployments.keys()); self.inner.deployments = deployments; self } ///The starting block number where Nervos DAO withdrawing cell's lock is /// size limited. pub fn starting_block_limiting_dao_withdrawing_lock( mut self, starting_block_limiting_dao_withdrawing_lock: u64, ) -> Self { self.inner.starting_block_limiting_dao_withdrawing_lock = starting_block_limiting_dao_withdrawing_lock; self } } /// Struct Consensus defines various parameters that influence chain consensus #[derive(Clone, Debug)] pub struct Consensus { /// Names the network. pub id: String, /// The genesis block pub genesis_block: BlockView, /// The genesis block hash pub genesis_hash: Byte32, /// The dao type hash /// /// [nervos-dao](https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0024-ckb-genesis-script-list/0024-ckb-genesis-script-list.md#nervos-dao) pub dao_type_hash: Byte32, /// The secp256k1_blake160_sighash_all_type_hash /// /// [SECP256K1/blake160](https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0024-ckb-genesis-script-list/0024-ckb-genesis-script-list.md#secp256k1blake160) pub secp256k1_blake160_sighash_all_type_hash: Option, /// The secp256k1_blake160_multisig_all_type_hash /// /// [SECP256K1/multisig](https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0024-ckb-genesis-script-list/0024-ckb-genesis-script-list.md#secp256k1multisig) pub secp256k1_blake160_multisig_all_type_hash: Option, /// The initial primary_epoch_reward /// /// [token-issuance](https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0015-ckb-cryptoeconomics/0015-ckb-cryptoeconomics.md#token-issuance) pub initial_primary_epoch_reward: Capacity, /// The secondary primary_epoch_reward /// /// [token-issuance](https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0015-ckb-cryptoeconomics/0015-ckb-cryptoeconomics.md#token-issuance) pub secondary_epoch_reward: Capacity, /// The maximum amount of uncles allowed for a block pub max_uncles_num: usize, /// The expected orphan_rate pub orphan_rate_target: RationalU256, /// The expected epoch_duration pub epoch_duration_target: u64, /// The two-step-transaction-confirmation proposal window pub tx_proposal_window: ProposalWindow, /// The two-step-transaction-confirmation proposer reward ratio pub proposer_reward_ratio: Ratio, /// The pow parameters pub pow: Pow, /// The Cellbase maturity /// For each input, if the referenced output transaction is cellbase, /// it must have at least `cellbase_maturity` confirmations; /// else reject this transaction. pub cellbase_maturity: EpochNumberWithFraction, /// This parameter indicates the count of past blocks used in the median time calculation pub median_time_block_count: usize, /// Maximum cycles that all the scripts in all the commit transactions can take 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 pub max_block_proposals_limit: u64, /// The genesis epoch information pub genesis_epoch_ext: EpochExt, /// Satoshi's pubkey hash in Bitcoin genesis. pub satoshi_pubkey_hash: H160, /// Ratio of satoshi cell occupied of capacity, /// only affects genesis cellbase's satoshi lock cells. pub satoshi_cell_occupied_ratio: Ratio, /// Primary reward is cut in half every halving_interval epoch /// which will occur approximately every 4 years. pub primary_epoch_reward_halving_interval: EpochNumber, /// Keep difficulty be permanent if the pow is dummy pub permanent_difficulty_in_dummy: bool, /// A switch to select hard fork features base on the epoch number. pub hardfork_switch: HardForks, /// Soft fork deployments pub deployments: HashMap, /// Soft fork state cache pub versionbits_caches: VersionbitsCache, /// Starting block where DAO withdrawing lock is limited in size pub starting_block_limiting_dao_withdrawing_lock: u64, } // genesis difficulty should not be zero impl Default for Consensus { fn default() -> Self { ConsensusBuilder::default().build() } } #[allow(clippy::op_ref)] impl Consensus { /// The genesis block pub fn genesis_block(&self) -> &BlockView { &self.genesis_block } /// The two-step-transaction-confirmation proposer reward ratio pub fn proposer_reward_ratio(&self) -> Ratio { self.proposer_reward_ratio } /// The two-step-transaction-confirmation block reward delay length pub fn finalization_delay_length(&self) -> BlockNumber { self.tx_proposal_window.farthest() + 1 } /// Get block reward finalize number from specified block number pub fn finalize_target(&self, block_number: BlockNumber) -> Option { if block_number != 0 { Some(block_number.saturating_sub(self.finalization_delay_length())) } else { // Genesis should not reward genesis itself None } } /// The genesis block hash pub fn genesis_hash(&self) -> Byte32 { self.genesis_hash.clone() } /// The dao type hash /// /// [nervos-dao](https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0024-ckb-genesis-script-list/0024-ckb-genesis-script-list.md#nervos-dao) pub fn dao_type_hash(&self) -> Byte32 { self.dao_type_hash.clone() } /// The secp256k1_blake160_sighash_all_type_hash /// /// [SECP256K1/blake160](https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0024-ckb-genesis-script-list/0024-ckb-genesis-script-list.md#secp256k1blake160) pub fn secp256k1_blake160_sighash_all_type_hash(&self) -> Option { self.secp256k1_blake160_sighash_all_type_hash.clone() } /// The secp256k1_blake160_multisig_all_type_hash /// /// [SECP256K1/multisig](https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0024-ckb-genesis-script-list/0024-ckb-genesis-script-list.md#secp256k1multisig) pub fn secp256k1_blake160_multisig_all_type_hash(&self) -> Option { self.secp256k1_blake160_multisig_all_type_hash.clone() } /// The maximum amount of uncles allowed for a block pub fn max_uncles_num(&self) -> usize { self.max_uncles_num } /// The minimum difficulty (genesis_block difficulty) pub fn min_difficulty(&self) -> U256 { self.genesis_block.difficulty() } /// The minimum difficulty (genesis_block difficulty) pub fn initial_primary_epoch_reward(&self) -> Capacity { self.initial_primary_epoch_reward } /// The initial primary_epoch_reward /// /// [token-issuance](https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0015-ckb-cryptoeconomics/0015-ckb-cryptoeconomics.md#token-issuance) pub fn primary_epoch_reward(&self, epoch_number: u64) -> Capacity { let halvings = epoch_number / self.primary_epoch_reward_halving_interval(); Capacity::shannons(self.initial_primary_epoch_reward.as_u64() >> halvings) } /// Primary reward is cut in half every halving_interval epoch /// which will occur approximately every 4 years. pub fn primary_epoch_reward_halving_interval(&self) -> EpochNumber { self.primary_epoch_reward_halving_interval } /// The expected epoch_duration pub fn epoch_duration_target(&self) -> u64 { self.epoch_duration_target } /// The genesis epoch information pub fn genesis_epoch_ext(&self) -> &EpochExt { &self.genesis_epoch_ext } /// The maximum epoch length pub fn max_epoch_length(&self) -> BlockNumber { MAX_EPOCH_LENGTH } /// The minimum epoch length pub fn min_epoch_length(&self) -> BlockNumber { MIN_EPOCH_LENGTH } /// The secondary primary_epoch_reward /// /// [token-issuance](https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0015-ckb-cryptoeconomics/0015-ckb-cryptoeconomics.md#token-issuance) pub fn secondary_epoch_reward(&self) -> Capacity { self.secondary_epoch_reward } /// The expected orphan_rate pub fn orphan_rate_target(&self) -> &RationalU256 { &self.orphan_rate_target } /// The pow_engine pub fn pow_engine(&self) -> Arc { self.pow.engine() } /// The permanent_difficulty mode pub fn permanent_difficulty(&self) -> bool { self.pow.is_dummy() && self.permanent_difficulty_in_dummy } /// The cellbase_maturity pub fn cellbase_maturity(&self) -> EpochNumberWithFraction { self.cellbase_maturity } /// This parameter indicates the count of past blocks used in the median time calculation pub fn median_time_block_count(&self) -> usize { self.median_time_block_count } /// Maximum cycles that all the scripts in all the commit transactions can take pub fn max_block_cycles(&self) -> Cycle { self.max_block_cycles } /// Maximum number of bytes to use for the entire block pub fn max_block_bytes(&self) -> u64 { self.max_block_bytes } /// The Limit to the number of proposals per block pub fn max_block_proposals_limit(&self) -> u64 { self.max_block_proposals_limit } /// The current block version pub fn block_version(&self) -> Version { self.block_version } /// The current transaction version pub fn tx_version(&self) -> Version { self.tx_version } /// The "TYPE_ID" in hex pub fn type_id_code_hash(&self) -> &H256 { &self.type_id_code_hash } /// The two-step-transaction-confirmation proposal window pub fn tx_proposal_window(&self) -> ProposalWindow { self.tx_proposal_window } /// The starting block number where Nervos DAO withdrawing cell's lock is /// size limited. pub fn starting_block_limiting_dao_withdrawing_lock(&self) -> u64 { self.starting_block_limiting_dao_withdrawing_lock } // Apply the dampening filter on hash_rate estimation calculate fn bounding_hash_rate( &self, last_epoch_hash_rate: U256, last_epoch_previous_hash_rate: U256, ) -> U256 { if last_epoch_previous_hash_rate == U256::zero() { return last_epoch_hash_rate; } let lower_bound = &last_epoch_previous_hash_rate / TAU; if last_epoch_hash_rate < lower_bound { return lower_bound; } let upper_bound = &last_epoch_previous_hash_rate * TAU; if last_epoch_hash_rate > upper_bound { return upper_bound; } last_epoch_hash_rate } // Apply the dampening filter on epoch_length calculate fn bounding_epoch_length( &self, length: BlockNumber, last_epoch_length: BlockNumber, ) -> (BlockNumber, bool) { let max_length = cmp::min(self.max_epoch_length(), last_epoch_length * TAU); let min_length = cmp::max(self.min_epoch_length(), last_epoch_length / TAU); if length > max_length { (max_length, true) } else if length < min_length { (min_length, true) } else { (length, false) } } /// The [dynamic-difficulty-adjustment-mechanism](https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0020-ckb-consensus-protocol/0020-ckb-consensus-protocol.md#dynamic-difficulty-adjustment-mechanism) /// implementation pub fn next_epoch_ext( &self, header: &HeaderView, provider: &P, ) -> Option { provider .get_block_epoch(header) .map(|block_epoch| match block_epoch { BlockEpoch::NonTailBlock { epoch } => NextBlockEpoch::NonHeadBlock(epoch), BlockEpoch::TailBlock { epoch, epoch_uncles_count, epoch_duration_in_milliseconds, } => { if self.permanent_difficulty() { let next_epoch_length = (self.epoch_duration_target() + MIN_BLOCK_INTERVAL - 1) / MIN_BLOCK_INTERVAL; let primary_epoch_reward = self.primary_epoch_reward_of_next_epoch(&epoch).as_u64(); let block_reward = Capacity::shannons(primary_epoch_reward / next_epoch_length); let remainder_reward = Capacity::shannons(primary_epoch_reward % next_epoch_length); let dummy_epoch_ext = epoch .clone() .into_builder() .base_block_reward(block_reward) .remainder_reward(remainder_reward) .number(epoch.number() + 1) .last_block_hash_in_previous_epoch(header.hash()) .start_number(header.number() + 1) .length(next_epoch_length) .build(); NextBlockEpoch::HeadBlock(dummy_epoch_ext) } else { // (1) Computing the Adjusted Hash Rate Estimation let last_difficulty = &header.difficulty(); let last_epoch_duration = U256::from(cmp::max( epoch_duration_in_milliseconds / MILLISECONDS_IN_A_SECOND, 1, )); let last_epoch_hash_rate = last_difficulty * (epoch.length() + epoch_uncles_count) / &last_epoch_duration; let adjusted_last_epoch_hash_rate = cmp::max( self.bounding_hash_rate( last_epoch_hash_rate, epoch.previous_epoch_hash_rate().to_owned(), ), U256::one(), ); // (2) Computing the Next Epoch’s Main Chain Block Number let orphan_rate_target = self.orphan_rate_target(); let epoch_duration_target = self.epoch_duration_target(); let epoch_duration_target_u256 = U256::from(self.epoch_duration_target()); let last_epoch_length_u256 = U256::from(epoch.length()); let last_orphan_rate = RationalU256::new( U256::from(epoch_uncles_count), last_epoch_length_u256.clone(), ); let (next_epoch_length, bound) = if epoch_uncles_count == 0 { ( cmp::min(self.max_epoch_length(), epoch.length() * TAU), true, ) } else { // o_ideal * (1 + o_i ) * L_ideal * C_i,m let numerator = orphan_rate_target * (&last_orphan_rate + U256::one()) * &epoch_duration_target_u256 * &last_epoch_length_u256; // o_i * (1 + o_ideal ) * L_i let denominator = &last_orphan_rate * (orphan_rate_target + U256::one()) * &last_epoch_duration; let raw_next_epoch_length = u256_low_u64((numerator / denominator).into_u256()); self.bounding_epoch_length(raw_next_epoch_length, epoch.length()) }; // (3) Determining the Next Epoch’s Difficulty let next_epoch_length_u256 = U256::from(next_epoch_length); let diff_numerator = RationalU256::new( &adjusted_last_epoch_hash_rate * epoch_duration_target, U256::one(), ); let diff_denominator = if bound { if last_orphan_rate.is_zero() { RationalU256::new(next_epoch_length_u256, U256::one()) } else { let orphan_rate_estimation_recip = ((&last_orphan_rate + U256::one()) * &epoch_duration_target_u256 * &last_epoch_length_u256 / (&last_orphan_rate * &last_epoch_duration * &next_epoch_length_u256)) .saturating_sub_u256(U256::one()); if orphan_rate_estimation_recip.is_zero() { // small probability event, use o_ideal for now (orphan_rate_target + U256::one()) * next_epoch_length_u256 } else { let orphan_rate_estimation = RationalU256::one() / orphan_rate_estimation_recip; (orphan_rate_estimation + U256::one()) * next_epoch_length_u256 } } } else { (orphan_rate_target + U256::one()) * next_epoch_length_u256 }; let next_epoch_diff = if diff_numerator > diff_denominator { (diff_numerator / diff_denominator).into_u256() } else { // next_epoch_diff cannot be zero U256::one() }; let primary_epoch_reward = self.primary_epoch_reward_of_next_epoch(&epoch).as_u64(); let block_reward = Capacity::shannons(primary_epoch_reward / next_epoch_length); let remainder_reward = Capacity::shannons(primary_epoch_reward % next_epoch_length); let epoch_ext = EpochExt::new_builder() .number(epoch.number() + 1) .base_block_reward(block_reward) .remainder_reward(remainder_reward) .previous_epoch_hash_rate(adjusted_last_epoch_hash_rate) .last_block_hash_in_previous_epoch(header.hash()) .start_number(header.number() + 1) .length(next_epoch_length) .compact_target(difficulty_to_compact(next_epoch_diff)) .build(); NextBlockEpoch::HeadBlock(epoch_ext) } } }) } /// The network identify name, used for network identify protocol pub fn identify_name(&self) -> String { let genesis_hash = format!("{:x}", Unpack::::unpack(&self.genesis_hash)); format!("/{}/{}", self.id, &genesis_hash[..8]) } /// The secp256k1_blake160_sighash_all code hash pub fn get_secp_type_script_hash(&self) -> Byte32 { let secp_cell_data = Resource::bundled("specs/cells/secp256k1_blake160_sighash_all".to_string()) .get() .expect("Load secp script data failed"); let genesis_cellbase = &self.genesis_block().transactions()[0]; genesis_cellbase .outputs() .into_iter() .zip(genesis_cellbase.outputs_data()) .find(|(_, data)| data.raw_data() == secp_cell_data.as_ref()) .and_then(|(output, _)| { output .type_() .to_opt() .map(|script| script.calc_script_hash()) }) .expect("Can not find secp script") } fn primary_epoch_reward_of_next_epoch(&self, epoch: &EpochExt) -> Capacity { if (epoch.number() + 1) % self.primary_epoch_reward_halving_interval() != 0 { epoch.primary_reward() } else { self.primary_epoch_reward(epoch.number() + 1) } } /// Returns the hardfork switch. pub fn hardfork_switch(&self) -> &HardForks { &self.hardfork_switch } /// Returns whether rfc0044 is active based on the epoch number pub fn rfc0044_active(&self, target: EpochNumber) -> bool { let rfc0044_active_epoch = match self.id.as_str() { mainnet::CHAIN_SPEC_NAME => softfork::mainnet::RFC0044_ACTIVE_EPOCH, testnet::CHAIN_SPEC_NAME => softfork::testnet::RFC0044_ACTIVE_EPOCH, _ => 0, }; target >= rfc0044_active_epoch } /// Returns what version a new block should use. pub fn compute_versionbits( &self, parent: &HeaderView, indexer: &I, ) -> Option { let mut version = versionbits::VERSIONBITS_TOP_BITS; for pos in self.deployments.keys() { let versionbits = Versionbits::new(*pos, self); let cache = self.versionbits_caches.cache(pos)?; let state = versionbits.get_state(parent, cache, indexer)?; if state == versionbits::ThresholdState::LockedIn || state == versionbits::ThresholdState::Started { version |= versionbits.mask(); } } Some(version) } /// Returns specified softfork deployment state pub fn versionbits_state( &self, pos: DeploymentPos, parent: &HeaderView, indexer: &I, ) -> Option { let cache = self.versionbits_caches.cache(&pos)?; let versionbits = Versionbits::new(pos, self); versionbits.get_state(parent, cache, indexer) } /// Returns the first epoch which the current state applies pub fn versionbits_state_since_epoch( &self, pos: DeploymentPos, parent: &HeaderView, indexer: &I, ) -> Option { let cache = self.versionbits_caches.cache(&pos)?; let versionbits = Versionbits::new(pos, self); versionbits.get_state_since_epoch(parent, cache, indexer) } /// If the CKB block chain specification is for an public chain. pub fn is_public_chain(&self) -> bool { matches!( self.id.as_str(), mainnet::CHAIN_SPEC_NAME | testnet::CHAIN_SPEC_NAME ) } /// Return true if specifies epoch is between delay window. pub fn is_in_delay_window(&self, epoch: &EpochNumberWithFraction) -> bool { let proposal_window = self.tx_proposal_window(); let epoch_length = epoch.length(); let index = epoch.index(); let epoch_number = epoch.number(); let rfc_0049 = self.hardfork_switch.ckb2023.rfc_0049(); // dev default is 0 if rfc_0049 != 0 && rfc_0049 != EpochNumber::MAX { return (epoch_number + 1 == rfc_0049 && (proposal_window.farthest() + index) >= epoch_length) || (epoch_number == rfc_0049 && index <= proposal_window.farthest()); } false } } /// Trait for consensus provider. pub trait ConsensusProvider { /// Returns the `Consensus`. fn get_consensus(&self) -> &Consensus; } /// Corresponding epoch information of next block pub enum NextBlockEpoch { /// Next block is the head block of epoch HeadBlock(EpochExt), /// Next block is not the head block of epoch NonHeadBlock(EpochExt), } impl NextBlockEpoch { /// Returns epoch information pub fn epoch(self) -> EpochExt { match self { Self::HeadBlock(epoch_ext) => epoch_ext, Self::NonHeadBlock(epoch_ext) => epoch_ext, } } /// Is a head block of epoch pub fn is_head(&self) -> bool { matches!(*self, Self::HeadBlock(_)) } } impl From for ckb_jsonrpc_types::Consensus { fn from(consensus: Consensus) -> Self { let mut softforks = HashMap::new(); for (pos, deployment) in consensus.deployments { softforks.insert( pos.into(), ckb_jsonrpc_types::SoftFork::new_rfc0043(deployment.into()), ); } match consensus.id.as_str() { mainnet::CHAIN_SPEC_NAME => { softforks.insert( DeploymentPos::LightClient.into(), ckb_jsonrpc_types::SoftFork::new_buried( true, softfork::mainnet::RFC0044_ACTIVE_EPOCH.into(), ), ); } testnet::CHAIN_SPEC_NAME => { softforks.insert( DeploymentPos::LightClient.into(), ckb_jsonrpc_types::SoftFork::new_buried( true, softfork::testnet::RFC0044_ACTIVE_EPOCH.into(), ), ); } _ => {} }; Self { id: consensus.id, genesis_hash: consensus.genesis_hash.unpack(), dao_type_hash: consensus.dao_type_hash.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, hardfork_features: ckb_jsonrpc_types::HardForks::new(&consensus.hardfork_switch), softforks, } } } // most simple and efficient way for now fn u256_low_u64(u: U256) -> u64 { u.0[0] }