mod convert;
use crate::consensus::Consensus;
use ckb_types::{
core::{EpochExt, EpochNumber, HeaderView, Ratio, TransactionView, Version},
packed::{Byte32, CellbaseWitnessReader},
prelude::*,
};
use ckb_util::Mutex;
use std::collections::{hash_map, HashMap};
use std::sync::Arc;
pub const VERSIONBITS_TOP_BITS: Version = 0x00000000;
pub const VERSIONBITS_TOP_MASK: Version = 0xE0000000;
pub const VERSIONBITS_NUM_BITS: u32 = 29;
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum ThresholdState {
Defined,
Started,
LockedIn,
Active,
Failed,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum ActiveMode {
Normal,
Always,
Never,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
pub enum DeploymentPos {
Testdummy,
LightClient,
}
pub trait VersionbitsIndexer {
fn block_epoch_index(&self, block_hash: &Byte32) -> Option<Byte32>;
fn epoch_ext(&self, index: &Byte32) -> Option<EpochExt>;
fn block_header(&self, block_hash: &Byte32) -> Option<HeaderView>;
fn cellbase(&self, block_hash: &Byte32) -> Option<TransactionView>;
fn ancestor_epoch(&self, index: &Byte32, target: EpochNumber) -> Option<EpochExt> {
let mut epoch_ext = self.epoch_ext(index)?;
if epoch_ext.number() < target {
return None;
}
while epoch_ext.number() > target {
let last_block_header_in_previous_epoch =
self.block_header(&epoch_ext.last_block_hash_in_previous_epoch())?;
let previous_epoch_index =
self.block_epoch_index(&last_block_header_in_previous_epoch.hash())?;
epoch_ext = self.epoch_ext(&previous_epoch_index)?;
}
Some(epoch_ext)
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Deployment {
pub bit: u8,
pub start: EpochNumber,
pub timeout: EpochNumber,
pub min_activation_epoch: EpochNumber,
pub period: EpochNumber,
pub active_mode: ActiveMode,
pub threshold: Ratio,
}
type Cache = Mutex<HashMap<Byte32, ThresholdState>>;
#[derive(Clone, Debug, Default)]
pub struct VersionbitsCache {
caches: Arc<HashMap<DeploymentPos, Cache>>,
}
impl VersionbitsCache {
pub fn new<'a>(deployments: impl Iterator<Item = &'a DeploymentPos>) -> Self {
let caches: HashMap<_, _> = deployments
.map(|pos| (*pos, Mutex::new(HashMap::new())))
.collect();
VersionbitsCache {
caches: Arc::new(caches),
}
}
pub fn cache(&self, pos: &DeploymentPos) -> Option<&Cache> {
self.caches.get(pos)
}
}
pub struct Versionbits<'a> {
id: DeploymentPos,
consensus: &'a Consensus,
}
pub trait VersionbitsConditionChecker {
fn start(&self) -> EpochNumber;
fn timeout(&self) -> EpochNumber;
fn active_mode(&self) -> ActiveMode;
fn condition<I: VersionbitsIndexer>(&self, header: &HeaderView, indexer: &I) -> bool;
fn min_activation_epoch(&self) -> EpochNumber;
fn period(&self) -> EpochNumber;
fn threshold(&self) -> Ratio;
fn get_state<I: VersionbitsIndexer>(
&self,
header: &HeaderView,
cache: &Cache,
indexer: &I,
) -> Option<ThresholdState> {
let active_mode = self.active_mode();
let start = self.start();
let timeout = self.timeout();
let period = self.period();
let min_activation_epoch = self.min_activation_epoch();
if active_mode == ActiveMode::Always {
return Some(ThresholdState::Active);
}
if active_mode == ActiveMode::Never {
return Some(ThresholdState::Failed);
}
let start_index = indexer.block_epoch_index(&header.hash())?;
let epoch_number = header.epoch().number();
let target = epoch_number.saturating_sub((epoch_number + 1) % period);
let mut epoch_ext = indexer.ancestor_epoch(&start_index, target)?;
let mut g_cache = cache.lock();
let mut to_compute = Vec::new();
let mut state = loop {
let epoch_index = epoch_ext.last_block_hash_in_previous_epoch();
match g_cache.entry(epoch_index.clone()) {
hash_map::Entry::Occupied(entry) => {
break *entry.get();
}
hash_map::Entry::Vacant(entry) => {
if epoch_ext.is_genesis() || epoch_ext.number() < start {
entry.insert(ThresholdState::Defined);
break ThresholdState::Defined;
}
let next_epoch_ext = indexer
.ancestor_epoch(&epoch_index, epoch_ext.number().saturating_sub(period))?;
to_compute.push(epoch_ext);
epoch_ext = next_epoch_ext;
}
}
};
while let Some(epoch_ext) = to_compute.pop() {
let mut next_state = state;
match state {
ThresholdState::Defined => {
if epoch_ext.number() >= start {
next_state = ThresholdState::Started;
}
}
ThresholdState::Started => {
debug_assert!(epoch_ext.number() + 1 >= period);
let mut count = 0;
let mut total = 0;
let mut header =
indexer.block_header(&epoch_ext.last_block_hash_in_previous_epoch())?;
let mut current_epoch_ext = epoch_ext.clone();
for _ in 0..period {
let current_epoch_length = current_epoch_ext.length();
total += current_epoch_length;
for _ in 0..current_epoch_length {
if self.condition(&header, indexer) {
count += 1;
}
header = indexer.block_header(&header.parent_hash())?;
}
let last_block_header_in_previous_epoch = indexer
.block_header(¤t_epoch_ext.last_block_hash_in_previous_epoch())?;
let previous_epoch_index = indexer
.block_epoch_index(&last_block_header_in_previous_epoch.hash())?;
current_epoch_ext = indexer.epoch_ext(&previous_epoch_index)?;
}
let threshold_number = threshold_number(total, self.threshold())?;
if count >= threshold_number {
next_state = ThresholdState::LockedIn;
} else if epoch_ext.number() >= timeout {
next_state = ThresholdState::Failed;
}
}
ThresholdState::LockedIn => {
if epoch_ext.number() >= min_activation_epoch {
next_state = ThresholdState::Active;
}
}
ThresholdState::Failed | ThresholdState::Active => {
}
}
state = next_state;
g_cache.insert(epoch_ext.last_block_hash_in_previous_epoch(), state);
}
Some(state)
}
fn get_state_since_epoch<I: VersionbitsIndexer>(
&self,
header: &HeaderView,
cache: &Cache,
indexer: &I,
) -> Option<EpochNumber> {
if matches!(self.active_mode(), ActiveMode::Always | ActiveMode::Never) {
return Some(0);
}
let period = self.period();
let init_state = self.get_state(header, cache, indexer)?;
if init_state == ThresholdState::Defined {
return Some(0);
}
if init_state == ThresholdState::Started {
return Some(self.start());
}
let index = indexer.block_epoch_index(&header.hash())?;
let epoch_number = header.epoch().number();
let period_start = epoch_number.saturating_sub((epoch_number + 1) % period);
let mut epoch_ext = indexer.ancestor_epoch(&index, period_start)?;
let mut epoch_index = epoch_ext.last_block_hash_in_previous_epoch();
let g_cache = cache.lock();
while let Some(prev_epoch_ext) =
indexer.ancestor_epoch(&epoch_index, epoch_ext.number().saturating_sub(period))
{
epoch_ext = prev_epoch_ext;
epoch_index = epoch_ext.last_block_hash_in_previous_epoch();
if let Some(state) = g_cache.get(&epoch_index) {
if state != &init_state {
break;
}
} else {
break;
}
}
Some(epoch_ext.number().saturating_add(period))
}
}
impl<'a> Versionbits<'a> {
pub fn new(id: DeploymentPos, consensus: &'a Consensus) -> Self {
Versionbits { id, consensus }
}
fn deployment(&self) -> &Deployment {
&self.consensus.deployments[&self.id]
}
pub fn mask(&self) -> u32 {
1u32 << self.deployment().bit as u32
}
}
impl<'a> VersionbitsConditionChecker for Versionbits<'a> {
fn start(&self) -> EpochNumber {
self.deployment().start
}
fn timeout(&self) -> EpochNumber {
self.deployment().timeout
}
fn period(&self) -> EpochNumber {
self.deployment().period
}
fn condition<I: VersionbitsIndexer>(&self, header: &HeaderView, indexer: &I) -> bool {
if let Some(cellbase) = indexer.cellbase(&header.hash()) {
if let Some(witness) = cellbase.witnesses().get(0) {
if let Ok(reader) = CellbaseWitnessReader::from_slice(&witness.raw_data()) {
let message = reader.message().to_entity();
if message.len() >= 4 {
if let Ok(raw) = message.raw_data()[..4].try_into() {
let version = u32::from_le_bytes(raw);
return ((version & VERSIONBITS_TOP_MASK) == VERSIONBITS_TOP_BITS)
&& (version & self.mask()) != 0;
}
}
}
}
}
false
}
fn min_activation_epoch(&self) -> EpochNumber {
self.deployment().min_activation_epoch
}
fn active_mode(&self) -> ActiveMode {
self.deployment().active_mode
}
fn threshold(&self) -> Ratio {
self.deployment().threshold
}
}
fn threshold_number(length: u64, threshold: Ratio) -> Option<u64> {
length
.checked_mul(threshold.numer())
.and_then(|ret| ret.checked_div(threshold.denom()))
}