ckb_chain_spec/versionbits/
mod.rs

1//! Versionbits defines a finite-state-machine to deploy a softfork in multiple stages.
2//!
3#![allow(dead_code)]
4
5mod convert;
6
7use crate::consensus::Consensus;
8#[cfg(not(target_family = "wasm"))]
9use ckb_logger::error;
10use ckb_types::global::DATA_DIR;
11use ckb_types::{
12    core::{EpochExt, EpochNumber, HeaderView, Ratio, TransactionView, Version},
13    packed::{Byte32, CellbaseWitnessReader},
14    prelude::*,
15};
16use std::fmt;
17use std::sync::Arc;
18use std::{collections::HashMap, path::PathBuf};
19
20/// What bits to set in version for versionbits blocks
21pub const VERSIONBITS_TOP_BITS: Version = 0x00000000;
22/// What bitmask determines whether versionbits is in use
23pub const VERSIONBITS_TOP_MASK: Version = 0xE0000000;
24/// Total bits available for versionbits
25pub const VERSIONBITS_NUM_BITS: u32 = 29;
26
27const PATH_PREFIX: &str = "softfork";
28
29/// RFC0043 defines a finite-state-machine to deploy a soft fork in multiple stages.
30/// State transitions happen during epoch if conditions are met
31/// In case of reorg, transitions can go backward. Without transition, state is
32/// inherited between epochs. All blocks of a epoch share the same state.
33#[derive(Copy, Clone, PartialEq, Eq, Debug)]
34#[repr(u8)]
35pub enum ThresholdState {
36    /// First state that each softfork starts.
37    /// The 0 epoch is by definition in this state for each deployment.
38    Defined = 0,
39    /// For epochs past the `start` epoch.
40    Started = 1,
41    /// For one epoch after the first epoch period with STARTED epochs of
42    /// which at least `threshold` has the associated bit set in `version`.
43    LockedIn = 2,
44    /// For all epochs after the LOCKED_IN epoch.
45    Active = 3,
46    /// For one epoch period past the `timeout_epoch`, if LOCKED_IN was not reached.
47    Failed = 4,
48}
49
50impl ThresholdState {
51    fn from_u8(value: u8) -> ThresholdState {
52        match value {
53            0 => ThresholdState::Defined,
54            1 => ThresholdState::Started,
55            2 => ThresholdState::LockedIn,
56            3 => ThresholdState::Active,
57            4 => ThresholdState::Failed,
58            _ => panic!("Unknown value: {}", value),
59        }
60    }
61}
62
63/// This is useful for testing, as it means tests don't need to deal with the activation
64/// process. Only tests that specifically test the behaviour during activation cannot use this.
65#[derive(Copy, Clone, PartialEq, Eq, Debug)]
66pub enum ActiveMode {
67    /// Indicating that the deployment is normal active.
68    Normal,
69    /// Indicating that the deployment is always active.
70    /// This is useful for testing, as it means tests don't need to deal with the activation
71    Always,
72    /// Indicating that the deployment is never active.
73    /// This is useful for testing.
74    Never,
75}
76
77/// Soft fork deployment
78#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
79pub enum DeploymentPos {
80    /// Dummy
81    Testdummy,
82    /// light client protocol
83    LightClient,
84}
85
86impl fmt::Display for DeploymentPos {
87    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88        write!(f, "{:?}", self)
89    }
90}
91
92/// VersionbitsIndexer
93pub trait VersionbitsIndexer {
94    /// Gets epoch index by block hash
95    fn block_epoch_index(&self, block_hash: &Byte32) -> Option<Byte32>;
96    /// Gets epoch ext by index
97    fn epoch_ext(&self, index: &Byte32) -> Option<EpochExt>;
98    /// Gets block header by block hash
99    fn block_header(&self, block_hash: &Byte32) -> Option<HeaderView>;
100    /// Gets cellbase by block hash
101    fn cellbase(&self, block_hash: &Byte32) -> Option<TransactionView>;
102    /// Gets ancestor of specified epoch.
103    fn ancestor_epoch(&self, index: &Byte32, target: EpochNumber) -> Option<EpochExt> {
104        let mut epoch_ext = self.epoch_ext(index)?;
105
106        if epoch_ext.number() < target {
107            return None;
108        }
109        while epoch_ext.number() > target {
110            let last_block_header_in_previous_epoch =
111                self.block_header(&epoch_ext.last_block_hash_in_previous_epoch())?;
112            let previous_epoch_index =
113                self.block_epoch_index(&last_block_header_in_previous_epoch.hash())?;
114            epoch_ext = self.epoch_ext(&previous_epoch_index)?;
115        }
116        Some(epoch_ext)
117    }
118}
119
120///Struct for each individual consensus rule change using soft fork.
121#[derive(Clone, PartialEq, Eq, Debug)]
122pub struct Deployment {
123    /// Determines which bit in the `version` field of the block is to be used to signal the softfork lock-in and activation.
124    /// It is chosen from the set {0,1,2,...,28}.
125    pub bit: u8,
126    /// Specifies the first epoch in which the bit gains meaning.
127    pub start: EpochNumber,
128    /// Specifies an epoch at which the miner signaling ends.
129    /// Once this epoch has been reached, if the softfork has not yet locked_in (excluding this epoch block's bit state),
130    /// the deployment is considered failed on all descendants of the block.
131    pub timeout: EpochNumber,
132    /// Specifies the epoch at which the softfork is allowed to become active.
133    pub min_activation_epoch: EpochNumber,
134    /// Specifies length of epochs of the signalling period.
135    pub period: EpochNumber,
136    /// This is useful for testing, as it means tests don't need to deal with the activation process
137    pub active_mode: ActiveMode,
138    /// Specifies the minimum ratio of block per `period`,
139    /// which indicate the locked_in of the softfork during the `period`.
140    pub threshold: Ratio,
141}
142
143/// Signal state cache
144///
145/// Persistent signal state cache, mmap-based cacache
146#[derive(Clone, Debug)]
147pub struct Cache {
148    path: PathBuf,
149}
150
151impl Cache {
152    /// Reads the entire contents of a cache file synchronously into a bytes vector,
153    /// looking the data up by key.
154    #[cfg(not(target_family = "wasm"))]
155    pub fn get(&self, key: &Byte32) -> Option<ThresholdState> {
156        match cacache::read_sync(&self.path, Self::encode_key(key)) {
157            Ok(bytes) => Some(Self::decode_value(bytes)),
158            Err(cacache::Error::EntryNotFound(_path, _key)) => None,
159            Err(err) => {
160                error!("cacache read_sync failed {:?}", err);
161                None
162            }
163        }
164    }
165
166    /// Soft Versionbit only work on tx-pool/block_assembler, it will not work on wasm,
167    /// so it can unimplemented
168    #[cfg(target_family = "wasm")]
169    pub fn get(&self, _key: &Byte32) -> Option<ThresholdState> {
170        unimplemented!()
171    }
172
173    /// Writes data to the cache synchronously
174    #[cfg(not(target_family = "wasm"))]
175    pub fn insert(&self, key: &Byte32, value: ThresholdState) {
176        if let Err(e) =
177            cacache::write_sync(&self.path, Self::encode_key(key), Self::encode_value(value))
178        {
179            error!("cacache write_sync failed {:?}", e);
180        }
181    }
182
183    /// Soft Versionbit only work on tx-pool/block_assembler, it will not work on wasm,
184    /// so it can unimplemented
185    #[cfg(target_family = "wasm")]
186    pub fn insert(&self, _key: &Byte32, _value: ThresholdState) {
187        unimplemented!()
188    }
189
190    fn decode_value(value: Vec<u8>) -> ThresholdState {
191        ThresholdState::from_u8(value[0])
192    }
193
194    fn encode_key(key: &Byte32) -> String {
195        format!("{}", key)
196    }
197
198    fn encode_value(value: ThresholdState) -> Vec<u8> {
199        vec![value as u8]
200    }
201}
202
203/// RFC0000 allows multiple soft forks to be deployed in parallel. We cache
204/// per-epoch state for every one of them. */
205#[derive(Clone, Debug, Default)]
206pub struct VersionbitsCache {
207    caches: Arc<HashMap<DeploymentPos, Cache>>,
208}
209
210impl VersionbitsCache {
211    /// Construct new VersionbitsCache instance from deployments
212    pub fn new<'a>(deployments: impl Iterator<Item = &'a DeploymentPos>) -> Self {
213        let default_dir = PathBuf::new();
214        let data_dir = DATA_DIR.get().unwrap_or(&default_dir);
215        let caches: HashMap<_, _> = deployments
216            .map(|pos| {
217                (
218                    *pos,
219                    Cache {
220                        path: data_dir.join(PATH_PREFIX).join(pos.to_string()),
221                    },
222                )
223            })
224            .collect();
225        VersionbitsCache {
226            caches: Arc::new(caches),
227        }
228    }
229
230    /// Returns a reference to the cache corresponding to the deployment.
231    pub fn cache(&self, pos: &DeploymentPos) -> Option<&Cache> {
232        self.caches.get(pos)
233    }
234}
235
236/// Struct Implements versionbits threshold logic, and caches results.
237pub struct Versionbits<'a> {
238    id: DeploymentPos,
239    consensus: &'a Consensus,
240}
241
242/// Trait that implements versionbits threshold logic, and caches results.
243pub trait VersionbitsConditionChecker {
244    /// Specifies the first epoch in which the bit gains meaning.
245    fn start(&self) -> EpochNumber;
246    /// Specifies an epoch at which the miner signaling ends.
247    /// Once this epoch has been reached,
248    /// if the softfork has not yet locked_in (excluding this epoch block's bit state),
249    /// the deployment is considered failed on all descendants of the block.
250    fn timeout(&self) -> EpochNumber;
251    /// Active mode for testing.
252    fn active_mode(&self) -> ActiveMode;
253    // fn condition(&self, header: &HeaderView) -> bool;
254    /// Determines whether bit in the `version` field of the block is to be used to signal
255    fn condition<I: VersionbitsIndexer>(&self, header: &HeaderView, indexer: &I) -> bool;
256    /// Specifies the epoch at which the softfork is allowed to become active.
257    fn min_activation_epoch(&self) -> EpochNumber;
258    /// The period for signal statistics are counted
259    fn period(&self) -> EpochNumber;
260    /// Specifies the minimum ratio of block per epoch,
261    /// which indicate the locked_in of the softfork during the epoch.
262    fn threshold(&self) -> Ratio;
263    /// Returns the state for a header. Applies any state transition if conditions are present.
264    /// Caches state from first block of period.
265    fn get_state<I: VersionbitsIndexer>(
266        &self,
267        header: &HeaderView,
268        cache: &Cache,
269        indexer: &I,
270    ) -> Option<ThresholdState> {
271        let active_mode = self.active_mode();
272        let start = self.start();
273        let timeout = self.timeout();
274        let period = self.period();
275        let min_activation_epoch = self.min_activation_epoch();
276
277        if active_mode == ActiveMode::Always {
278            return Some(ThresholdState::Active);
279        }
280
281        if active_mode == ActiveMode::Never {
282            return Some(ThresholdState::Failed);
283        }
284
285        let start_index = indexer.block_epoch_index(&header.hash())?;
286        let epoch_number = header.epoch().number();
287        let target = epoch_number.saturating_sub((epoch_number + 1) % period);
288
289        let mut epoch_ext = indexer.ancestor_epoch(&start_index, target)?;
290        let mut to_compute = Vec::new();
291        let mut state = loop {
292            let epoch_index = epoch_ext.last_block_hash_in_previous_epoch();
293            if let Some(value) = cache.get(&epoch_index) {
294                break value;
295            } else {
296                if epoch_ext.is_genesis() || epoch_ext.number() < start {
297                    cache.insert(&epoch_index, ThresholdState::Defined);
298                    break ThresholdState::Defined;
299                }
300                let next_epoch_ext = indexer
301                    .ancestor_epoch(&epoch_index, epoch_ext.number().saturating_sub(period))?;
302                to_compute.push(epoch_ext);
303                epoch_ext = next_epoch_ext;
304            }
305        };
306
307        while let Some(epoch_ext) = to_compute.pop() {
308            let mut next_state = state;
309
310            match state {
311                ThresholdState::Defined => {
312                    if epoch_ext.number() >= start {
313                        next_state = ThresholdState::Started;
314                    }
315                }
316                ThresholdState::Started => {
317                    // We need to count
318                    debug_assert!(epoch_ext.number() + 1 >= period);
319
320                    let mut count = 0;
321                    let mut total = 0;
322                    let mut header =
323                        indexer.block_header(&epoch_ext.last_block_hash_in_previous_epoch())?;
324
325                    let mut current_epoch_ext = epoch_ext.clone();
326                    for _ in 0..period {
327                        let current_epoch_length = current_epoch_ext.length();
328                        total += current_epoch_length;
329                        for _ in 0..current_epoch_length {
330                            if self.condition(&header, indexer) {
331                                count += 1;
332                            }
333                            header = indexer.block_header(&header.parent_hash())?;
334                        }
335                        let last_block_header_in_previous_epoch = indexer
336                            .block_header(&current_epoch_ext.last_block_hash_in_previous_epoch())?;
337                        let previous_epoch_index = indexer
338                            .block_epoch_index(&last_block_header_in_previous_epoch.hash())?;
339                        current_epoch_ext = indexer.epoch_ext(&previous_epoch_index)?;
340                    }
341
342                    let threshold_number = threshold_number(total, self.threshold())?;
343                    if count >= threshold_number {
344                        next_state = ThresholdState::LockedIn;
345                    } else if epoch_ext.number() >= timeout {
346                        next_state = ThresholdState::Failed;
347                    }
348                }
349                ThresholdState::LockedIn => {
350                    if epoch_ext.number() >= min_activation_epoch {
351                        next_state = ThresholdState::Active;
352                    }
353                }
354                ThresholdState::Failed | ThresholdState::Active => {
355                    // Nothing happens, these are terminal states.
356                }
357            }
358            state = next_state;
359            cache.insert(&epoch_ext.last_block_hash_in_previous_epoch(), state);
360        }
361
362        Some(state)
363    }
364
365    /// Returns the first epoch which the state applies
366    fn get_state_since_epoch<I: VersionbitsIndexer>(
367        &self,
368        header: &HeaderView,
369        cache: &Cache,
370        indexer: &I,
371    ) -> Option<EpochNumber> {
372        if matches!(self.active_mode(), ActiveMode::Always | ActiveMode::Never) {
373            return Some(0);
374        }
375        let period = self.period();
376
377        let init_state = self.get_state(header, cache, indexer)?;
378        if init_state == ThresholdState::Defined {
379            return Some(0);
380        }
381
382        if init_state == ThresholdState::Started {
383            return Some(self.start());
384        }
385
386        let index = indexer.block_epoch_index(&header.hash())?;
387        let epoch_number = header.epoch().number();
388        let period_start = epoch_number.saturating_sub((epoch_number + 1) % period);
389
390        let mut epoch_ext = indexer.ancestor_epoch(&index, period_start)?;
391        let mut epoch_index = epoch_ext.last_block_hash_in_previous_epoch();
392
393        while let Some(prev_epoch_ext) =
394            indexer.ancestor_epoch(&epoch_index, epoch_ext.number().saturating_sub(period))
395        {
396            epoch_ext = prev_epoch_ext;
397            epoch_index = epoch_ext.last_block_hash_in_previous_epoch();
398            if let Some(state) = cache.get(&epoch_index) {
399                if state != init_state {
400                    break;
401                }
402            } else {
403                break;
404            }
405        }
406
407        Some(epoch_ext.number().saturating_add(period))
408    }
409}
410
411impl<'a> Versionbits<'a> {
412    /// construct new Versionbits wrapper
413    pub fn new(id: DeploymentPos, consensus: &'a Consensus) -> Self {
414        Versionbits { id, consensus }
415    }
416
417    fn deployment(&self) -> &Deployment {
418        &self.consensus.deployments[&self.id]
419    }
420
421    /// return bit mask corresponding deployment
422    pub fn mask(&self) -> u32 {
423        1u32 << self.deployment().bit as u32
424    }
425}
426
427impl<'a> VersionbitsConditionChecker for Versionbits<'a> {
428    fn start(&self) -> EpochNumber {
429        self.deployment().start
430    }
431
432    fn timeout(&self) -> EpochNumber {
433        self.deployment().timeout
434    }
435
436    fn period(&self) -> EpochNumber {
437        self.deployment().period
438    }
439
440    fn condition<I: VersionbitsIndexer>(&self, header: &HeaderView, indexer: &I) -> bool {
441        if let Some(cellbase) = indexer.cellbase(&header.hash()) {
442            if let Some(witness) = cellbase.witnesses().get(0) {
443                if let Ok(reader) = CellbaseWitnessReader::from_slice(&witness.raw_data()) {
444                    let message = reader.message().to_entity();
445                    if message.len() >= 4 {
446                        if let Ok(raw) = message.raw_data()[..4].try_into() {
447                            let version = u32::from_le_bytes(raw);
448                            return ((version & VERSIONBITS_TOP_MASK) == VERSIONBITS_TOP_BITS)
449                                && (version & self.mask()) != 0;
450                        }
451                    }
452                }
453            }
454        }
455        false
456    }
457
458    // fn condition(&self, header: &HeaderView) -> bool {
459    //     let version = header.version();
460    //     (((version & VERSIONBITS_TOP_MASK) == VERSIONBITS_TOP_BITS) && (version & self.mask()) != 0)
461    // }
462
463    fn min_activation_epoch(&self) -> EpochNumber {
464        self.deployment().min_activation_epoch
465    }
466
467    fn active_mode(&self) -> ActiveMode {
468        self.deployment().active_mode
469    }
470
471    fn threshold(&self) -> Ratio {
472        self.deployment().threshold
473    }
474}
475
476fn threshold_number(length: u64, threshold: Ratio) -> Option<u64> {
477    length
478        .checked_mul(threshold.numer())
479        .and_then(|ret| ret.checked_div(threshold.denom()))
480}