ckb_sdk_types/
transaction.rs

1use ckb_jsonrpc_types as json_types;
2use ckb_traits::{CellDataProvider, HeaderProvider};
3use ckb_types::{
4    bytes::Bytes,
5    core::{
6        cell::{CellMeta, CellMetaBuilder, CellProvider, CellStatus, HeaderChecker},
7        error::OutPointError,
8        DepType, EpochNumberWithFraction, HeaderView, TransactionInfo, TransactionView,
9    },
10    packed::{Byte32, CellDep, CellInput, CellOutput, OutPoint, OutPointVec, Transaction},
11    prelude::*,
12    H256,
13};
14use serde_derive::{Deserialize, Serialize};
15use std::collections::HashMap;
16
17#[derive(Clone, Default)]
18pub struct MockCellDep {
19    pub cell_dep: CellDep,
20    pub output: CellOutput,
21    pub data: Bytes,
22    // Where this cell_dep belong to (for load_header by cell_dep)
23    pub block_hash: H256,
24}
25
26#[derive(Clone, Default)]
27pub struct MockInput {
28    pub input: CellInput,
29    pub output: CellOutput,
30    pub data: Bytes,
31    // Where this input belong to (for load_header by input)
32    pub block_hash: H256,
33}
34
35#[derive(Clone, Default)]
36pub struct MockInfo {
37    pub inputs: Vec<MockInput>,
38    pub cell_deps: Vec<MockCellDep>,
39    pub header_deps: Vec<HeaderView>,
40}
41
42/// A wrapper transaction with mock inputs and deps
43#[derive(Clone, Default)]
44pub struct MockTransaction {
45    pub mock_info: MockInfo,
46    pub tx: Transaction,
47}
48
49impl MockTransaction {
50    pub fn get_input_cell<
51        F: FnMut(OutPoint) -> Result<Option<(CellOutput, Bytes, H256)>, String>,
52    >(
53        &self,
54        input: &CellInput,
55        mut live_cell_getter: F,
56    ) -> Result<Option<(CellOutput, Bytes, H256)>, String> {
57        for mock_input in &self.mock_info.inputs {
58            if input == &mock_input.input {
59                return Ok(Some((
60                    mock_input.output.clone(),
61                    mock_input.data.clone(),
62                    mock_input.block_hash.clone(),
63                )));
64            }
65        }
66        live_cell_getter(input.previous_output())
67    }
68
69    pub fn get_dep_cell<F: FnMut(OutPoint) -> Result<Option<(CellOutput, Bytes, H256)>, String>>(
70        &self,
71        out_point: &OutPoint,
72        mut live_cell_getter: F,
73    ) -> Result<Option<(CellOutput, Bytes, H256)>, String> {
74        for mock_cell in &self.mock_info.cell_deps {
75            if out_point == &mock_cell.cell_dep.out_point() {
76                return Ok(Some((
77                    mock_cell.output.clone(),
78                    mock_cell.data.clone(),
79                    mock_cell.block_hash.clone(),
80                )));
81            }
82        }
83        live_cell_getter(out_point.clone())
84    }
85
86    pub fn get_header<F: FnMut(H256) -> Result<Option<HeaderView>, String>>(
87        &self,
88        block_hash: &H256,
89        mut header_getter: F,
90    ) -> Result<Option<HeaderView>, String> {
91        for mock_header in &self.mock_info.header_deps {
92            if block_hash == &mock_header.hash().unpack() {
93                return Ok(Some(mock_header.clone()));
94            }
95        }
96        header_getter(block_hash.clone())
97    }
98
99    /// Generate the core transaction
100    pub fn core_transaction(&self) -> TransactionView {
101        self.tx.clone().into_view()
102    }
103}
104
105pub trait MockResourceLoader {
106    fn get_header(&mut self, hash: H256) -> Result<Option<HeaderView>, String>;
107    fn get_live_cell(
108        &mut self,
109        out_point: OutPoint,
110    ) -> Result<Option<(CellOutput, Bytes, H256)>, String>;
111}
112
113pub struct Resource {
114    required_cells: HashMap<OutPoint, CellMeta>,
115    required_headers: HashMap<Byte32, HeaderView>,
116}
117
118impl Resource {
119    pub fn from_both<L: MockResourceLoader>(
120        mock_tx: &MockTransaction,
121        mut loader: L,
122    ) -> Result<Resource, String> {
123        let tx = mock_tx.core_transaction();
124        let mut required_cells = HashMap::default();
125        let mut required_headers = HashMap::default();
126
127        for input in tx.inputs().into_iter() {
128            let (output, data, block_hash) = mock_tx
129                .get_input_cell(&input, |out_point| loader.get_live_cell(out_point))?
130                .ok_or_else(|| format!("Can not get CellOutput by input={}", input))?;
131            let cell_meta = CellMetaBuilder::from_cell_output(output, data)
132                .out_point(input.previous_output())
133                .transaction_info(TransactionInfo::new(
134                    1,
135                    EpochNumberWithFraction::new(1, 1, 1),
136                    block_hash.pack(),
137                    1,
138                ))
139                .build();
140            required_cells.insert(input.previous_output(), cell_meta);
141        }
142
143        for cell_dep in tx.cell_deps().into_iter() {
144            let (output, data, block_hash) = mock_tx
145                .get_dep_cell(&cell_dep.out_point(), |out_point| {
146                    loader.get_live_cell(out_point)
147                })?
148                .ok_or_else(|| format!("Can not get CellOutput by dep={}", cell_dep))?;
149            // Handle dep group
150            if cell_dep.dep_type() == DepType::DepGroup.into() {
151                for sub_out_point in OutPointVec::from_slice(&data)
152                    .map_err(|err| format!("Parse dep group data error: {}", err))?
153                    .into_iter()
154                {
155                    let (sub_output, sub_data, block_hash) = mock_tx
156                        .get_dep_cell(&sub_out_point, |out_point| loader.get_live_cell(out_point))?
157                        .ok_or_else(|| {
158                            format!(
159                                "(dep group) Can not get CellOutput by out_point={}",
160                                sub_out_point
161                            )
162                        })?;
163
164                    let sub_cell_meta = CellMetaBuilder::from_cell_output(sub_output, sub_data)
165                        .out_point(sub_out_point.clone())
166                        .transaction_info(TransactionInfo::new(
167                            1,
168                            EpochNumberWithFraction::new(1, 1, 1),
169                            block_hash.pack(),
170                            1,
171                        ))
172                        .build();
173                    required_cells.insert(sub_out_point, sub_cell_meta);
174                }
175            }
176            let cell_meta = CellMetaBuilder::from_cell_output(output, data)
177                .out_point(cell_dep.out_point())
178                .transaction_info(TransactionInfo::new(
179                    1,
180                    EpochNumberWithFraction::new(1, 1, 1),
181                    block_hash.pack(),
182                    1,
183                ))
184                .build();
185            required_cells.insert(cell_dep.out_point(), cell_meta);
186        }
187
188        for block_hash in tx.header_deps().into_iter() {
189            let header = mock_tx
190                .get_header(&block_hash.unpack(), |block_hash| {
191                    loader.get_header(block_hash)
192                })?
193                .ok_or_else(|| format!("Can not get header: {:x}", block_hash))?;
194            required_headers.insert(block_hash, header);
195        }
196
197        Ok(Resource {
198            required_cells,
199            required_headers,
200        })
201    }
202}
203
204impl<'a> HeaderChecker for Resource {
205    fn check_valid(
206        &self,
207        block_hash: &Byte32,
208    ) -> Result<(), ckb_types::core::error::OutPointError> {
209        if !self.required_headers.contains_key(block_hash) {
210            return Err(OutPointError::InvalidHeader(block_hash.clone()));
211        }
212        Ok(())
213    }
214}
215
216impl CellProvider for Resource {
217    fn cell(&self, out_point: &OutPoint, _with_data: bool) -> CellStatus {
218        self.required_cells
219            .get(out_point)
220            .cloned()
221            .map(CellStatus::live_cell)
222            .unwrap_or(CellStatus::Unknown)
223    }
224}
225
226impl CellDataProvider for Resource {
227    fn load_cell_data(&self, cell: &CellMeta) -> Option<Bytes> {
228        cell.mem_cell_data
229            .as_ref()
230            .map(ToOwned::to_owned)
231            .or_else(|| self.get_cell_data(&cell.out_point))
232    }
233
234    fn get_cell_data(&self, out_point: &OutPoint) -> Option<Bytes> {
235        self.required_cells
236            .get(out_point)
237            .and_then(|cell_meta| cell_meta.mem_cell_data.clone())
238    }
239
240    fn get_cell_data_hash(&self, out_point: &OutPoint) -> Option<Byte32> {
241        self.required_cells
242            .get(out_point)
243            .and_then(|cell_meta| cell_meta.mem_cell_data_hash.clone())
244    }
245}
246
247impl HeaderProvider for Resource {
248    fn get_header(&self, hash: &Byte32) -> Option<HeaderView> {
249        self.required_headers.get(hash).cloned()
250    }
251}
252
253#[derive(Clone, Serialize, Deserialize)]
254pub struct ReprMockCellDep {
255    pub cell_dep: json_types::CellDep,
256    pub output: json_types::CellOutput,
257    pub data: json_types::JsonBytes,
258    pub block_hash: Option<H256>,
259}
260#[derive(Clone, Serialize, Deserialize)]
261pub struct ReprMockInput {
262    pub input: json_types::CellInput,
263    pub output: json_types::CellOutput,
264    pub data: json_types::JsonBytes,
265    pub block_hash: Option<H256>,
266}
267#[derive(Clone, Serialize, Deserialize)]
268pub struct ReprMockInfo {
269    pub inputs: Vec<ReprMockInput>,
270    pub cell_deps: Vec<ReprMockCellDep>,
271    pub header_deps: Vec<json_types::HeaderView>,
272}
273#[derive(Clone, Serialize, Deserialize)]
274pub struct ReprMockTransaction {
275    pub mock_info: ReprMockInfo,
276    pub tx: json_types::Transaction,
277}
278
279impl From<MockCellDep> for ReprMockCellDep {
280    fn from(dep: MockCellDep) -> ReprMockCellDep {
281        ReprMockCellDep {
282            cell_dep: dep.cell_dep.into(),
283            output: dep.output.into(),
284            data: json_types::JsonBytes::from_bytes(dep.data),
285            block_hash: Some(dep.block_hash),
286        }
287    }
288}
289impl From<ReprMockCellDep> for MockCellDep {
290    fn from(dep: ReprMockCellDep) -> MockCellDep {
291        MockCellDep {
292            cell_dep: dep.cell_dep.into(),
293            output: dep.output.into(),
294            data: dep.data.into_bytes(),
295            block_hash: dep.block_hash.unwrap_or_default(),
296        }
297    }
298}
299
300impl From<MockInput> for ReprMockInput {
301    fn from(input: MockInput) -> ReprMockInput {
302        ReprMockInput {
303            input: input.input.into(),
304            output: input.output.into(),
305            data: json_types::JsonBytes::from_bytes(input.data),
306            block_hash: Some(input.block_hash),
307        }
308    }
309}
310impl From<ReprMockInput> for MockInput {
311    fn from(input: ReprMockInput) -> MockInput {
312        MockInput {
313            input: input.input.into(),
314            output: input.output.into(),
315            data: input.data.into_bytes(),
316            block_hash: input.block_hash.unwrap_or_default(),
317        }
318    }
319}
320
321impl From<MockInfo> for ReprMockInfo {
322    fn from(info: MockInfo) -> ReprMockInfo {
323        ReprMockInfo {
324            inputs: info.inputs.into_iter().map(Into::into).collect(),
325            cell_deps: info.cell_deps.into_iter().map(Into::into).collect(),
326            header_deps: info
327                .header_deps
328                .into_iter()
329                .map(|header| {
330                    // Keep the user given hash
331                    let hash = header.hash().unpack();
332                    let mut json_header: json_types::HeaderView = header.into();
333                    json_header.hash = hash;
334                    json_header
335                })
336                .collect(),
337        }
338    }
339}
340
341impl From<ReprMockInfo> for MockInfo {
342    fn from(info: ReprMockInfo) -> MockInfo {
343        MockInfo {
344            inputs: info.inputs.into_iter().map(Into::into).collect(),
345            cell_deps: info.cell_deps.into_iter().map(Into::into).collect(),
346            header_deps: info
347                .header_deps
348                .into_iter()
349                .map(|json_header| {
350                    // Keep the user given hash
351                    let hash = json_header.hash.pack();
352                    HeaderView::from(json_header).fake_hash(hash)
353                })
354                .collect(),
355        }
356    }
357}
358
359impl From<MockTransaction> for ReprMockTransaction {
360    fn from(tx: MockTransaction) -> ReprMockTransaction {
361        ReprMockTransaction {
362            mock_info: tx.mock_info.into(),
363            tx: tx.tx.into(),
364        }
365    }
366}
367impl From<ReprMockTransaction> for MockTransaction {
368    fn from(tx: ReprMockTransaction) -> MockTransaction {
369        MockTransaction {
370            mock_info: tx.mock_info.into(),
371            tx: tx.tx.into(),
372        }
373    }
374}