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
24pub 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
32pub fn random_out_point() -> OutPoint {
34 OutPoint::new_builder().tx_hash(random_hash()).build()
35}
36
37pub 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#[derive(Debug, Clone, Eq, PartialEq)]
50pub struct Message {
51 pub id: Byte32,
53 pub message: String,
55}
56
57#[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 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 pub fn new_with_deterministic_rng() -> Self {
113 Self {
114 deterministic_rng: true,
115 ..Default::default()
116 }
117 }
118
119 pub fn add_contract_dir(&mut self, path: &str) {
121 self.contracts_dirs.push(path.into());
122 }
123
124 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 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 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 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 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 pub fn insert_header(&mut self, header: HeaderView) {
215 self.headers.insert(header.hash(), header);
216 }
217
218 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 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 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 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 pub fn get_cell(&self, out_point: &OutPoint) -> Option<(CellOutput, Bytes)> {
265 self.cells.get(out_point).cloned()
266 }
267
268 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 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 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 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 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 fn verify_tx_consensus(&self, tx: &TransactionView) -> Result<(), CKBError> {
432 OutputsDataVerifier::new(tx).verify()?;
433 Ok(())
434 }
435
436 pub fn capture_debug(&self) -> bool {
438 self.capture_debug
439 }
440
441 pub fn set_capture_debug(&mut self, capture_debug: bool) {
443 self.capture_debug = capture_debug;
444 }
445
446 pub fn captured_messages(&self) -> Vec<Message> {
448 self.captured_messages.lock().unwrap().clone()
449 }
450
451 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 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 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 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 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 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 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 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 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}