ckb_testtool/
context.rs

1use crate::tx_verifier::OutputsDataVerifier;
2use ckb_chain_spec::consensus::{ConsensusBuilder, TYPE_ID_CODE_HASH};
3use ckb_error::Error as CKBError;
4use ckb_mock_tx_types::{MockCellDep, MockInfo, MockInput, MockTransaction, ReprMockTransaction};
5use ckb_script::{TransactionScriptsVerifier, TxVerifyEnv};
6use ckb_traits::{CellDataProvider, ExtensionProvider, HeaderProvider};
7use ckb_types::{
8    bytes::Bytes,
9    core::{
10        Capacity, Cycle, DepType, EpochExt, HeaderBuilder, HeaderView, ScriptHashType,
11        TransactionInfo, TransactionView,
12        cell::{CellMetaBuilder, ResolvedTransaction},
13        hardfork::{CKB2021, CKB2023, HardForks},
14    },
15    packed::{Byte32, CellDep, CellDepBuilder, CellOutput, OutPoint, OutPointVec, Script},
16    prelude::*,
17};
18use rand::{Rng, SeedableRng, rngs::StdRng, thread_rng};
19use std::collections::HashMap;
20use std::env;
21use std::path::PathBuf;
22use std::sync::{Arc, Mutex};
23
24/// Return a random hash.
25pub fn random_hash() -> Byte32 {
26    let mut rng = thread_rng();
27    let mut buf = [0u8; 32];
28    rng.fill(&mut buf);
29    buf.pack()
30}
31
32/// Return a random OutPoint.
33pub fn random_out_point() -> OutPoint {
34    OutPoint::new_builder().tx_hash(random_hash()).build()
35}
36
37/// Return a random Type ID Script.
38pub fn random_type_id_script() -> Script {
39    let args = random_hash().as_bytes();
40    debug_assert_eq!(args.len(), 32);
41    Script::new_builder()
42        .code_hash(TYPE_ID_CODE_HASH.pack())
43        .hash_type(ScriptHashType::Type)
44        .args(args.pack())
45        .build()
46}
47
48/// A single debug message. By setting context.capture_debug, you can capture debug syscalls issued by your script.
49#[derive(Debug, Clone, Eq, PartialEq)]
50pub struct Message {
51    /// The current script hash.
52    pub id: Byte32,
53    /// Payload.
54    pub message: String,
55}
56
57/// Verification Context.
58#[derive(Clone)]
59pub struct Context {
60    pub cells: HashMap<OutPoint, (CellOutput, Bytes)>,
61    pub transaction_infos: HashMap<OutPoint, TransactionInfo>,
62    pub headers: HashMap<Byte32, HeaderView>,
63    pub epoches: HashMap<Byte32, EpochExt>,
64    pub block_extensions: HashMap<Byte32, Bytes>,
65    pub cells_by_data_hash: HashMap<Byte32, OutPoint>,
66    pub cells_by_type_hash: HashMap<Byte32, OutPoint>,
67    deterministic_rng: bool,
68    capture_debug: bool,
69    captured_messages: Arc<Mutex<Vec<Message>>>,
70    contracts_dirs: Vec<PathBuf>,
71    #[cfg(feature = "native-simulator")]
72    simulator_binaries: HashMap<Byte32, PathBuf>,
73    #[cfg(feature = "native-simulator")]
74    simulator_bin_name: String,
75}
76
77impl Default for Context {
78    fn default() -> Self {
79        // The search directory for scripts is $TOP/build/$MODE. You can change this path by setting the $TOP and $MODE
80        // environment variables. If you do nothing, the default path is ../build/release.
81        let mut contracts_dir = env::var("TOP").map(PathBuf::from).unwrap_or_default();
82        contracts_dir.push("build");
83        if !contracts_dir.exists() {
84            contracts_dir.pop();
85            contracts_dir.push("../build");
86        }
87        let contracts_dir = contracts_dir.join(env::var("MODE").unwrap_or("release".to_string()));
88
89        Self {
90            cells: Default::default(),
91            transaction_infos: Default::default(),
92            headers: Default::default(),
93            epoches: Default::default(),
94            block_extensions: Default::default(),
95            cells_by_data_hash: Default::default(),
96            cells_by_type_hash: Default::default(),
97            deterministic_rng: false,
98            capture_debug: Default::default(),
99            captured_messages: Default::default(),
100            contracts_dirs: vec![contracts_dir],
101            #[cfg(feature = "native-simulator")]
102            simulator_binaries: Default::default(),
103            #[cfg(feature = "native-simulator")]
104            simulator_bin_name: "lib<contract>_sim".to_string(),
105        }
106    }
107}
108
109impl Context {
110    /// Create a new context with a deterministic random number generator, which can be used to generate deterministic
111    /// out point of deployed contract.
112    pub fn new_with_deterministic_rng() -> Self {
113        Self {
114            deterministic_rng: true,
115            ..Default::default()
116        }
117    }
118
119    /// Add new script search paths. Note that this does not replace the default search paths.
120    pub fn add_contract_dir(&mut self, path: &str) {
121        self.contracts_dirs.push(path.into());
122    }
123
124    /// Deploy a cell.
125    pub fn deploy_cell(&mut self, data: Bytes) -> OutPoint {
126        let data_hash = CellOutput::calc_data_hash(&data);
127        if let Some(out_point) = self.cells_by_data_hash.get(&data_hash) {
128            // Contract has already been deployed.
129            return out_point.to_owned();
130        }
131        let (out_point, type_id_script) = if self.deterministic_rng {
132            let mut rng = StdRng::from_seed(data_hash.as_slice().try_into().unwrap());
133            let mut tx_hash = [0u8; 32];
134            rng.fill(&mut tx_hash);
135            let mut script_args = [0u8; 32];
136            rng.fill(&mut script_args);
137            (
138                OutPoint::new_builder().tx_hash(tx_hash.pack()).build(),
139                Script::new_builder()
140                    .code_hash(TYPE_ID_CODE_HASH.pack())
141                    .hash_type(ScriptHashType::Type)
142                    .args(script_args.as_slice().pack())
143                    .build(),
144            )
145        } else {
146            (random_out_point(), random_type_id_script())
147        };
148        let type_id_hash = type_id_script.calc_script_hash();
149        let cell = {
150            let cell = CellOutput::new_builder()
151                .type_(Some(type_id_script).pack())
152                .build();
153            let occupied_capacity = cell
154                .occupied_capacity(Capacity::bytes(data.len()).expect("data occupied capacity"))
155                .expect("cell capacity");
156            cell.as_builder().capacity(occupied_capacity.pack()).build()
157        };
158        self.cells.insert(out_point.clone(), (cell, data));
159        self.cells_by_data_hash.insert(data_hash, out_point.clone());
160        self.cells_by_type_hash
161            .insert(type_id_hash, out_point.clone());
162        out_point
163    }
164
165    /// Deploy a cell by filename. It provides the same functionality as the deploy_cell function, but looks for data
166    /// in the file system.
167    pub fn deploy_cell_by_name(&mut self, filename: &str) -> OutPoint {
168        let path = self.get_contract_path(filename).expect("get contract path");
169        let data = std::fs::read(&path).unwrap_or_else(|_| panic!("read local file: {:?}", path));
170
171        #[cfg(feature = "native-simulator")]
172        {
173            let native_path = self.get_native_simulator_path(filename);
174            if native_path.is_some() {
175                let code_hash = CellOutput::calc_data_hash(&data);
176                self.simulator_binaries
177                    .insert(code_hash, native_path.unwrap());
178            }
179        }
180
181        self.deploy_cell(data.into())
182    }
183
184    /// Get the full path of the specified script.
185    fn get_contract_path(&self, filename: &str) -> Option<PathBuf> {
186        for dir in &self.contracts_dirs {
187            let path = dir.join(filename);
188            if path.is_file() {
189                return Some(path);
190            }
191        }
192        None
193    }
194
195    #[cfg(feature = "native-simulator")]
196    /// Get the full path of the specified script. Only useful in simulator mode.
197    fn get_native_simulator_path(&self, filename: &str) -> Option<PathBuf> {
198        let cdylib_name = format!(
199            "{}.{}",
200            self.simulator_bin_name
201                .replace("<contract>", &filename.replace("-", "_")),
202            std::env::consts::DLL_EXTENSION
203        );
204        for dir in &self.contracts_dirs {
205            let path = dir.join(&cdylib_name);
206            if path.is_file() {
207                return Some(path);
208            }
209        }
210        None
211    }
212
213    /// Insert a block header into context. Afterwards, the header can be retrieved by its hash.
214    pub fn insert_header(&mut self, header: HeaderView) {
215        self.headers.insert(header.hash(), header);
216    }
217
218    /// Link a cell with a block to make the load_header_by_cell syscalls works.
219    pub fn link_cell_with_block(
220        &mut self,
221        out_point: OutPoint,
222        block_hash: Byte32,
223        tx_index: usize,
224    ) {
225        let header = self
226            .headers
227            .get(&block_hash)
228            .expect("can't find the header");
229        self.transaction_infos.insert(
230            out_point,
231            TransactionInfo::new(header.number(), header.epoch(), block_hash, tx_index),
232        );
233    }
234
235    /// Get the out-point of a cell by data_hash. The cell must has deployed to this context.
236    pub fn get_cell_by_data_hash(&self, data_hash: &Byte32) -> Option<OutPoint> {
237        self.cells_by_data_hash.get(data_hash).cloned()
238    }
239
240    /// Create a cell with data.
241    pub fn create_cell(&mut self, cell: CellOutput, data: Bytes) -> OutPoint {
242        let out_point = random_out_point();
243        self.create_cell_with_out_point(out_point.clone(), cell, data);
244        out_point
245    }
246
247    /// Create cell with specified out-point and cell data.
248    pub fn create_cell_with_out_point(
249        &mut self,
250        out_point: OutPoint,
251        cell: CellOutput,
252        data: Bytes,
253    ) {
254        let data_hash = CellOutput::calc_data_hash(&data);
255        self.cells_by_data_hash.insert(data_hash, out_point.clone());
256        if let Some(_type) = cell.type_().to_opt() {
257            let type_hash = _type.calc_script_hash();
258            self.cells_by_type_hash.insert(type_hash, out_point.clone());
259        }
260        self.cells.insert(out_point, (cell, data));
261    }
262
263    /// Get cell output and data by out-point.
264    pub fn get_cell(&self, out_point: &OutPoint) -> Option<(CellOutput, Bytes)> {
265        self.cells.get(out_point).cloned()
266    }
267
268    /// Build script with out_point, hash_type, args. Return none if the out-point is not exist.
269    pub fn build_script_with_hash_type(
270        &mut self,
271        out_point: &OutPoint,
272        hash_type: ScriptHashType,
273        args: Bytes,
274    ) -> Option<Script> {
275        let (cell, contract_data) = self.cells.get(out_point)?;
276        let code_hash = match hash_type {
277            ScriptHashType::Data | ScriptHashType::Data1 | ScriptHashType::Data2 => {
278                CellOutput::calc_data_hash(contract_data)
279            }
280            ScriptHashType::Type => cell
281                .type_()
282                .to_opt()
283                .expect("get cell's type hash")
284                .calc_script_hash(),
285            _ => unreachable!(),
286        };
287        Some(
288            Script::new_builder()
289                .code_hash(code_hash)
290                .hash_type(hash_type)
291                .args(args.pack())
292                .build(),
293        )
294    }
295
296    /// Build script with out_point, args and hash_type(ScriptHashType::Type). Return none if the out-point is not
297    /// exist.
298    pub fn build_script(&mut self, out_point: &OutPoint, args: Bytes) -> Option<Script> {
299        self.build_script_with_hash_type(out_point, ScriptHashType::Type, args)
300    }
301
302    /// Find cell dep for the specified script.
303    fn find_cell_dep_for_script(&self, script: &Script) -> CellDep {
304        let out_point = match ScriptHashType::try_from(u8::from(script.hash_type()))
305            .expect("invalid script hash type")
306        {
307            ScriptHashType::Data | ScriptHashType::Data1 | ScriptHashType::Data2 => self
308                .get_cell_by_data_hash(&script.code_hash())
309                .expect("find contract out point by data_hash"),
310            ScriptHashType::Type => self
311                .cells_by_type_hash
312                .get(&script.code_hash())
313                .cloned()
314                .expect("find contract out point by type_hash"),
315            _ => unreachable!(),
316        };
317
318        CellDep::new_builder()
319            .out_point(out_point)
320            .dep_type(DepType::Code)
321            .build()
322    }
323
324    /// Complete cell deps for a transaction.
325    /// This function searches context cells; generate cell dep for referenced scripts.
326    pub fn complete_tx(&mut self, tx: TransactionView) -> TransactionView {
327        let mut cell_deps: Vec<CellDep> = Vec::new();
328
329        for cell_dep in tx.cell_deps_iter() {
330            cell_deps.push(cell_dep);
331        }
332
333        for i in tx.input_pts_iter() {
334            if let Some((cell, _data)) = self.cells.get(&i) {
335                let dep = self.find_cell_dep_for_script(&cell.lock());
336                if !cell_deps.contains(&dep) {
337                    cell_deps.push(dep);
338                }
339                if let Some(script) = cell.type_().to_opt() {
340                    let dep = self.find_cell_dep_for_script(&script);
341                    if !cell_deps.contains(&dep) {
342                        cell_deps.push(dep);
343                    }
344                }
345            }
346        }
347
348        for (cell, _data) in tx.outputs_with_data_iter() {
349            if let Some(script) = cell.type_().to_opt() {
350                let dep = self.find_cell_dep_for_script(&script);
351                if !cell_deps.contains(&dep) {
352                    cell_deps.push(dep);
353                }
354            }
355        }
356
357        tx.as_advanced_builder()
358            .set_cell_deps(Vec::new())
359            .cell_deps(cell_deps.pack())
360            .build()
361    }
362
363    /// Build transaction with resolved input cells.
364    fn build_resolved_tx(&self, tx: &TransactionView) -> ResolvedTransaction {
365        let input_cells = tx
366            .inputs()
367            .into_iter()
368            .map(|input| {
369                let previous_out_point = input.previous_output();
370                let (input_output, input_data) = self.cells.get(&previous_out_point).unwrap();
371                let tx_info_opt = self.transaction_infos.get(&previous_out_point);
372                let mut b = CellMetaBuilder::from_cell_output(
373                    input_output.to_owned(),
374                    input_data.to_vec().into(),
375                )
376                .out_point(previous_out_point);
377                if let Some(tx_info) = tx_info_opt {
378                    b = b.transaction_info(tx_info.to_owned());
379                }
380                b.build()
381            })
382            .collect();
383        let mut resolved_cell_deps = vec![];
384        let mut resolved_dep_groups = vec![];
385        tx.cell_deps().into_iter().for_each(|cell_dep| {
386            let mut out_points = vec![];
387            if cell_dep.dep_type() == DepType::DepGroup.into() {
388                let (dep_group_output, dep_group_data) =
389                    self.cells.get(&cell_dep.out_point()).unwrap();
390                let dep_group_tx_info_opt = self.transaction_infos.get(&cell_dep.out_point());
391                let mut b = CellMetaBuilder::from_cell_output(
392                    dep_group_output.to_owned(),
393                    dep_group_data.to_vec().into(),
394                )
395                .out_point(cell_dep.out_point());
396                if let Some(tx_info) = dep_group_tx_info_opt {
397                    b = b.transaction_info(tx_info.to_owned());
398                }
399                resolved_dep_groups.push(b.build());
400
401                let sub_out_points =
402                    OutPointVec::from_slice(dep_group_data).expect("Parsing dep group error!");
403                out_points.extend(sub_out_points);
404            } else {
405                out_points.push(cell_dep.out_point());
406            }
407
408            for out_point in out_points {
409                let (dep_output, dep_data) = self.cells.get(&out_point).unwrap();
410                let tx_info_opt = self.transaction_infos.get(&out_point);
411                let mut b = CellMetaBuilder::from_cell_output(
412                    dep_output.to_owned(),
413                    dep_data.to_vec().into(),
414                )
415                .out_point(out_point);
416                if let Some(tx_info) = tx_info_opt {
417                    b = b.transaction_info(tx_info.to_owned());
418                }
419                resolved_cell_deps.push(b.build());
420            }
421        });
422        ResolvedTransaction {
423            transaction: tx.clone(),
424            resolved_cell_deps,
425            resolved_inputs: input_cells,
426            resolved_dep_groups,
427        }
428    }
429
430    /// Check format and consensus rules.
431    fn verify_tx_consensus(&self, tx: &TransactionView) -> Result<(), CKBError> {
432        OutputsDataVerifier::new(tx).verify()?;
433        Ok(())
434    }
435
436    /// Return capture_debug flag.
437    pub fn capture_debug(&self) -> bool {
438        self.capture_debug
439    }
440
441    /// Capture debug output, default value is false.
442    pub fn set_capture_debug(&mut self, capture_debug: bool) {
443        self.capture_debug = capture_debug;
444    }
445
446    /// Return captured messages.
447    pub fn captured_messages(&self) -> Vec<Message> {
448        self.captured_messages.lock().unwrap().clone()
449    }
450
451    /// Verify the transaction in CKB-VM.
452    pub fn verify_tx(&self, tx: &TransactionView, max_cycles: u64) -> Result<Cycle, CKBError> {
453        self.verify_tx_consensus(tx)?;
454        let resolved_tx = self.build_resolved_tx(tx);
455        let consensus = ConsensusBuilder::default()
456            .hardfork_switch(HardForks {
457                ckb2021: CKB2021::new_dev_default(),
458                ckb2023: CKB2023::new_dev_default(),
459            })
460            .build();
461        let tip = HeaderBuilder::default().number(0).build();
462        let tx_verify_env = TxVerifyEnv::new_submit(&tip);
463        let verifier = if self.capture_debug {
464            let captured_messages = self.captured_messages.clone();
465            TransactionScriptsVerifier::new_with_debug_printer(
466                Arc::new(resolved_tx),
467                self.clone(),
468                Arc::new(consensus),
469                Arc::new(tx_verify_env),
470                Arc::new(move |id, message| {
471                    //
472                    let msg = Message {
473                        id: id.clone(),
474                        message: message.to_string(),
475                    };
476                    captured_messages.lock().unwrap().push(msg);
477                }),
478            )
479        } else {
480            TransactionScriptsVerifier::new_with_debug_printer(
481                Arc::new(resolved_tx),
482                self.clone(),
483                Arc::new(consensus),
484                Arc::new(tx_verify_env),
485                Arc::new(|_id, msg| {
486                    println!("[contract debug] {}", msg);
487                }),
488            )
489        };
490
491        #[cfg(feature = "native-simulator")]
492        {
493            self.native_simulator_verify(tx, verifier, max_cycles)
494        }
495        #[cfg(not(feature = "native-simulator"))]
496        verifier.verify(max_cycles)
497    }
498
499    #[cfg(feature = "native-simulator")]
500    /// Verify the transaction in simulator mode.
501    fn native_simulator_verify<DL>(
502        &self,
503        tx: &TransactionView,
504        verifier: TransactionScriptsVerifier<DL>,
505        max_cycles: u64,
506    ) -> Result<Cycle, CKBError>
507    where
508        DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static,
509    {
510        let mut cycles: Cycle = 0;
511
512        for (hash, group) in verifier.groups() {
513            let code_hash = if group.script.hash_type() == ScriptHashType::Type.into() {
514                let code_hash = group.script.code_hash();
515                let out_point = match self.cells_by_type_hash.get(&code_hash) {
516                    Some(out_point) => out_point,
517                    None => panic!("unknow code hash(ScriptHashType::Type)"),
518                };
519
520                match self.cells.get(out_point) {
521                    Some((_cell, bin)) => CellOutput::calc_data_hash(bin),
522                    None => panic!("unknow code hash(ScriptHashType::Type) in deps"),
523                }
524            } else {
525                group.script.code_hash()
526            };
527
528            let use_cycles = match self.simulator_binaries.get(&code_hash) {
529                Some(sim_path) => self.run_simulator(sim_path, tx, group),
530                None => {
531                    group.script.code_hash();
532                    verifier
533                        .verify_single(group.group_type, hash, max_cycles)
534                        .map_err(|e| e.source(group))?
535                }
536            };
537            let r = cycles.overflowing_add(use_cycles);
538            assert!(!r.1, "cycles overflow");
539            cycles = r.0;
540        }
541        Ok(cycles)
542    }
543
544    #[cfg(feature = "native-simulator")]
545    /// Run simulator.
546    fn run_simulator(
547        &self,
548        sim_path: &PathBuf,
549        tx: &TransactionView,
550        group: &ckb_script::ScriptGroup,
551    ) -> u64 {
552        println!(
553            "run native-simulator: {}",
554            sim_path.file_name().unwrap().to_str().unwrap()
555        );
556        let tmp_dir = if !self.simulator_binaries.is_empty() {
557            let tmp_dir = std::env::temp_dir().join("ckb-simulator-debugger");
558            if !tmp_dir.exists() {
559                std::fs::create_dir(tmp_dir.clone())
560                    .expect("create tmp dir: ckb-simulator-debugger");
561            }
562            let tx_file: PathBuf = tmp_dir.join("ckb_running_tx.json");
563            let dump_tx = self.dump_tx(&tx).unwrap();
564
565            let tx_json = serde_json::to_string(&dump_tx).expect("dump tx to string");
566            std::fs::write(&tx_file, tx_json).expect("write setup");
567
568            unsafe {
569                std::env::set_var("CKB_TX_FILE", tx_file.to_str().unwrap());
570            }
571            Some(tmp_dir)
572        } else {
573            None
574        };
575        let running_setup = tmp_dir.as_ref().unwrap().join("ckb_running_setup.json");
576
577        let mut native_binaries = self
578            .simulator_binaries
579            .iter()
580            .map(|(code_hash, path)| {
581                let buf = vec![
582                    code_hash.as_bytes().to_vec(),
583                    vec![0xff],
584                    0u32.to_le_bytes().to_vec(),
585                    0u32.to_le_bytes().to_vec(),
586                ]
587                .concat();
588
589                format!(
590                    "\"0x{}\" : \"{}\",",
591                    faster_hex::hex_string(&buf),
592                    path.to_str().unwrap()
593                )
594            })
595            .collect::<Vec<String>>()
596            .concat();
597        if !native_binaries.is_empty() {
598            native_binaries.pop();
599        }
600
601        let native_binaries = format!("{{ {} }}", native_binaries);
602
603        let setup = format!(
604            "{{\"is_lock_script\": {}, \"is_output\": false, \"script_index\": {}, \"vm_version\": {}, \"native_binaries\": {}, \"run_type\": \"DynamicLib\" }}",
605            group.group_type == ckb_script::ScriptGroupType::Lock,
606            group.input_indices[0],
607            2,
608            native_binaries
609        );
610        std::fs::write(&running_setup, setup).expect("write setup");
611        unsafe {
612            std::env::set_var("CKB_RUNNING_SETUP", running_setup.to_str().unwrap());
613        }
614
615        type CkbMainFunc<'a> =
616            libloading::Symbol<'a, unsafe extern "C" fn(argc: i32, argv: *const *const i8) -> i8>;
617        type SetScriptInfo<'a> = libloading::Symbol<
618            'a,
619            unsafe extern "C" fn(ptr: *const std::ffi::c_void, tx_ctx_id: u64, vm_ctx_id: u64),
620        >;
621
622        // ckb_x64_simulator::run_native_simulator(sim_path);
623        unsafe {
624            let lib = libloading::Library::new(sim_path).expect("Load library");
625
626            let func: SetScriptInfo = lib
627                .get(b"__set_script_info")
628                .expect("load function : __update_spawn_info");
629            func(std::ptr::null(), 0, 0);
630
631            let func: CkbMainFunc = lib
632                .get(b"__ckb_std_main")
633                .expect("load function : __ckb_std_main");
634            let argv = vec![];
635            func(0, argv.as_ptr());
636        }
637        0
638    }
639
640    #[cfg(feature = "native-simulator")]
641    /// Add code_hash and script binary to simulator.
642    pub fn set_simulator(&mut self, code_hash: Byte32, path: &str) {
643        let path = PathBuf::from(path);
644        assert!(path.is_file());
645        self.simulator_binaries.insert(code_hash, path);
646    }
647
648    /// Dump the transaction in mock transaction format, so we can offload it to ckb debugger.
649    pub fn dump_tx(&self, tx: &TransactionView) -> Result<ReprMockTransaction, CKBError> {
650        let rtx = self.build_resolved_tx(tx);
651        let mut inputs = Vec::with_capacity(rtx.resolved_inputs.len());
652        // We are doing it this way so we can keep original since value is available
653        for (i, input) in rtx.resolved_inputs.iter().enumerate() {
654            inputs.push(MockInput {
655                input: rtx.transaction.inputs().get(i).unwrap(),
656                output: input.cell_output.clone(),
657                data: input.mem_cell_data.clone().unwrap(),
658                header: input.transaction_info.clone().map(|info| info.block_hash),
659            });
660        }
661        // MockTransaction keeps both types of cell deps in a single array, the order does
662        // not really matter for now
663        let mut cell_deps =
664            Vec::with_capacity(rtx.resolved_cell_deps.len() + rtx.resolved_dep_groups.len());
665        for dep in rtx.resolved_cell_deps.iter() {
666            cell_deps.push(MockCellDep {
667                cell_dep: CellDepBuilder::default()
668                    .out_point(dep.out_point.clone())
669                    .dep_type(DepType::Code)
670                    .build(),
671                output: dep.cell_output.clone(),
672                data: dep.mem_cell_data.clone().unwrap(),
673                header: dep.transaction_info.clone().map(|info| info.block_hash),
674            });
675        }
676        for dep in rtx.resolved_dep_groups.iter() {
677            cell_deps.push(MockCellDep {
678                cell_dep: CellDepBuilder::default()
679                    .out_point(dep.out_point.clone())
680                    .dep_type(DepType::DepGroup)
681                    .build(),
682                output: dep.cell_output.clone(),
683                data: dep.mem_cell_data.clone().unwrap(),
684                header: dep.transaction_info.clone().map(|info| info.block_hash),
685            });
686        }
687        let mut header_deps = Vec::with_capacity(rtx.transaction.header_deps().len());
688        let mut extensions = Vec::new();
689        for header_hash in rtx.transaction.header_deps_iter() {
690            header_deps.push(self.get_header(&header_hash).unwrap());
691            if let Some(extension) = self.get_block_extension(&header_hash) {
692                extensions.push((header_hash, extension.unpack()));
693            }
694        }
695        Ok(MockTransaction {
696            mock_info: MockInfo {
697                inputs,
698                cell_deps,
699                header_deps,
700                extensions,
701            },
702            tx: rtx.transaction.data(),
703        }
704        .into())
705    }
706}
707
708impl CellDataProvider for Context {
709    fn get_cell_data(&self, out_point: &OutPoint) -> Option<Bytes> {
710        self.cells
711            .get(out_point)
712            .map(|(_, data)| Bytes::from(data.to_vec()))
713    }
714
715    fn get_cell_data_hash(&self, out_point: &OutPoint) -> Option<Byte32> {
716        self.cells
717            .get(out_point)
718            .map(|(_, data)| CellOutput::calc_data_hash(data))
719    }
720}
721
722impl HeaderProvider for Context {
723    fn get_header(&self, block_hash: &Byte32) -> Option<HeaderView> {
724        self.headers.get(block_hash).cloned()
725    }
726}
727
728impl ExtensionProvider for Context {
729    fn get_block_extension(
730        &self,
731        hash: &ckb_types::packed::Byte32,
732    ) -> Option<ckb_types::packed::Bytes> {
733        self.block_extensions.get(hash).map(|b| b.pack())
734    }
735}