ckb-debugger 0.200.1

Standalone debugger for Nervos CKB
Documentation
use std::fmt::Debug;

use ckb_types::prelude::Entity;

pub fn analyze(data: &str) -> Result<(), CheckError> {
    prelude(data)?;
    let mock: ckb_mock_tx_types::ReprMockTransaction = serde_json::from_str(&data).unwrap();
    analyze_cell_dep(&mock)?;
    analyze_header_dep(&mock)?;
    analyze_input(&mock)?;
    analyze_output(&mock)?;
    Ok(())
}

pub fn analyze_cell_dep(data: &ckb_mock_tx_types::ReprMockTransaction) -> Result<(), CheckError> {
    let cset: Vec<ckb_jsonrpc_types::CellDep> = data.mock_info.cell_deps.iter().map(|e| e.cell_dep.clone()).collect();
    for (i, e) in data.tx.cell_deps.iter().enumerate() {
        if !cset.contains(&e) {
            let path = vec![Key::Table(String::from("tx")), Key::Table(String::from("cell_deps")), Key::Index(i)];
            return Err(CheckError(format!("Check Fail: {} used unprovided cell dep", keyfmt(&path))));
        }
    }
    let mut ccnt = vec![0u8; cset.len()];
    for (_, e) in data.tx.cell_deps.iter().enumerate() {
        let i = cset.iter().position(|r| r == e).unwrap();
        ccnt[i] += 1;
        if data.mock_info.cell_deps[i].cell_dep.dep_type == ckb_jsonrpc_types::DepType::Code {
            continue;
        }
        let outpoints =
            ckb_types::packed::OutPointVec::from_slice(data.mock_info.cell_deps[i].data.as_bytes()).unwrap();
        let outpoints: Vec<ckb_types::packed::OutPoint> = outpoints.into_iter().collect();
        for (j, f) in outpoints.iter().enumerate() {
            let cdep =
                ckb_jsonrpc_types::CellDep { out_point: f.clone().into(), dep_type: ckb_jsonrpc_types::DepType::Code };
            if !cset.contains(&cdep) {
                let path = vec![
                    Key::Table(String::from("mock_info")),
                    Key::Table(String::from("cell_deps")),
                    Key::Index(i),
                    Key::Table(String::from("data")),
                    Key::Index(j),
                ];
                return Err(CheckError(format!("Check Fail: {} used unprovided cell dep", keyfmt(&path))));
            }
            let k = cset.iter().position(|r| r == &cdep).unwrap();
            ccnt[k] += 1;
        }
    }
    for (i, e) in ccnt.iter().enumerate() {
        if *e != 0 {
            continue;
        }
        let path = vec![Key::Table(String::from("mock_info")), Key::Table(String::from("cell_deps")), Key::Index(i)];
        return Err(CheckError(format!("Check Fail: {} unused", keyfmt(&path))));
    }
    Ok(())
}

pub fn analyze_header_dep(data: &ckb_mock_tx_types::ReprMockTransaction) -> Result<(), CheckError> {
    let hset: Vec<ckb_types::H256> = data.mock_info.header_deps.iter().map(|e| e.hash.clone()).collect();
    for (i, e) in data.tx.header_deps.iter().enumerate() {
        if !hset.contains(&e) {
            let path = vec![Key::Table(String::from("tx")), Key::Table(String::from("header_deps")), Key::Index(i)];
            return Err(CheckError(format!("Check Fail: {} used unprovided header dep", keyfmt(&path))));
        }
    }
    for (i, e) in hset.iter().enumerate() {
        if !data.tx.header_deps.contains(&e) {
            let path =
                vec![Key::Table(String::from("mock_info")), Key::Table(String::from("header_deps")), Key::Index(i)];
            return Err(CheckError(format!("Check Fail: {} unused", keyfmt(&path))));
        }
    }
    Ok(())
}

pub fn analyze_input(data: &ckb_mock_tx_types::ReprMockTransaction) -> Result<(), CheckError> {
    let iset: Vec<ckb_jsonrpc_types::CellInput> = data.mock_info.inputs.iter().map(|e| e.input.clone()).collect();
    for (i, e) in data.tx.inputs.iter().enumerate() {
        if !iset.contains(&e) {
            let path = vec![Key::Table(String::from("tx")), Key::Table(String::from("inputs")), Key::Index(i)];
            return Err(CheckError(format!("Check Fail: {} used unprovided input", keyfmt(&path))));
        }
    }
    for (i, e) in iset.iter().enumerate() {
        if !data.tx.inputs.contains(&e) {
            let path = vec![Key::Table(String::from("mock_info")), Key::Table(String::from("inputs")), Key::Index(i)];
            return Err(CheckError(format!("Check Fail: {} unused", keyfmt(&path))));
        }
    }
    Ok(())
}

pub fn analyze_output(data: &ckb_mock_tx_types::ReprMockTransaction) -> Result<(), CheckError> {
    if data.tx.outputs.len() != data.tx.outputs_data.len() {
        let path = vec![Key::Table(String::from("tx")), Key::Table(String::from("outputs"))];
        return Err(CheckError(format!(
            "Check Fail: {} outputs and outputs_data are not one-to-one correspondence",
            keyfmt(&path)
        )));
    }
    Ok(())
}

pub fn prelude(data: &str) -> Result<(), CheckError> {
    let j: serde_json::Value = serde_json::from_str(data).map_err(|e| CheckError(e.to_string()))?;
    prelude_contains_key(vec![], &j, "mock_info")?;
    prelude_contains_key(vec![], &j, "tx")?;
    prelude_mock_info(keyadd_table(vec![], "mock_info"), j.as_object().unwrap().get("mock_info").unwrap())?;
    prelude_tx(keyadd_table(vec![], "tx"), j.as_object().unwrap().get("tx").unwrap())?;
    Ok(())
}

pub fn prelude_cell_dep(path: Vec<Key>, data: &serde_json::Value) -> Result<(), CheckError> {
    prelude_contains_key(path.clone(), &data, "out_point")?;
    prelude_contains_key(path.clone(), &data, "dep_type")?;
    prelude_out_point(keyadd_table(path.clone(), "out_point"), data.as_object().unwrap().get("out_point").unwrap())?;
    prelude_dep_type(keyadd_table(path.clone(), "dep_type"), data.as_object().unwrap().get("dep_type").unwrap())?;
    Ok(())
}

pub fn prelude_contains_key(path: Vec<Key>, data: &serde_json::Value, key: &str) -> Result<(), CheckError> {
    if !data.is_object() {
        return Err(CheckError(format!("Check Fail: {} is not an object", keyfmt(&path))));
    }
    if !data.as_object().unwrap().contains_key(key) {
        return Err(CheckError(format!("Check Fail: {} missing members: {}", keyfmt(&path), key)));
    }
    Ok(())
}

pub fn prelude_dep_type(path: Vec<Key>, data: &serde_json::Value) -> Result<(), CheckError> {
    if !data.is_string() {
        return Err(CheckError(format!("Check Fail: {} {}", keyfmt(&path), "is not a legal dep type")));
    }
    if serde_json::from_str::<ckb_jsonrpc_types::DepType>(&format!("{:?}", &data.as_str().unwrap())).is_err() {
        return Err(CheckError(format!("Check Fail: {} {}", keyfmt(&path), "is not a legal dep type")));
    }
    Ok(())
}

pub fn prelude_hash(path: Vec<Key>, data: &serde_json::Value) -> Result<(), CheckError> {
    prelude_hex(path.clone(), data)?;
    if data.as_str().unwrap().len() != 66 {
        return Err(CheckError(format!("Check Fail: {} {}", keyfmt(&path), "is not a legal hash")));
    }
    Ok(())
}

pub fn prelude_hash_type(path: Vec<Key>, data: &serde_json::Value) -> Result<(), CheckError> {
    if !data.is_string() {
        return Err(CheckError(format!("Check Fail: {} {}", keyfmt(&path), "is not a legal hash type")));
    }
    if serde_json::from_str::<ckb_jsonrpc_types::ScriptHashType>(&format!("{:?}", &data.as_str().unwrap())).is_err() {
        return Err(CheckError(format!("Check Fail: {} {}", keyfmt(&path), "is not a legal hash type")));
    }
    Ok(())
}

pub fn prelude_hex(path: Vec<Key>, data: &serde_json::Value) -> Result<(), CheckError> {
    if !data.is_string() {
        return Err(CheckError(format!("Check Fail: {} {}", keyfmt(&path), "is not a legal hex string")));
    }
    if !data.as_str().unwrap().starts_with("0x") {
        return Err(CheckError(format!("Check Fail: {} {}", keyfmt(&path), "is not a legal hex string")));
    }
    if hex::decode(&data.as_str().unwrap()[2..]).is_err() {
        return Err(CheckError(format!("Check Fail: {} {}", keyfmt(&path), "is not a legal hex string")));
    }
    Ok(())
}

pub fn prelude_input(path: Vec<Key>, data: &serde_json::Value) -> Result<(), CheckError> {
    prelude_contains_key(path.clone(), &data, "since")?;
    prelude_contains_key(path.clone(), &data, "previous_output")?;
    prelude_u64(keyadd_table(path.clone(), "since"), data.as_object().unwrap().get("since").unwrap())?;
    prelude_out_point(
        keyadd_table(path.clone(), "previous_output"),
        data.as_object().unwrap().get("previous_output").unwrap(),
    )?;
    Ok(())
}

pub fn prelude_mock_info(path: Vec<Key>, data: &serde_json::Value) -> Result<(), CheckError> {
    prelude_contains_key(path.clone(), &data, "inputs")?;
    prelude_contains_key(path.clone(), &data, "cell_deps")?;
    prelude_contains_key(path.clone(), &data, "header_deps")?;
    for (i, e) in data.as_object().unwrap().get("inputs").unwrap().as_array().unwrap().iter().enumerate() {
        let path = keyadd_index(keyadd_table(path.clone(), "inputs"), i);
        prelude_contains_key(path.clone(), e, "input")?;
        prelude_contains_key(path.clone(), e, "output")?;
        prelude_contains_key(path.clone(), e, "data")?;
        prelude_input(keyadd_table(path.clone(), "input"), e.as_object().unwrap().get("input").unwrap())?;
        prelude_output(keyadd_table(path.clone(), "output"), e.as_object().unwrap().get("output").unwrap())?;
        prelude_hex(keyadd_table(path.clone(), "data"), e.as_object().unwrap().get("data").unwrap())?;
        if e.as_object().unwrap().contains_key("header") && !e.as_object().unwrap().get("header").unwrap().is_null() {
            prelude_hash(keyadd_table(path.clone(), "header"), e.as_object().unwrap().get("header").unwrap())?;
        }
    }
    for (i, e) in data.as_object().unwrap().get("cell_deps").unwrap().as_array().unwrap().iter().enumerate() {
        let path = keyadd_index(keyadd_table(path.clone(), "cell_deps"), i);
        prelude_contains_key(path.clone(), e, "cell_dep")?;
        prelude_contains_key(path.clone(), e, "output")?;
        prelude_contains_key(path.clone(), e, "data")?;
        prelude_cell_dep(keyadd_table(path.clone(), "cell_dep"), e.as_object().unwrap().get("cell_dep").unwrap())?;
        prelude_output(keyadd_table(path.clone(), "output"), e.as_object().unwrap().get("output").unwrap())?;
        prelude_hex(keyadd_table(path.clone(), "data"), e.as_object().unwrap().get("data").unwrap())?;
        if e.as_object().unwrap().contains_key("header") && !e.as_object().unwrap().get("header").unwrap().is_null() {
            prelude_hash(keyadd_table(path.clone(), "header"), e.as_object().unwrap().get("header").unwrap())?;
        }
    }
    for (i, e) in data.as_object().unwrap().get("header_deps").unwrap().as_array().unwrap().iter().enumerate() {
        let path = keyadd_index(keyadd_table(path.clone(), "header_deps"), i);
        prelude_contains_key(path.clone(), e, "compact_target")?;
        prelude_contains_key(path.clone(), e, "dao")?;
        prelude_contains_key(path.clone(), e, "epoch")?;
        prelude_contains_key(path.clone(), e, "extra_hash")?;
        prelude_contains_key(path.clone(), e, "hash")?;
        prelude_contains_key(path.clone(), e, "nonce")?;
        prelude_contains_key(path.clone(), e, "number")?;
        prelude_contains_key(path.clone(), e, "parent_hash")?;
        prelude_contains_key(path.clone(), e, "proposals_hash")?;
        prelude_contains_key(path.clone(), e, "timestamp")?;
        prelude_contains_key(path.clone(), e, "transactions_root")?;
        prelude_contains_key(path.clone(), e, "version")?;
        prelude_u32(
            keyadd_table(path.clone(), "compact_target"),
            e.as_object().unwrap().get("compact_target").unwrap(),
        )?;
        prelude_hash(keyadd_table(path.clone(), "dao"), e.as_object().unwrap().get("dao").unwrap())?;
        prelude_u64(keyadd_table(path.clone(), "epoch"), e.as_object().unwrap().get("epoch").unwrap())?;
        prelude_hash(keyadd_table(path.clone(), "extra_hash"), e.as_object().unwrap().get("extra_hash").unwrap())?;
        prelude_hash(keyadd_table(path.clone(), "hash"), e.as_object().unwrap().get("hash").unwrap())?;
        prelude_u128(keyadd_table(path.clone(), "nonce"), e.as_object().unwrap().get("nonce").unwrap())?;
        prelude_u64(keyadd_table(path.clone(), "number"), e.as_object().unwrap().get("number").unwrap())?;
        prelude_hash(keyadd_table(path.clone(), "parent_hash"), e.as_object().unwrap().get("parent_hash").unwrap())?;
        prelude_hash(
            keyadd_table(path.clone(), "proposals_hash"),
            e.as_object().unwrap().get("proposals_hash").unwrap(),
        )?;
        prelude_u64(keyadd_table(path.clone(), "timestamp"), e.as_object().unwrap().get("timestamp").unwrap())?;
        prelude_hash(
            keyadd_table(path.clone(), "transactions_root"),
            e.as_object().unwrap().get("transactions_root").unwrap(),
        )?;
        prelude_u32(keyadd_table(path.clone(), "version"), e.as_object().unwrap().get("version").unwrap())?;
    }
    Ok(())
}

pub fn prelude_out_point(path: Vec<Key>, data: &serde_json::Value) -> Result<(), CheckError> {
    prelude_contains_key(path.clone(), data, "tx_hash")?;
    prelude_contains_key(path.clone(), data, "index")?;
    prelude_hash(keyadd_table(path.clone(), "tx_hash"), data.as_object().unwrap().get("tx_hash").unwrap())?;
    prelude_u32(keyadd_table(path.clone(), "index"), data.as_object().unwrap().get("index").unwrap())?;
    Ok(())
}

pub fn prelude_output(path: Vec<Key>, data: &serde_json::Value) -> Result<(), CheckError> {
    prelude_contains_key(path.clone(), data, "capacity")?;
    prelude_contains_key(path.clone(), data, "lock")?;
    prelude_u64(keyadd_table(path.clone(), "capacity"), data.as_object().unwrap().get("capacity").unwrap())?;
    prelude_script(keyadd_table(path.clone(), "lock"), data.as_object().unwrap().get("lock").unwrap())?;
    if data.as_object().unwrap().contains_key("type") && !data.as_object().unwrap().get("type").unwrap().is_null() {
        prelude_script(keyadd_table(path.clone(), "type"), data.as_object().unwrap().get("type").unwrap())?;
    }
    Ok(())
}

pub fn prelude_script(path: Vec<Key>, data: &serde_json::Value) -> Result<(), CheckError> {
    prelude_contains_key(path.clone(), data, "code_hash")?;
    prelude_contains_key(path.clone(), data, "hash_type")?;
    prelude_contains_key(path.clone(), data, "args")?;
    prelude_hash(keyadd_table(path.clone(), "code_hash"), data.as_object().unwrap().get("code_hash").unwrap())?;
    prelude_hash_type(keyadd_table(path.clone(), "hash_type"), data.as_object().unwrap().get("hash_type").unwrap())?;
    prelude_hex(keyadd_table(path.clone(), "args"), data.as_object().unwrap().get("args").unwrap())?;
    Ok(())
}

pub fn prelude_tx(path: Vec<Key>, data: &serde_json::Value) -> Result<(), CheckError> {
    prelude_contains_key(path.clone(), &data, "version")?;
    prelude_contains_key(path.clone(), &data, "cell_deps")?;
    prelude_contains_key(path.clone(), &data, "header_deps")?;
    prelude_contains_key(path.clone(), &data, "inputs")?;
    prelude_contains_key(path.clone(), &data, "outputs")?;
    prelude_contains_key(path.clone(), &data, "outputs_data")?;
    prelude_contains_key(path.clone(), &data, "witnesses")?;
    prelude_u32(keyadd_table(path.clone(), "version"), data.as_object().unwrap().get("version").unwrap())?;
    for (i, e) in data.as_object().unwrap().get("cell_deps").unwrap().as_array().unwrap().iter().enumerate() {
        let path = keyadd_index(keyadd_table(path.clone(), "cell_deps"), i);
        prelude_cell_dep(path, e)?;
    }
    for (i, e) in data.as_object().unwrap().get("header_deps").unwrap().as_array().unwrap().iter().enumerate() {
        let path = keyadd_index(keyadd_table(path.clone(), "header_deps"), i);
        prelude_hash(path, e)?;
    }
    for (i, e) in data.as_object().unwrap().get("inputs").unwrap().as_array().unwrap().iter().enumerate() {
        let path = keyadd_index(keyadd_table(path.clone(), "inputs"), i);
        prelude_input(path, e)?;
    }
    for (i, e) in data.as_object().unwrap().get("outputs").unwrap().as_array().unwrap().iter().enumerate() {
        let path = keyadd_index(keyadd_table(path.clone(), "outputs"), i);
        prelude_output(path, e)?;
    }
    for (i, e) in data.as_object().unwrap().get("outputs_data").unwrap().as_array().unwrap().iter().enumerate() {
        let path = keyadd_index(keyadd_table(path.clone(), "outputs_data"), i);
        prelude_hex(path, e)?;
    }
    for (i, e) in data.as_object().unwrap().get("witnesses").unwrap().as_array().unwrap().iter().enumerate() {
        let path = keyadd_index(keyadd_table(path.clone(), "witnesses"), i);
        prelude_hex(path, e)?;
    }
    Ok(())
}

pub fn prelude_u128(path: Vec<Key>, data: &serde_json::Value) -> Result<(), CheckError> {
    if !data.is_string() || !data.as_str().unwrap().starts_with("0x") {
        return Err(CheckError(format!("Check Fail: {} {}", keyfmt(&path), "is not a legal u128")));
    }
    if u128::from_str_radix(&data.as_str().unwrap()[2..], 16).is_err() {
        return Err(CheckError(format!("Check Fail: {} {}", keyfmt(&path), "is not a legal u128")));
    }
    Ok(())
}

pub fn prelude_u32(path: Vec<Key>, data: &serde_json::Value) -> Result<(), CheckError> {
    if !data.is_string() || !data.as_str().unwrap().starts_with("0x") {
        return Err(CheckError(format!("Check Fail: {} {}", keyfmt(&path), "is not a legal u32")));
    }
    if u32::from_str_radix(&data.as_str().unwrap()[2..], 16).is_err() {
        return Err(CheckError(format!("Check Fail: {} {}", keyfmt(&path), "is not a legal u32")));
    }
    Ok(())
}

pub fn prelude_u64(path: Vec<Key>, data: &serde_json::Value) -> Result<(), CheckError> {
    if !data.is_string() || !data.as_str().unwrap().starts_with("0x") {
        return Err(CheckError(format!("Check Fail: {} {}", keyfmt(&path), "is not a legal u64")));
    }
    if u64::from_str_radix(&data.as_str().unwrap()[2..], 16).is_err() {
        return Err(CheckError(format!("Check Fail: {} {}", keyfmt(&path), "is not a legal u64")));
    }
    Ok(())
}

pub struct CheckError(String);

impl std::fmt::Debug for CheckError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(&self.0)
    }
}

impl std::fmt::Display for CheckError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(&self.0)
    }
}

impl std::error::Error for CheckError {}

#[derive(Clone, Debug)]
pub enum Key {
    Table(String),
    Index(usize),
}

pub fn keyfmt(key: &[Key]) -> String {
    let mut s = String::from("json");
    for e in key {
        match e {
            Key::Table(k) => {
                s.push_str(&format!("[\"{}\"]", k));
            }
            Key::Index(i) => {
                s.push_str(&format!("[{}]", i));
            }
        }
    }
    s
}

pub fn keyadd_index(mut key: Vec<Key>, add: usize) -> Vec<Key> {
    key.push(Key::Index(add));
    key
}

pub fn keyadd_table(mut key: Vec<Key>, add: &str) -> Vec<Key> {
    key.push(Key::Table(String::from(add)));
    key
}