#![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;
pub(crate) const DEFAULT_SECONDARY_EPOCH_REWARD: Capacity = Capacity::shannons(613_698_63013698);
pub(crate) const INITIAL_PRIMARY_EPOCH_REWARD: Capacity = Capacity::shannons(1_917_808_21917808);
const MAX_UNCLE_NUM: usize = 2;
pub(crate) const TX_PROPOSAL_WINDOW: ProposalWindow = ProposalWindow(2, 10);
pub(crate) const CELLBASE_MATURITY: EpochNumberWithFraction =
EpochNumberWithFraction::new_unchecked(4, 0, 1);
const MEDIAN_TIME_BLOCK_COUNT: usize = 37;
pub(crate) const GENESIS_EPOCH_LENGTH: u64 = 1_000;
pub(crate) const DEFAULT_ORPHAN_RATE_TARGET: (u32, u32) = (1, 40);
pub const MAX_BLOCK_INTERVAL: u64 = 48;
pub const MIN_BLOCK_INTERVAL: u64 = 8;
pub const TWO_IN_TWO_OUT_CYCLES: Cycle = 3_500_000;
pub const TWO_IN_TWO_OUT_BYTES: u64 = 597;
const TWO_IN_TWO_OUT_COUNT: u64 = 1_000;
pub(crate) const DEFAULT_EPOCH_DURATION_TARGET: u64 = 4 * 60 * 60; const MILLISECONDS_IN_A_SECOND: u64 = 1000;
const MAX_EPOCH_LENGTH: u64 = DEFAULT_EPOCH_DURATION_TARGET / MIN_BLOCK_INTERVAL; const MIN_EPOCH_LENGTH: u64 = DEFAULT_EPOCH_DURATION_TARGET / MAX_BLOCK_INTERVAL; pub(crate) const DEFAULT_PRIMARY_EPOCH_REWARD_HALVING_INTERVAL: EpochNumber =
4 * 365 * 24 * 60 * 60 / DEFAULT_EPOCH_DURATION_TARGET; 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;
pub const MAX_BLOCK_PROPOSALS_LIMIT: u64 = 1_500;
const PROPOSER_REWARD_RATIO: Ratio = Ratio::new(4, 10);
pub(crate) const SATOSHI_PUBKEY_HASH: H160 = h160!("0x62e907b15cbf27d5425399ebf6f0fb50ebb88f18");
pub(crate) const SATOSHI_CELL_OCCUPIED_RATIO: Ratio = Ratio::new(6, 10);
pub const LC_MAINNET_ACTIVATION_THRESHOLD: Ratio = Ratio::new(8, 10);
pub const TESTNET_ACTIVATION_THRESHOLD: Ratio = Ratio::new(3, 4);
pub(crate) const STARTING_BLOCK_LIMITING_DAO_WITHDRAWING_LOCK: u64 = 10_000_000;
#[derive(Clone, PartialEq, Debug, Eq, Copy)]
pub struct ProposalWindow(pub BlockNumber, pub BlockNumber);
pub const TYPE_ID_CODE_HASH: H256 = h256!("0x545950455f4944");
impl ProposalWindow {
pub fn closest(&self) -> BlockNumber {
self.0
}
pub fn farthest(&self) -> BlockNumber {
self.1
}
pub fn length(&self) -> BlockNumber {
self.1 - self.0 + 1
}
}
pub struct ConsensusBuilder {
inner: Consensus,
}
impl Default for ConsensusBuilder {
fn default() -> Self {
let input = CellInput::new_cellbase_input(0);
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)
}
}
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()
}
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 {
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<Byte32> {
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())
}
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
}
pub fn id(mut self, id: String) -> Self {
self.inner.id = id;
self
}
pub fn genesis_block(mut self, genesis_block: BlockView) -> Self {
self.inner.genesis_block = genesis_block;
self
}
#[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
}
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
}
#[must_use]
pub fn secondary_epoch_reward(mut self, secondary_epoch_reward: Capacity) -> Self {
self.inner.secondary_epoch_reward = secondary_epoch_reward;
self
}
#[must_use]
pub fn max_block_cycles(mut self, max_block_cycles: Cycle) -> Self {
self.inner.max_block_cycles = max_block_cycles;
self
}
#[must_use]
pub fn max_block_bytes(mut self, max_block_bytes: u64) -> Self {
self.inner.max_block_bytes = max_block_bytes;
self
}
#[must_use]
pub fn cellbase_maturity(mut self, cellbase_maturity: EpochNumberWithFraction) -> Self {
self.inner.cellbase_maturity = cellbase_maturity;
self
}
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
}
pub fn tx_proposal_window(mut self, proposal_window: ProposalWindow) -> Self {
self.inner.tx_proposal_window = proposal_window;
self
}
pub fn pow(mut self, pow: Pow) -> Self {
self.inner.pow = pow;
self
}
pub fn satoshi_pubkey_hash(mut self, pubkey_hash: H160) -> Self {
self.inner.satoshi_pubkey_hash = pubkey_hash;
self
}
pub fn satoshi_cell_occupied_ratio(mut self, ratio: Ratio) -> Self {
self.inner.satoshi_cell_occupied_ratio = ratio;
self
}
#[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
}
#[must_use]
pub fn epoch_duration_target(mut self, target: u64) -> Self {
self.inner.epoch_duration_target = target;
self
}
#[must_use]
pub fn permanent_difficulty_in_dummy(mut self, permanent: bool) -> Self {
self.inner.permanent_difficulty_in_dummy = permanent;
self
}
#[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
}
pub fn hardfork_switch(mut self, hardfork_switch: HardForks) -> Self {
self.inner.hardfork_switch = hardfork_switch;
self
}
pub fn softfork_deployments(mut self, deployments: HashMap<DeploymentPos, Deployment>) -> Self {
self.inner.versionbits_caches = VersionbitsCache::new(deployments.keys());
self.inner.deployments = deployments;
self
}
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
}
}
#[derive(Clone, Debug)]
pub struct Consensus {
pub id: String,
pub genesis_block: BlockView,
pub genesis_hash: Byte32,
pub dao_type_hash: Byte32,
pub secp256k1_blake160_sighash_all_type_hash: Option<Byte32>,
pub secp256k1_blake160_multisig_all_type_hash: Option<Byte32>,
pub initial_primary_epoch_reward: Capacity,
pub secondary_epoch_reward: Capacity,
pub max_uncles_num: usize,
pub orphan_rate_target: RationalU256,
pub epoch_duration_target: u64,
pub tx_proposal_window: ProposalWindow,
pub proposer_reward_ratio: Ratio,
pub pow: Pow,
pub cellbase_maturity: EpochNumberWithFraction,
pub median_time_block_count: usize,
pub max_block_cycles: Cycle,
pub max_block_bytes: u64,
pub block_version: Version,
pub tx_version: Version,
pub type_id_code_hash: H256,
pub max_block_proposals_limit: u64,
pub genesis_epoch_ext: EpochExt,
pub satoshi_pubkey_hash: H160,
pub satoshi_cell_occupied_ratio: Ratio,
pub primary_epoch_reward_halving_interval: EpochNumber,
pub permanent_difficulty_in_dummy: bool,
pub hardfork_switch: HardForks,
pub deployments: HashMap<DeploymentPos, Deployment>,
pub versionbits_caches: VersionbitsCache,
pub starting_block_limiting_dao_withdrawing_lock: u64,
}
impl Default for Consensus {
fn default() -> Self {
ConsensusBuilder::default().build()
}
}
#[allow(clippy::op_ref)]
impl Consensus {
pub fn genesis_block(&self) -> &BlockView {
&self.genesis_block
}
pub fn proposer_reward_ratio(&self) -> Ratio {
self.proposer_reward_ratio
}
pub fn finalization_delay_length(&self) -> BlockNumber {
self.tx_proposal_window.farthest() + 1
}
pub fn finalize_target(&self, block_number: BlockNumber) -> Option<BlockNumber> {
if block_number != 0 {
Some(block_number.saturating_sub(self.finalization_delay_length()))
} else {
None
}
}
pub fn genesis_hash(&self) -> Byte32 {
self.genesis_hash.clone()
}
pub fn dao_type_hash(&self) -> Byte32 {
self.dao_type_hash.clone()
}
pub fn secp256k1_blake160_sighash_all_type_hash(&self) -> Option<Byte32> {
self.secp256k1_blake160_sighash_all_type_hash.clone()
}
pub fn secp256k1_blake160_multisig_all_type_hash(&self) -> Option<Byte32> {
self.secp256k1_blake160_multisig_all_type_hash.clone()
}
pub fn max_uncles_num(&self) -> usize {
self.max_uncles_num
}
pub fn min_difficulty(&self) -> U256 {
self.genesis_block.difficulty()
}
pub fn initial_primary_epoch_reward(&self) -> Capacity {
self.initial_primary_epoch_reward
}
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)
}
pub fn primary_epoch_reward_halving_interval(&self) -> EpochNumber {
self.primary_epoch_reward_halving_interval
}
pub fn epoch_duration_target(&self) -> u64 {
self.epoch_duration_target
}
pub fn genesis_epoch_ext(&self) -> &EpochExt {
&self.genesis_epoch_ext
}
pub fn max_epoch_length(&self) -> BlockNumber {
MAX_EPOCH_LENGTH
}
pub fn min_epoch_length(&self) -> BlockNumber {
MIN_EPOCH_LENGTH
}
pub fn secondary_epoch_reward(&self) -> Capacity {
self.secondary_epoch_reward
}
pub fn orphan_rate_target(&self) -> &RationalU256 {
&self.orphan_rate_target
}
pub fn pow_engine(&self) -> Arc<dyn PowEngine> {
self.pow.engine()
}
pub fn permanent_difficulty(&self) -> bool {
self.pow.is_dummy() && self.permanent_difficulty_in_dummy
}
pub fn cellbase_maturity(&self) -> EpochNumberWithFraction {
self.cellbase_maturity
}
pub fn median_time_block_count(&self) -> usize {
self.median_time_block_count
}
pub fn max_block_cycles(&self) -> Cycle {
self.max_block_cycles
}
pub fn max_block_bytes(&self) -> u64 {
self.max_block_bytes
}
pub fn max_block_proposals_limit(&self) -> u64 {
self.max_block_proposals_limit
}
pub fn block_version(&self) -> Version {
self.block_version
}
pub fn tx_version(&self) -> Version {
self.tx_version
}
pub fn type_id_code_hash(&self) -> &H256 {
&self.type_id_code_hash
}
pub fn tx_proposal_window(&self) -> ProposalWindow {
self.tx_proposal_window
}
pub fn starting_block_limiting_dao_withdrawing_lock(&self) -> u64 {
self.starting_block_limiting_dao_withdrawing_lock
}
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
}
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)
}
}
pub fn next_epoch_ext<P: EpochProvider>(
&self,
header: &HeaderView,
provider: &P,
) -> Option<NextBlockEpoch> {
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 {
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(),
);
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 {
let numerator = orphan_rate_target
* (&last_orphan_rate + U256::one())
* &epoch_duration_target_u256
* &last_epoch_length_u256;
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())
};
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() {
(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 {
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)
}
}
})
}
pub fn identify_name(&self) -> String {
let genesis_hash = format!("{:x}", Unpack::<H256>::unpack(&self.genesis_hash));
format!("/{}/{}", self.id, &genesis_hash[..8])
}
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)
}
}
pub fn hardfork_switch(&self) -> &HardForks {
&self.hardfork_switch
}
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
}
pub fn compute_versionbits<I: VersionbitsIndexer>(
&self,
parent: &HeaderView,
indexer: &I,
) -> Option<Version> {
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)
}
pub fn versionbits_state<I: VersionbitsIndexer>(
&self,
pos: DeploymentPos,
parent: &HeaderView,
indexer: &I,
) -> Option<ThresholdState> {
let cache = self.versionbits_caches.cache(&pos)?;
let versionbits = Versionbits::new(pos, self);
versionbits.get_state(parent, cache, indexer)
}
pub fn versionbits_state_since_epoch<I: VersionbitsIndexer>(
&self,
pos: DeploymentPos,
parent: &HeaderView,
indexer: &I,
) -> Option<EpochNumber> {
let cache = self.versionbits_caches.cache(&pos)?;
let versionbits = Versionbits::new(pos, self);
versionbits.get_state_since_epoch(parent, cache, indexer)
}
pub fn is_public_chain(&self) -> bool {
matches!(
self.id.as_str(),
mainnet::CHAIN_SPEC_NAME | testnet::CHAIN_SPEC_NAME
)
}
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();
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
}
}
pub trait ConsensusProvider {
fn get_consensus(&self) -> &Consensus;
}
pub enum NextBlockEpoch {
HeadBlock(EpochExt),
NonHeadBlock(EpochExt),
}
impl NextBlockEpoch {
pub fn epoch(self) -> EpochExt {
match self {
Self::HeadBlock(epoch_ext) => epoch_ext,
Self::NonHeadBlock(epoch_ext) => epoch_ext,
}
}
pub fn is_head(&self) -> bool {
matches!(*self, Self::HeadBlock(_))
}
}
impl From<Consensus> 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,
}
}
}
fn u256_low_u64(u: U256) -> u64 {
u.0[0]
}