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