Skip to main content

ckb_debugger/
mock_tx_analyzer.rs

1use std::fmt::Debug;
2
3use ckb_types::prelude::Entity;
4
5fn analyze(data: &str) -> Result<(), CheckError> {
6    let mock: ckb_mock_tx_types::ReprMockTransaction = serde_json::from_str(&data).unwrap();
7    analyze_cell_dep(&mock)?;
8    analyze_header_dep(&mock)?;
9    analyze_input(&mock)?;
10    analyze_output(&mock)?;
11    Ok(())
12}
13
14fn analyze_cell_dep(data: &ckb_mock_tx_types::ReprMockTransaction) -> Result<(), CheckError> {
15    let cset: Vec<ckb_jsonrpc_types::CellDep> = data.mock_info.cell_deps.iter().map(|e| e.cell_dep.clone()).collect();
16    for (i, e) in data.tx.cell_deps.iter().enumerate() {
17        if !cset.contains(&e) {
18            let path = vec![Key::Table(String::from("tx")), Key::Table(String::from("cell_deps")), Key::Index(i)];
19            return Err(CheckError(format!("Check Fail: {} used unprovided cell dep", keyfmt(&path))));
20        }
21    }
22    let mut ccnt = vec![0u8; cset.len()];
23    for (_, e) in data.tx.cell_deps.iter().enumerate() {
24        let i = cset.iter().position(|r| r == e).unwrap();
25        ccnt[i] += 1;
26        if data.mock_info.cell_deps[i].cell_dep.dep_type == ckb_jsonrpc_types::DepType::Code {
27            continue;
28        }
29        let outpoints =
30            ckb_types::packed::OutPointVec::from_slice(data.mock_info.cell_deps[i].data.as_bytes()).unwrap();
31        let outpoints: Vec<ckb_types::packed::OutPoint> = outpoints.into_iter().collect();
32        for (j, f) in outpoints.iter().enumerate() {
33            let cdep =
34                ckb_jsonrpc_types::CellDep { out_point: f.clone().into(), dep_type: ckb_jsonrpc_types::DepType::Code };
35            if !cset.contains(&cdep) {
36                let path = vec![
37                    Key::Table(String::from("mock_info")),
38                    Key::Table(String::from("cell_deps")),
39                    Key::Index(i),
40                    Key::Table(String::from("data")),
41                    Key::Index(j),
42                ];
43                return Err(CheckError(format!("Check Fail: {} used unprovided cell dep", keyfmt(&path))));
44            }
45            let k = cset.iter().position(|r| r == &cdep).unwrap();
46            ccnt[k] += 1;
47        }
48    }
49    for (i, e) in ccnt.iter().enumerate() {
50        if *e != 0 {
51            continue;
52        }
53        let path = vec![Key::Table(String::from("mock_info")), Key::Table(String::from("cell_deps")), Key::Index(i)];
54        return Err(CheckError(format!("Check Fail: {} unused", keyfmt(&path))));
55    }
56    Ok(())
57}
58
59fn analyze_header_dep(data: &ckb_mock_tx_types::ReprMockTransaction) -> Result<(), CheckError> {
60    let hset: Vec<ckb_types::H256> = data.mock_info.header_deps.iter().map(|e| e.hash.clone()).collect();
61    for (i, e) in data.tx.header_deps.iter().enumerate() {
62        if !hset.contains(&e) {
63            let path = vec![Key::Table(String::from("tx")), Key::Table(String::from("header_deps")), Key::Index(i)];
64            return Err(CheckError(format!("Check Fail: {} used unprovided header dep", keyfmt(&path))));
65        }
66    }
67    for (i, e) in hset.iter().enumerate() {
68        if !data.tx.header_deps.contains(&e) {
69            let path =
70                vec![Key::Table(String::from("mock_info")), Key::Table(String::from("header_deps")), Key::Index(i)];
71            return Err(CheckError(format!("Check Fail: {} unused", keyfmt(&path))));
72        }
73    }
74    Ok(())
75}
76
77fn analyze_input(data: &ckb_mock_tx_types::ReprMockTransaction) -> Result<(), CheckError> {
78    let iset: Vec<ckb_jsonrpc_types::CellInput> = data.mock_info.inputs.iter().map(|e| e.input.clone()).collect();
79    for (i, e) in data.tx.inputs.iter().enumerate() {
80        if !iset.contains(&e) {
81            let path = vec![Key::Table(String::from("tx")), Key::Table(String::from("inputs")), Key::Index(i)];
82            return Err(CheckError(format!("Check Fail: {} used unprovided input", keyfmt(&path))));
83        }
84    }
85    for (i, e) in iset.iter().enumerate() {
86        if !data.tx.inputs.contains(&e) {
87            let path = vec![Key::Table(String::from("mock_info")), Key::Table(String::from("inputs")), Key::Index(i)];
88            return Err(CheckError(format!("Check Fail: {} unused", keyfmt(&path))));
89        }
90    }
91    Ok(())
92}
93
94fn analyze_output(data: &ckb_mock_tx_types::ReprMockTransaction) -> Result<(), CheckError> {
95    if data.tx.outputs.len() != data.tx.outputs_data.len() {
96        let path = vec![Key::Table(String::from("tx")), Key::Table(String::from("outputs"))];
97        return Err(CheckError(format!(
98            "Check Fail: {} outputs and outputs_data are not one-to-one correspondence",
99            keyfmt(&path)
100        )));
101    }
102    Ok(())
103}
104
105/// Analyzes a JSON string representing a mock transaction, validating its structure and dependencies. Returns Ok(())
106/// if valid, or a CheckError if validation fails.
107pub fn mock_tx_analyze(data: &str) -> Result<(), CheckError> {
108    prelude(data)?;
109    analyze(data)
110}
111
112fn prelude(data: &str) -> Result<(), CheckError> {
113    let j: serde_json::Value = serde_json::from_str(data).map_err(|e| CheckError(e.to_string()))?;
114    prelude_contains_key(vec![], &j, "mock_info")?;
115    prelude_contains_key(vec![], &j, "tx")?;
116    prelude_mock_info(keyadd_table(vec![], "mock_info"), j.as_object().unwrap().get("mock_info").unwrap())?;
117    prelude_tx(keyadd_table(vec![], "tx"), j.as_object().unwrap().get("tx").unwrap())?;
118    Ok(())
119}
120
121fn prelude_cell_dep(path: Vec<Key>, data: &serde_json::Value) -> Result<(), CheckError> {
122    prelude_contains_key(path.clone(), &data, "out_point")?;
123    prelude_contains_key(path.clone(), &data, "dep_type")?;
124    prelude_out_point(keyadd_table(path.clone(), "out_point"), data.as_object().unwrap().get("out_point").unwrap())?;
125    prelude_dep_type(keyadd_table(path.clone(), "dep_type"), data.as_object().unwrap().get("dep_type").unwrap())?;
126    Ok(())
127}
128
129fn prelude_contains_key(path: Vec<Key>, data: &serde_json::Value, key: &str) -> Result<(), CheckError> {
130    if !data.is_object() {
131        return Err(CheckError(format!("Check Fail: {} is not an object", keyfmt(&path))));
132    }
133    if !data.as_object().unwrap().contains_key(key) {
134        return Err(CheckError(format!("Check Fail: {} missing members: {}", keyfmt(&path), key)));
135    }
136    Ok(())
137}
138
139fn prelude_dep_type(path: Vec<Key>, data: &serde_json::Value) -> Result<(), CheckError> {
140    if !data.is_string() {
141        return Err(CheckError(format!("Check Fail: {} {}", keyfmt(&path), "is not a legal dep type")));
142    }
143    if serde_json::from_str::<ckb_jsonrpc_types::DepType>(&format!("{:?}", &data.as_str().unwrap())).is_err() {
144        return Err(CheckError(format!("Check Fail: {} {}", keyfmt(&path), "is not a legal dep type")));
145    }
146    Ok(())
147}
148
149fn prelude_hash(path: Vec<Key>, data: &serde_json::Value) -> Result<(), CheckError> {
150    prelude_hex(path.clone(), data)?;
151    if data.as_str().unwrap().len() != 66 {
152        return Err(CheckError(format!("Check Fail: {} {}", keyfmt(&path), "is not a legal hash")));
153    }
154    Ok(())
155}
156
157fn prelude_hash_type(path: Vec<Key>, data: &serde_json::Value) -> Result<(), CheckError> {
158    if !data.is_string() {
159        return Err(CheckError(format!("Check Fail: {} {}", keyfmt(&path), "is not a legal hash type")));
160    }
161    if serde_json::from_str::<ckb_jsonrpc_types::ScriptHashType>(&format!("{:?}", &data.as_str().unwrap())).is_err() {
162        return Err(CheckError(format!("Check Fail: {} {}", keyfmt(&path), "is not a legal hash type")));
163    }
164    Ok(())
165}
166
167fn prelude_hex(path: Vec<Key>, data: &serde_json::Value) -> Result<(), CheckError> {
168    if !data.is_string() {
169        return Err(CheckError(format!("Check Fail: {} {}", keyfmt(&path), "is not a legal hex string")));
170    }
171    if !data.as_str().unwrap().starts_with("0x") {
172        return Err(CheckError(format!("Check Fail: {} {}", keyfmt(&path), "is not a legal hex string")));
173    }
174    if hex::decode(&data.as_str().unwrap()[2..]).is_err() {
175        return Err(CheckError(format!("Check Fail: {} {}", keyfmt(&path), "is not a legal hex string")));
176    }
177    Ok(())
178}
179
180fn prelude_input(path: Vec<Key>, data: &serde_json::Value) -> Result<(), CheckError> {
181    prelude_contains_key(path.clone(), &data, "since")?;
182    prelude_contains_key(path.clone(), &data, "previous_output")?;
183    prelude_u64(keyadd_table(path.clone(), "since"), data.as_object().unwrap().get("since").unwrap())?;
184    prelude_out_point(
185        keyadd_table(path.clone(), "previous_output"),
186        data.as_object().unwrap().get("previous_output").unwrap(),
187    )?;
188    Ok(())
189}
190
191fn prelude_mock_info(path: Vec<Key>, data: &serde_json::Value) -> Result<(), CheckError> {
192    prelude_contains_key(path.clone(), &data, "inputs")?;
193    prelude_contains_key(path.clone(), &data, "cell_deps")?;
194    prelude_contains_key(path.clone(), &data, "header_deps")?;
195    for (i, e) in data.as_object().unwrap().get("inputs").unwrap().as_array().unwrap().iter().enumerate() {
196        let path = keyadd_index(keyadd_table(path.clone(), "inputs"), i);
197        prelude_contains_key(path.clone(), e, "input")?;
198        prelude_contains_key(path.clone(), e, "output")?;
199        prelude_contains_key(path.clone(), e, "data")?;
200        prelude_input(keyadd_table(path.clone(), "input"), e.as_object().unwrap().get("input").unwrap())?;
201        prelude_output(keyadd_table(path.clone(), "output"), e.as_object().unwrap().get("output").unwrap())?;
202        prelude_hex(keyadd_table(path.clone(), "data"), e.as_object().unwrap().get("data").unwrap())?;
203        if e.as_object().unwrap().contains_key("header") && !e.as_object().unwrap().get("header").unwrap().is_null() {
204            prelude_hash(keyadd_table(path.clone(), "header"), e.as_object().unwrap().get("header").unwrap())?;
205        }
206    }
207    for (i, e) in data.as_object().unwrap().get("cell_deps").unwrap().as_array().unwrap().iter().enumerate() {
208        let path = keyadd_index(keyadd_table(path.clone(), "cell_deps"), i);
209        prelude_contains_key(path.clone(), e, "cell_dep")?;
210        prelude_contains_key(path.clone(), e, "output")?;
211        prelude_contains_key(path.clone(), e, "data")?;
212        prelude_cell_dep(keyadd_table(path.clone(), "cell_dep"), e.as_object().unwrap().get("cell_dep").unwrap())?;
213        prelude_output(keyadd_table(path.clone(), "output"), e.as_object().unwrap().get("output").unwrap())?;
214        prelude_hex(keyadd_table(path.clone(), "data"), e.as_object().unwrap().get("data").unwrap())?;
215        if e.as_object().unwrap().contains_key("header") && !e.as_object().unwrap().get("header").unwrap().is_null() {
216            prelude_hash(keyadd_table(path.clone(), "header"), e.as_object().unwrap().get("header").unwrap())?;
217        }
218    }
219    for (i, e) in data.as_object().unwrap().get("header_deps").unwrap().as_array().unwrap().iter().enumerate() {
220        let path = keyadd_index(keyadd_table(path.clone(), "header_deps"), i);
221        prelude_contains_key(path.clone(), e, "compact_target")?;
222        prelude_contains_key(path.clone(), e, "dao")?;
223        prelude_contains_key(path.clone(), e, "epoch")?;
224        prelude_contains_key(path.clone(), e, "extra_hash")?;
225        prelude_contains_key(path.clone(), e, "hash")?;
226        prelude_contains_key(path.clone(), e, "nonce")?;
227        prelude_contains_key(path.clone(), e, "number")?;
228        prelude_contains_key(path.clone(), e, "parent_hash")?;
229        prelude_contains_key(path.clone(), e, "proposals_hash")?;
230        prelude_contains_key(path.clone(), e, "timestamp")?;
231        prelude_contains_key(path.clone(), e, "transactions_root")?;
232        prelude_contains_key(path.clone(), e, "version")?;
233        prelude_u32(
234            keyadd_table(path.clone(), "compact_target"),
235            e.as_object().unwrap().get("compact_target").unwrap(),
236        )?;
237        prelude_hash(keyadd_table(path.clone(), "dao"), e.as_object().unwrap().get("dao").unwrap())?;
238        prelude_u64(keyadd_table(path.clone(), "epoch"), e.as_object().unwrap().get("epoch").unwrap())?;
239        prelude_hash(keyadd_table(path.clone(), "extra_hash"), e.as_object().unwrap().get("extra_hash").unwrap())?;
240        prelude_hash(keyadd_table(path.clone(), "hash"), e.as_object().unwrap().get("hash").unwrap())?;
241        prelude_u128(keyadd_table(path.clone(), "nonce"), e.as_object().unwrap().get("nonce").unwrap())?;
242        prelude_u64(keyadd_table(path.clone(), "number"), e.as_object().unwrap().get("number").unwrap())?;
243        prelude_hash(keyadd_table(path.clone(), "parent_hash"), e.as_object().unwrap().get("parent_hash").unwrap())?;
244        prelude_hash(
245            keyadd_table(path.clone(), "proposals_hash"),
246            e.as_object().unwrap().get("proposals_hash").unwrap(),
247        )?;
248        prelude_u64(keyadd_table(path.clone(), "timestamp"), e.as_object().unwrap().get("timestamp").unwrap())?;
249        prelude_hash(
250            keyadd_table(path.clone(), "transactions_root"),
251            e.as_object().unwrap().get("transactions_root").unwrap(),
252        )?;
253        prelude_u32(keyadd_table(path.clone(), "version"), e.as_object().unwrap().get("version").unwrap())?;
254    }
255    Ok(())
256}
257
258fn prelude_out_point(path: Vec<Key>, data: &serde_json::Value) -> Result<(), CheckError> {
259    prelude_contains_key(path.clone(), data, "tx_hash")?;
260    prelude_contains_key(path.clone(), data, "index")?;
261    prelude_hash(keyadd_table(path.clone(), "tx_hash"), data.as_object().unwrap().get("tx_hash").unwrap())?;
262    prelude_u32(keyadd_table(path.clone(), "index"), data.as_object().unwrap().get("index").unwrap())?;
263    Ok(())
264}
265
266fn prelude_output(path: Vec<Key>, data: &serde_json::Value) -> Result<(), CheckError> {
267    prelude_contains_key(path.clone(), data, "capacity")?;
268    prelude_contains_key(path.clone(), data, "lock")?;
269    prelude_u64(keyadd_table(path.clone(), "capacity"), data.as_object().unwrap().get("capacity").unwrap())?;
270    prelude_script(keyadd_table(path.clone(), "lock"), data.as_object().unwrap().get("lock").unwrap())?;
271    if data.as_object().unwrap().contains_key("type") && !data.as_object().unwrap().get("type").unwrap().is_null() {
272        prelude_script(keyadd_table(path.clone(), "type"), data.as_object().unwrap().get("type").unwrap())?;
273    }
274    Ok(())
275}
276
277fn prelude_script(path: Vec<Key>, data: &serde_json::Value) -> Result<(), CheckError> {
278    prelude_contains_key(path.clone(), data, "code_hash")?;
279    prelude_contains_key(path.clone(), data, "hash_type")?;
280    prelude_contains_key(path.clone(), data, "args")?;
281    prelude_hash(keyadd_table(path.clone(), "code_hash"), data.as_object().unwrap().get("code_hash").unwrap())?;
282    prelude_hash_type(keyadd_table(path.clone(), "hash_type"), data.as_object().unwrap().get("hash_type").unwrap())?;
283    prelude_hex(keyadd_table(path.clone(), "args"), data.as_object().unwrap().get("args").unwrap())?;
284    Ok(())
285}
286
287fn prelude_tx(path: Vec<Key>, data: &serde_json::Value) -> Result<(), CheckError> {
288    prelude_contains_key(path.clone(), &data, "version")?;
289    prelude_contains_key(path.clone(), &data, "cell_deps")?;
290    prelude_contains_key(path.clone(), &data, "header_deps")?;
291    prelude_contains_key(path.clone(), &data, "inputs")?;
292    prelude_contains_key(path.clone(), &data, "outputs")?;
293    prelude_contains_key(path.clone(), &data, "outputs_data")?;
294    prelude_contains_key(path.clone(), &data, "witnesses")?;
295    prelude_u32(keyadd_table(path.clone(), "version"), data.as_object().unwrap().get("version").unwrap())?;
296    for (i, e) in data.as_object().unwrap().get("cell_deps").unwrap().as_array().unwrap().iter().enumerate() {
297        let path = keyadd_index(keyadd_table(path.clone(), "cell_deps"), i);
298        prelude_cell_dep(path, e)?;
299    }
300    for (i, e) in data.as_object().unwrap().get("header_deps").unwrap().as_array().unwrap().iter().enumerate() {
301        let path = keyadd_index(keyadd_table(path.clone(), "header_deps"), i);
302        prelude_hash(path, e)?;
303    }
304    for (i, e) in data.as_object().unwrap().get("inputs").unwrap().as_array().unwrap().iter().enumerate() {
305        let path = keyadd_index(keyadd_table(path.clone(), "inputs"), i);
306        prelude_input(path, e)?;
307    }
308    for (i, e) in data.as_object().unwrap().get("outputs").unwrap().as_array().unwrap().iter().enumerate() {
309        let path = keyadd_index(keyadd_table(path.clone(), "outputs"), i);
310        prelude_output(path, e)?;
311    }
312    for (i, e) in data.as_object().unwrap().get("outputs_data").unwrap().as_array().unwrap().iter().enumerate() {
313        let path = keyadd_index(keyadd_table(path.clone(), "outputs_data"), i);
314        prelude_hex(path, e)?;
315    }
316    for (i, e) in data.as_object().unwrap().get("witnesses").unwrap().as_array().unwrap().iter().enumerate() {
317        let path = keyadd_index(keyadd_table(path.clone(), "witnesses"), i);
318        prelude_hex(path, e)?;
319    }
320    Ok(())
321}
322
323fn prelude_u128(path: Vec<Key>, data: &serde_json::Value) -> Result<(), CheckError> {
324    if !data.is_string() || !data.as_str().unwrap().starts_with("0x") {
325        return Err(CheckError(format!("Check Fail: {} {}", keyfmt(&path), "is not a legal u128")));
326    }
327    if u128::from_str_radix(&data.as_str().unwrap()[2..], 16).is_err() {
328        return Err(CheckError(format!("Check Fail: {} {}", keyfmt(&path), "is not a legal u128")));
329    }
330    Ok(())
331}
332
333fn prelude_u32(path: Vec<Key>, data: &serde_json::Value) -> Result<(), CheckError> {
334    if !data.is_string() || !data.as_str().unwrap().starts_with("0x") {
335        return Err(CheckError(format!("Check Fail: {} {}", keyfmt(&path), "is not a legal u32")));
336    }
337    if u32::from_str_radix(&data.as_str().unwrap()[2..], 16).is_err() {
338        return Err(CheckError(format!("Check Fail: {} {}", keyfmt(&path), "is not a legal u32")));
339    }
340    Ok(())
341}
342
343fn prelude_u64(path: Vec<Key>, data: &serde_json::Value) -> Result<(), CheckError> {
344    if !data.is_string() || !data.as_str().unwrap().starts_with("0x") {
345        return Err(CheckError(format!("Check Fail: {} {}", keyfmt(&path), "is not a legal u64")));
346    }
347    if u64::from_str_radix(&data.as_str().unwrap()[2..], 16).is_err() {
348        return Err(CheckError(format!("Check Fail: {} {}", keyfmt(&path), "is not a legal u64")));
349    }
350    Ok(())
351}
352
353pub struct CheckError(String);
354
355impl std::fmt::Debug for CheckError {
356    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
357        f.write_str(&self.0)
358    }
359}
360
361impl std::fmt::Display for CheckError {
362    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
363        f.write_str(&self.0)
364    }
365}
366
367impl std::error::Error for CheckError {}
368
369#[derive(Clone, Debug)]
370enum Key {
371    Table(String),
372    Index(usize),
373}
374
375fn keyfmt(key: &[Key]) -> String {
376    let mut s = String::from("json");
377    for e in key {
378        match e {
379            Key::Table(k) => {
380                s.push_str(&format!("[\"{}\"]", k));
381            }
382            Key::Index(i) => {
383                s.push_str(&format!("[{}]", i));
384            }
385        }
386    }
387    s
388}
389
390fn keyadd_index(mut key: Vec<Key>, add: usize) -> Vec<Key> {
391    key.push(Key::Index(add));
392    key
393}
394
395fn keyadd_table(mut key: Vec<Key>, add: &str) -> Vec<Key> {
396    key.push(Key::Table(String::from(add)));
397    key
398}