ckb_script/
verify_env.rs

1//! Transaction verification environment.
2
3use ckb_chain_spec::consensus::ProposalWindow;
4use ckb_types::{
5    core::{BlockNumber, EpochNumber, EpochNumberWithFraction, HeaderView},
6    packed::Byte32,
7};
8
9/// The phase that transactions are in.
10#[derive(Debug, Clone, Copy)]
11enum TxVerifyPhase {
12    /// The transaction has just been submitted.
13    ///
14    /// So the transaction will be:
15    /// - proposed after (or in) the `tip_number + 1` block.
16    /// - committed after (or in) `tip_number + 1 + proposal_window.closest()` block.
17    Submitted,
18    /// The transaction has already been proposed before several blocks.
19    ///
20    /// Assume that the inner block number is `N`.
21    /// So the transaction is proposed in the `tip_number - N` block.
22    /// Then it will be committed after (or in) the `tip_number - N + proposal_window.closest()` block.
23    Proposed(BlockNumber),
24    /// The transaction is commit.
25    ///
26    /// So the transaction will be committed in current block.
27    Committed,
28}
29
30/// The environment that transactions are in.
31#[derive(Debug, Clone)]
32pub struct TxVerifyEnv {
33    // Please keep these fields to be private.
34    // So we can update this struct easier when we want to add more data.
35    phase: TxVerifyPhase,
36    // Current Tip Environment
37    number: BlockNumber,
38    epoch: EpochNumberWithFraction,
39    hash: Byte32,
40    parent_hash: Byte32,
41}
42
43impl TxVerifyEnv {
44    /// The transaction has just been submitted.
45    ///
46    /// The input is current tip header.
47    pub fn new_submit(header: &HeaderView) -> Self {
48        Self {
49            phase: TxVerifyPhase::Submitted,
50            number: header.number(),
51            epoch: header.epoch(),
52            hash: header.hash(),
53            parent_hash: header.parent_hash(),
54        }
55    }
56
57    /// The transaction has already been proposed before several blocks.
58    ///
59    /// The input is current tip header and how many blocks have been passed since the transaction was proposed.
60    pub fn new_proposed(header: &HeaderView, n_blocks: BlockNumber) -> Self {
61        Self {
62            phase: TxVerifyPhase::Proposed(n_blocks),
63            number: header.number(),
64            epoch: header.epoch(),
65            hash: header.hash(),
66            parent_hash: header.parent_hash(),
67        }
68    }
69
70    /// The transaction will committed in current block.
71    ///
72    /// The input is current tip header.
73    pub fn new_commit(header: &HeaderView) -> Self {
74        Self {
75            phase: TxVerifyPhase::Committed,
76            number: header.number(),
77            epoch: header.epoch(),
78            hash: header.hash(),
79            parent_hash: header.parent_hash(),
80        }
81    }
82
83    /// The block number of the earliest block which the transaction will committed in.
84    pub fn block_number(&self, proposal_window: ProposalWindow) -> BlockNumber {
85        match self.phase {
86            TxVerifyPhase::Submitted => self.number + 1 + proposal_window.closest(),
87            TxVerifyPhase::Proposed(already_proposed) => {
88                self.number.saturating_sub(already_proposed) + proposal_window.closest()
89            }
90            TxVerifyPhase::Committed => self.number,
91        }
92    }
93
94    /// The epoch number of the earliest epoch which the transaction will committed in.
95    pub fn epoch_number(&self, proposal_window: ProposalWindow) -> EpochNumber {
96        let n_blocks = match self.phase {
97            TxVerifyPhase::Submitted => 1 + proposal_window.closest(),
98            TxVerifyPhase::Proposed(already_proposed) => {
99                proposal_window.closest().saturating_sub(already_proposed)
100            }
101            TxVerifyPhase::Committed => 0,
102        };
103        self.epoch.minimum_epoch_number_after_n_blocks(n_blocks)
104    }
105
106    /// The parent block hash of the earliest block which the transaction will committed in.
107    pub fn parent_hash(&self) -> Byte32 {
108        match self.phase {
109            TxVerifyPhase::Submitted => &self.hash,
110            TxVerifyPhase::Proposed(_) => &self.hash,
111            TxVerifyPhase::Committed => &self.parent_hash,
112        }
113        .to_owned()
114    }
115
116    /// The earliest epoch which the transaction will committed in.
117    pub fn epoch(&self) -> EpochNumberWithFraction {
118        self.epoch
119    }
120
121    /// The epoch number of the earliest epoch which the transaction will committed in without
122    /// consider about the proposal window.
123    pub fn epoch_number_without_proposal_window(&self) -> EpochNumber {
124        let n_blocks = match self.phase {
125            TxVerifyPhase::Submitted | TxVerifyPhase::Proposed(_) => 1,
126            TxVerifyPhase::Committed => 0,
127        };
128        self.epoch.minimum_epoch_number_after_n_blocks(n_blocks)
129    }
130
131    /// Returns true if TxVerifyEnv is created to verify a submitted or
132    /// proposed tx
133    pub fn verifying_inflight_tx(&self) -> bool {
134        !self.verifying_committed_tx()
135    }
136
137    /// Returns true if TxVerifyEnv is created to verify a committed tx
138    /// in a block
139    pub fn verifying_committed_tx(&self) -> bool {
140        matches!(self.phase, TxVerifyPhase::Committed)
141    }
142}