ckb_chain_spec/versionbits/
mod.rs1#![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
20pub const VERSIONBITS_TOP_BITS: Version = 0x00000000;
22pub const VERSIONBITS_TOP_MASK: Version = 0xE0000000;
24pub const VERSIONBITS_NUM_BITS: u32 = 29;
26
27const PATH_PREFIX: &str = "softfork";
28
29#[derive(Copy, Clone, PartialEq, Eq, Debug)]
34#[repr(u8)]
35pub enum ThresholdState {
36 Defined = 0,
39 Started = 1,
41 LockedIn = 2,
44 Active = 3,
46 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#[derive(Copy, Clone, PartialEq, Eq, Debug)]
66pub enum ActiveMode {
67 Normal,
69 Always,
72 Never,
75}
76
77#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
79pub enum DeploymentPos {
80 Testdummy,
82 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
92pub trait VersionbitsIndexer {
94 fn block_epoch_index(&self, block_hash: &Byte32) -> Option<Byte32>;
96 fn epoch_ext(&self, index: &Byte32) -> Option<EpochExt>;
98 fn block_header(&self, block_hash: &Byte32) -> Option<HeaderView>;
100 fn cellbase(&self, block_hash: &Byte32) -> Option<TransactionView>;
102 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#[derive(Clone, PartialEq, Eq, Debug)]
122pub struct Deployment {
123 pub bit: u8,
126 pub start: EpochNumber,
128 pub timeout: EpochNumber,
132 pub min_activation_epoch: EpochNumber,
134 pub period: EpochNumber,
136 pub active_mode: ActiveMode,
138 pub threshold: Ratio,
141}
142
143#[derive(Clone, Debug)]
147pub struct Cache {
148 path: PathBuf,
149}
150
151impl Cache {
152 #[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 #[cfg(target_family = "wasm")]
169 pub fn get(&self, _key: &Byte32) -> Option<ThresholdState> {
170 unimplemented!()
171 }
172
173 #[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 #[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#[derive(Clone, Debug, Default)]
206pub struct VersionbitsCache {
207 caches: Arc<HashMap<DeploymentPos, Cache>>,
208}
209
210impl VersionbitsCache {
211 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 pub fn cache(&self, pos: &DeploymentPos) -> Option<&Cache> {
232 self.caches.get(pos)
233 }
234}
235
236pub struct Versionbits<'a> {
238 id: DeploymentPos,
239 consensus: &'a Consensus,
240}
241
242pub trait VersionbitsConditionChecker {
244 fn start(&self) -> EpochNumber;
246 fn timeout(&self) -> EpochNumber;
251 fn active_mode(&self) -> ActiveMode;
253 fn condition<I: VersionbitsIndexer>(&self, header: &HeaderView, indexer: &I) -> bool;
256 fn min_activation_epoch(&self) -> EpochNumber;
258 fn period(&self) -> EpochNumber;
260 fn threshold(&self) -> Ratio;
263 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 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(¤t_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 }
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 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 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 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 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}