Skip to main content

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