Skip to content

Commit

Permalink
Merge pull request #3 from joii2020/main
Browse files Browse the repository at this point in the history
Support native simulator debug
  • Loading branch information
quake authored Jul 23, 2024
2 parents 226221b + 9451133 commit 9a3fd89
Show file tree
Hide file tree
Showing 3 changed files with 179 additions and 3 deletions.
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ repository = "https://github.com/nervosnetwork/capsule"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[features]
simulator = [ "libloading", "serde_json" ]

[dependencies]
ckb-always-success-script = "0.0.1"
rand = "0.8"
Expand All @@ -24,6 +27,8 @@ ckb-resource = "0.116.1"
ckb-verification = "0.116.1"
ckb-traits = "0.116.1"
ckb-mock-tx-types = "0.116.1"
libloading = { version = "0.8.4", optional = true }
serde_json = { version = "1.0", optional = true }

[dev-dependencies]
anyhow = "1.0"
Expand Down
165 changes: 162 additions & 3 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ pub struct Message {
}

/// Verification Context
#[derive(Clone, Default)]
#[derive(Clone)]
pub struct Context {
pub cells: HashMap<OutPoint, (CellOutput, Bytes)>,
pub transaction_infos: HashMap<OutPoint, TransactionInfo>,
Expand All @@ -61,6 +61,45 @@ pub struct Context {
pub cells_by_type_hash: HashMap<Byte32, OutPoint>,
capture_debug: bool,
captured_messages: Arc<Mutex<Vec<Message>>>,
#[cfg(feature = "simulator")]
simulator_hash_map: HashMap<Byte32, std::path::PathBuf>,
contracts_dir: std::path::PathBuf,
}

impl Default for Context {
fn default() -> Self {
use std::env;
use std::path::PathBuf;
// Get from $TOP/$MODE
let contracts_dir = env::var("TOP")
.map(|f| PathBuf::from(f))
.unwrap_or({
let mut base_path = PathBuf::new();
base_path.push("build");
if !base_path.exists() {
base_path.pop();
base_path.push("..");
base_path.push("build");
}
base_path
})
.join(env::var("MODE").unwrap_or("release".to_string()));

Self {
cells: Default::default(),
transaction_infos: Default::default(),
headers: Default::default(),
epoches: Default::default(),
block_extensions: Default::default(),
cells_by_data_hash: Default::default(),
cells_by_type_hash: Default::default(),
capture_debug: Default::default(),
captured_messages: Default::default(),
#[cfg(feature = "simulator")]
simulator_hash_map: Default::default(),
contracts_dir,
}
}
}

impl Context {
Expand Down Expand Up @@ -102,6 +141,26 @@ impl Context {
out_point
}

pub fn deploy_cell_by_name(&mut self, filename: &str) -> OutPoint {
let path = self.contracts_dir.join(filename);
let data = std::fs::read(path).expect("read local file");

#[cfg(feature = "simulator")]
{
let native_path = self.contracts_dir.join(format!(
"lib{}_dbg.{}",
filename.replace("-", "_"),
std::env::consts::DLL_EXTENSION
));
if native_path.is_file() {
let code_hash = CellOutput::calc_data_hash(&data);
self.simulator_hash_map.insert(code_hash, native_path);
}
}

self.deploy_cell(data.into())
}

/// Insert a block header into context
pub fn insert_header(&mut self, header: HeaderView) {
self.headers.insert(header.hash(), header);
Expand Down Expand Up @@ -386,9 +445,109 @@ impl Context {
println!("[contract debug] {}", msg);
});
}

#[cfg(feature = "simulator")]
{
use core::ffi::c_int;
pub struct Arg(());
type CkbMainFunc<'a> =
libloading::Symbol<'a, unsafe extern "C" fn(argc: c_int, argv: *const Arg) -> i8>;

let mut cycles: Cycle = 0;
let mut index = 0;
let tmp_dir = if !self.simulator_hash_map.is_empty() {
let tmp_dir = std::env::temp_dir().join("ckb-simulator-debugger");
if !tmp_dir.exists() {
std::fs::create_dir(tmp_dir.clone())
.expect("create tmp dir: ckb-simulator-debugger");
}
let tx_file: std::path::PathBuf = tmp_dir.join("ckb_running_tx.json");
let dump_tx = self.dump_tx(&tx)?;

let tx_json = serde_json::to_string(&dump_tx).expect("dump tx to string");
std::fs::write(&tx_file, tx_json).expect("write setup");

std::env::set_var("CKB_TX_FILE", tx_file.to_str().unwrap());
Some(tmp_dir)
} else {
None
};

for (hash, group) in verifier.groups() {
let code_hash = if group.script.hash_type() == ScriptHashType::Type.into() {
let code_hash = group.script.code_hash();
let out_point = match self.cells_by_type_hash.get(&code_hash) {
Some(out_point) => out_point,
None => panic!("unknow code hash(ScriptHashType::Type)"),
};

match self.cells.get(out_point) {
Some((_cell, bin)) => CellOutput::calc_data_hash(bin),
None => panic!("unknow code hash(ScriptHashType::Type) in deps"),
}
} else {
group.script.code_hash()
};

let use_cycles = match self.simulator_hash_map.get(&code_hash) {
Some(sim_path) => {
println!(
"run simulator: {}",
sim_path.file_name().unwrap().to_str().unwrap()
);
let running_setup =
tmp_dir.as_ref().unwrap().join("ckb_running_setup.json");

let setup = format!(
"{{\"is_lock_script\": {}, \"is_output\": false, \"script_index\": {}, \"vm_version\": 1, \"native_binaries\": {{}} }}",
group.group_type == ckb_script::ScriptGroupType::Lock,
index,
);
std::fs::write(&running_setup, setup).expect("write setup");
std::env::set_var("CKB_RUNNING_SETUP", running_setup.to_str().unwrap());

unsafe {
if let Ok(lib) = libloading::Library::new(sim_path) {
if let Ok(func) = lib.get(b"__ckb_std_main") {
let func: CkbMainFunc = func;
let rc = { func(0, [].as_ptr()) };
assert!(rc == 0, "run simulator failed");
}
}
}
index += 1;
0
}
None => {
index += 1;
group.script.code_hash();
verifier
.verify_single(group.group_type, hash, max_cycles)
.map_err(|e| {
#[cfg(feature = "logging")]
logging::on_script_error(_hash, &self.hash(), &e);
e.source(group)
})?
}
};
let r = cycles.overflowing_add(use_cycles);
assert!(!r.1, "cycles overflow");
cycles = r.0;
}
Ok(cycles)
}

#[cfg(not(feature = "simulator"))]
verifier.verify(max_cycles)
}

#[cfg(feature = "simulator")]
pub fn set_simulator(&mut self, code_hash: Byte32, path: &str) {
let path = std::path::PathBuf::from(path);
assert!(path.is_file());
self.simulator_hash_map.insert(code_hash, path);
}

/// Dump the transaction in mock transaction format, so we can offload it to ckb debugger
pub fn dump_tx(&self, tx: &TransactionView) -> Result<ReprMockTransaction, CKBError> {
let rtx = self.build_resolved_tx(tx);
Expand All @@ -414,7 +573,7 @@ impl Context {
.build(),
output: dep.cell_output.clone(),
data: dep.mem_cell_data.clone().unwrap(),
header: None,
header: dep.transaction_info.clone().map(|info| info.block_hash),
});
}
for dep in rtx.resolved_dep_groups.iter() {
Expand All @@ -425,7 +584,7 @@ impl Context {
.build(),
output: dep.cell_output.clone(),
data: dep.mem_cell_data.clone().unwrap(),
header: None,
header: dep.transaction_info.clone().map(|info| info.block_hash),
});
}
let mut header_deps = Vec::with_capacity(rtx.transaction.header_deps().len());
Expand Down

0 comments on commit 9a3fd89

Please sign in to comment.