ckb_sdk/traits/
offchain_impls.rs

1//! For for implement offchain operations or for testing purpose
2
3use std::collections::HashMap;
4
5use ckb_types::{
6    bytes::Bytes,
7    core::{HeaderView, TransactionView},
8    packed::{Byte32, CellDep, CellOutput, OutPoint, Script, Transaction},
9    prelude::*,
10    H256,
11};
12
13use crate::traits::{
14    CellCollectorError, CellDepResolver, CellQueryOptions, HeaderDepResolver, LiveCell,
15    TransactionDependencyError, TransactionDependencyProvider,
16};
17use crate::types::ScriptId;
18use anyhow::anyhow;
19
20/// A offchain cell_dep resolver
21#[derive(Default, Clone)]
22pub struct OffchainCellDepResolver {
23    pub items: HashMap<ScriptId, (CellDep, String)>,
24}
25impl CellDepResolver for OffchainCellDepResolver {
26    fn resolve(&self, script: &Script) -> Option<CellDep> {
27        let script_id = ScriptId::from(script);
28        self.items
29            .get(&script_id)
30            .map(|(cell_dep, _)| cell_dep.clone())
31    }
32}
33
34#[derive(Default, Clone)]
35pub struct OffchainHeaderDepResolver {
36    pub by_tx_hash: HashMap<H256, HeaderView>,
37    pub by_number: HashMap<u64, HeaderView>,
38}
39
40#[cfg_attr(target_arch="wasm32", async_trait::async_trait(?Send))]
41#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
42impl HeaderDepResolver for OffchainHeaderDepResolver {
43    async fn resolve_by_tx_async(
44        &self,
45        tx_hash: &Byte32,
46    ) -> Result<Option<HeaderView>, anyhow::Error> {
47        let tx_hash: H256 = tx_hash.unpack();
48        let header = self.by_tx_hash.get(&tx_hash).cloned();
49        Ok(header)
50    }
51    async fn resolve_by_number_async(
52        &self,
53        number: u64,
54    ) -> Result<Option<HeaderView>, anyhow::Error> {
55        let header = self.by_number.get(&number).cloned();
56
57        Ok(header)
58    }
59}
60
61const KEEP_BLOCK_PERIOD: u64 = 13;
62/// A cell collector only use offchain data
63#[derive(Default, Clone)]
64pub struct OffchainCellCollector {
65    // (block_hash, index) => tip_block_number
66    pub locked_cells: HashMap<(H256, u32), u64>,
67    // (live_cell, tip_block_number)
68    pub live_cells: Vec<(LiveCell, u64)>,
69    pub max_mature_number: u64,
70}
71
72pub(crate) struct CollectResult {
73    pub(crate) cells: Vec<(LiveCell, u64)>,
74    pub(crate) rest_cells: Vec<(LiveCell, u64)>,
75    pub(crate) total_capacity: u64,
76}
77impl OffchainCellCollector {
78    fn truncate(&mut self, current_tip_block_number: u64) {
79        self.live_cells = self
80            .live_cells
81            .clone()
82            .into_iter()
83            .filter(|(_cell, block_num)| {
84                *block_num >= current_tip_block_number
85                    || (current_tip_block_number - block_num) <= KEEP_BLOCK_PERIOD
86            })
87            .collect();
88        self.locked_cells = self
89            .locked_cells
90            .clone()
91            .into_iter()
92            .filter(|(_k, block_num)| {
93                *block_num >= current_tip_block_number
94                    || (current_tip_block_number - block_num) <= KEEP_BLOCK_PERIOD
95            })
96            .collect();
97    }
98
99    pub(crate) fn collect(
100        &mut self,
101        query: &CellQueryOptions,
102        tip_block_number: u64,
103    ) -> CollectResult {
104        self.truncate(tip_block_number);
105        let mut total_capacity = 0;
106        let (cells, rest_cells): (Vec<_>, Vec<_>) =
107            self.live_cells
108                .clone()
109                .into_iter()
110                .partition(|(cell, _tip_num)| {
111                    if total_capacity < query.min_total_capacity
112                        && query.match_cell(cell, self.max_mature_number)
113                    {
114                        let capacity: u64 = cell.output.capacity().unpack();
115                        total_capacity += capacity;
116                        true
117                    } else {
118                        false
119                    }
120                });
121        CollectResult {
122            cells,
123            rest_cells,
124            total_capacity,
125        }
126    }
127
128    pub(crate) fn lock_cell(
129        &mut self,
130        out_point: OutPoint,
131        tip_blocknumber: u64,
132    ) -> Result<(), CellCollectorError> {
133        self.locked_cells.insert(
134            (out_point.tx_hash().unpack(), out_point.index().unpack()),
135            tip_blocknumber,
136        );
137        Ok(())
138    }
139    pub(crate) fn apply_tx(
140        &mut self,
141        tx: Transaction,
142        tip_blocknumber: u64,
143    ) -> Result<(), CellCollectorError> {
144        let tx_view = tx.into_view();
145        let tx_hash = tx_view.hash();
146        for out_point in tx_view.input_pts_iter() {
147            self.lock_cell(out_point, tip_blocknumber)?;
148        }
149        for (output_index, (output, data)) in tx_view.outputs_with_data_iter().enumerate() {
150            let out_point = OutPoint::new(tx_hash.clone(), output_index as u32);
151            let info = LiveCell {
152                output: output.clone(),
153                output_data: data.clone(),
154                out_point,
155                block_number: 0,
156                tx_index: 0,
157            };
158            self.live_cells.push((info, tip_blocknumber));
159        }
160        Ok(())
161    }
162
163    pub(crate) fn reset(&mut self) {
164        self.locked_cells.clear();
165        self.live_cells.clear();
166    }
167}
168
169/// offchain transaction dependency provider
170#[derive(Default, Clone)]
171pub struct OffchainTransactionDependencyProvider {
172    pub tx_tip_num_map: HashMap<H256, u64>,
173    pub txs: HashMap<H256, TransactionView>,
174    pub cells: HashMap<(H256, u32), (CellOutput, Bytes)>,
175}
176
177impl OffchainTransactionDependencyProvider {
178    /// create a new OffchainTransactionDependencyProvider
179    pub(crate) fn new() -> Self {
180        OffchainTransactionDependencyProvider {
181            tx_tip_num_map: HashMap::new(),
182            txs: HashMap::new(),
183            cells: HashMap::new(),
184        }
185    }
186    /// Add newly create transaction, so it can get transaction offchain
187    pub(crate) fn apply_tx(
188        &mut self,
189        tx: Transaction,
190        tip_blocknumber: u64,
191    ) -> Result<(), TransactionDependencyError> {
192        self.truncate(tip_blocknumber);
193        let tx_view = tx.into_view();
194        let tx_hash: H256 = tx_view.hash().unpack();
195        self.tx_tip_num_map.insert(tx_hash.clone(), tip_blocknumber);
196        self.txs.insert(tx_hash.clone(), tx_view.clone());
197
198        for (idx, (cell_output, output_data)) in tx_view.outputs_with_data_iter().enumerate() {
199            self.cells
200                .insert((tx_hash.clone(), idx as u32), (cell_output, output_data));
201        }
202        Ok(())
203    }
204
205    /// Remove offchain data
206    pub(crate) fn truncate(&mut self, current_tip_block_number: u64) {
207        let (keep, removed) = self
208            .tx_tip_num_map
209            .clone()
210            .into_iter()
211            .partition(|(_k, v)| {
212                *v >= current_tip_block_number
213                    || (current_tip_block_number - v) >= KEEP_BLOCK_PERIOD
214            });
215        self.tx_tip_num_map = keep;
216        self.txs = self
217            .txs
218            .clone()
219            .into_iter()
220            .filter(|(k, _v)| !removed.contains_key(k))
221            .collect();
222        self.cells = self
223            .cells
224            .clone()
225            .into_iter()
226            .filter(|(k, _v)| !removed.contains_key(&k.0))
227            .collect();
228        // self.headers =self.headers.clone().into_iter().filter(|(k,_v)|!removed.contains(&k)).collect();
229    }
230}
231
232#[cfg_attr(target_arch="wasm32", async_trait::async_trait(?Send))]
233#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
234impl TransactionDependencyProvider for OffchainTransactionDependencyProvider {
235    // For verify certain cell belong to certain transaction
236    async fn get_transaction_async(
237        &self,
238        tx_hash: &Byte32,
239    ) -> Result<TransactionView, TransactionDependencyError> {
240        let tx_hash: H256 = tx_hash.unpack();
241        self.txs
242            .get(&tx_hash)
243            .cloned()
244            .ok_or_else(|| TransactionDependencyError::Other(anyhow!("offchain get_transaction")))
245    }
246    // For get the output information of inputs or cell_deps, those cell should be live cell
247    async fn get_cell_async(
248        &self,
249        out_point: &OutPoint,
250    ) -> Result<CellOutput, TransactionDependencyError> {
251        let tx_hash: H256 = out_point.tx_hash().unpack();
252        let index: u32 = out_point.index().unpack();
253        self.cells
254            .get(&(tx_hash, index))
255            .map(|(output, _)| output.clone())
256            .ok_or_else(|| TransactionDependencyError::Other(anyhow!("offchain get_cell")))
257    }
258    // For get the output data information of inputs or cell_deps
259    async fn get_cell_data_async(
260        &self,
261        out_point: &OutPoint,
262    ) -> Result<Bytes, TransactionDependencyError> {
263        let tx_hash: H256 = out_point.tx_hash().unpack();
264        let index: u32 = out_point.index().unpack();
265        self.cells
266            .get(&(tx_hash, index))
267            .map(|(_, data)| data.clone())
268            .ok_or_else(|| TransactionDependencyError::Other(anyhow!("offchain get_cell_data")))
269    }
270    // For get the header information of header_deps
271    async fn get_header_async(
272        &self,
273        _block_hash: &Byte32,
274    ) -> Result<HeaderView, TransactionDependencyError> {
275        Err(TransactionDependencyError::Other(anyhow!(
276            "get_header not supported"
277        )))
278    }
279
280    async fn get_block_extension_async(
281        &self,
282        _block_hash: &Byte32,
283    ) -> Result<Option<ckb_types::packed::Bytes>, TransactionDependencyError> {
284        Err(TransactionDependencyError::Other(anyhow!(
285            "get_block_extension not supported"
286        )))
287    }
288}