1use 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#[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#[derive(Default, Clone)]
64pub struct OffchainCellCollector {
65 pub locked_cells: HashMap<(H256, u32), u64>,
67 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#[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 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 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 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 }
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 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 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 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 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}