|
| 1 | +// Copyright 2025 Andrew Poelstra |
| 2 | +// SPDX-License-Identifier: CC0-1.0 |
| 3 | + |
| 4 | +use crate::cmd; |
| 5 | + |
| 6 | +use hal_simplicity::hal_simplicity::Program; |
| 7 | +use hal_simplicity::simplicity::bit_machine::{BitMachine, ExecTracker}; |
| 8 | +use hal_simplicity::simplicity::jet; |
| 9 | +use hal_simplicity::simplicity::{Cmr, Ihr}; |
| 10 | + |
| 11 | +use super::super::{Error, ErrorExt as _}; |
| 12 | + |
| 13 | +pub fn cmd<'a>() -> clap::App<'a, 'a> { |
| 14 | + cmd::subcommand("run", "Run a Simplicity program in the context of a PSET input.") |
| 15 | + .args(&cmd::opts_networks()) |
| 16 | + .args(&[ |
| 17 | + cmd::arg("pset", "PSET to update (base64)").takes_value(true).required(true), |
| 18 | + cmd::arg("input-index", "the index of the input to sign (decimal)") |
| 19 | + .takes_value(true) |
| 20 | + .required(true), |
| 21 | + cmd::arg("program", "Simplicity program (base64)").takes_value(true).required(true), |
| 22 | + cmd::arg("witness", "Simplicity program witness (hex)") |
| 23 | + .takes_value(true) |
| 24 | + .required(true), |
| 25 | + cmd::opt( |
| 26 | + "genesis-hash", |
| 27 | + "genesis hash of the blockchain the transaction belongs to (hex)", |
| 28 | + ) |
| 29 | + .short("g") |
| 30 | + .required(false), |
| 31 | + ]) |
| 32 | +} |
| 33 | + |
| 34 | +pub fn exec<'a>(matches: &clap::ArgMatches<'a>) { |
| 35 | + let pset_b64 = matches.value_of("pset").expect("tx mandatory"); |
| 36 | + let input_idx = matches.value_of("input-index").expect("input-idx is mandatory"); |
| 37 | + let program = matches.value_of("program").expect("program is mandatory"); |
| 38 | + let witness = matches.value_of("witness").expect("witness is mandatory"); |
| 39 | + let genesis_hash = matches.value_of("genesis-hash"); |
| 40 | + |
| 41 | + match exec_inner(pset_b64, input_idx, program, witness, genesis_hash) { |
| 42 | + Ok(info) => cmd::print_output(matches, &info), |
| 43 | + Err(e) => cmd::print_output(matches, &e), |
| 44 | + } |
| 45 | +} |
| 46 | + |
| 47 | +#[derive(serde::Serialize)] |
| 48 | +struct JetCall { |
| 49 | + jet: String, |
| 50 | + source_ty: String, |
| 51 | + target_ty: String, |
| 52 | + success: bool, |
| 53 | + input_hex: String, |
| 54 | + output_hex: String, |
| 55 | + #[serde(skip_serializing_if = "Option::is_none")] |
| 56 | + equality_check: Option<(String, String)>, |
| 57 | +} |
| 58 | + |
| 59 | +#[derive(serde::Serialize)] |
| 60 | +struct Response { |
| 61 | + success: bool, |
| 62 | + jets: Vec<JetCall>, |
| 63 | +} |
| 64 | + |
| 65 | +#[allow(clippy::too_many_arguments)] |
| 66 | +fn exec_inner( |
| 67 | + pset_b64: &str, |
| 68 | + input_idx: &str, |
| 69 | + program: &str, |
| 70 | + witness: &str, |
| 71 | + genesis_hash: Option<&str>, |
| 72 | +) -> Result<Response, Error> { |
| 73 | + struct JetTracker(Vec<JetCall>); |
| 74 | + impl<J: jet::Jet> ExecTracker<J> for JetTracker { |
| 75 | + fn track_left(&mut self, _: Ihr) {} |
| 76 | + fn track_right(&mut self, _: Ihr) {} |
| 77 | + fn track_jet_call( |
| 78 | + &mut self, |
| 79 | + jet: &J, |
| 80 | + input_buffer: &[simplicity::ffi::ffi::UWORD], |
| 81 | + output_buffer: &[simplicity::ffi::ffi::UWORD], |
| 82 | + success: bool, |
| 83 | + ) { |
| 84 | + // The word slices are in reverse order for some reason. |
| 85 | + // FIXME maybe we should attempt to parse out Simplicity values here which |
| 86 | + // can often be displayed in a better way, esp for e.g. option types. |
| 87 | + let mut input_hex = String::new(); |
| 88 | + for word in input_buffer.iter().rev() { |
| 89 | + for byte in word.to_be_bytes() { |
| 90 | + input_hex.push_str(&format!("{:02x}", byte)); |
| 91 | + } |
| 92 | + } |
| 93 | + |
| 94 | + let mut output_hex = String::new(); |
| 95 | + for word in output_buffer.iter().rev() { |
| 96 | + for byte in word.to_be_bytes() { |
| 97 | + output_hex.push_str(&format!("{:02x}", byte)); |
| 98 | + } |
| 99 | + } |
| 100 | + |
| 101 | + let jet_name = jet.to_string(); |
| 102 | + let equality_check = match jet_name.as_str() { |
| 103 | + "eq_1" => None, // FIXME parse bits out of input |
| 104 | + "eq_2" => None, // FIXME parse bits out of input |
| 105 | + x if x.strip_prefix("eq_").is_some() => { |
| 106 | + let split = input_hex.split_at(input_hex.len() / 2); |
| 107 | + Some((split.0.to_owned(), split.1.to_owned())) |
| 108 | + } |
| 109 | + _ => None, |
| 110 | + }; |
| 111 | + self.0.push(JetCall { |
| 112 | + jet: jet_name, |
| 113 | + source_ty: jet.source_ty().to_final().to_string(), |
| 114 | + target_ty: jet.target_ty().to_final().to_string(), |
| 115 | + success, |
| 116 | + input_hex, |
| 117 | + output_hex, |
| 118 | + equality_check, |
| 119 | + }); |
| 120 | + } |
| 121 | + |
| 122 | + fn track_dbg_call(&mut self, _: &Cmr, _: simplicity::Value) {} |
| 123 | + fn is_track_debug_enabled(&self) -> bool { |
| 124 | + false |
| 125 | + } |
| 126 | + } |
| 127 | + |
| 128 | + // 1. Parse everything. |
| 129 | + let pset: elements::pset::PartiallySignedTransaction = |
| 130 | + pset_b64.parse().result_context("decoding PSET")?; |
| 131 | + let input_idx: u32 = input_idx.parse().result_context("parsing input-idx")?; |
| 132 | + let input_idx_usize = input_idx as usize; // 32->usize cast ok on almost all systems |
| 133 | + |
| 134 | + let program = Program::<jet::Elements>::from_str(program, Some(witness)) |
| 135 | + .result_context("parsing program")?; |
| 136 | + |
| 137 | + // 2. Extract transaction environment. |
| 138 | + let (tx_env, _control_block, _tap_leaf) = |
| 139 | + super::execution_environment(&pset, input_idx_usize, program.cmr(), genesis_hash)?; |
| 140 | + |
| 141 | + // 3. Prune program. |
| 142 | + let redeem_node = program.redeem_node().expect("populated"); |
| 143 | + |
| 144 | + let mut mac = |
| 145 | + BitMachine::for_program(redeem_node).result_context("constructing bit machine")?; |
| 146 | + let mut tracker = JetTracker(vec![]); |
| 147 | + // Eat success/failure. FIXME should probably report this to the user. |
| 148 | + let success = mac.exec_with_tracker(redeem_node, &tx_env, &mut tracker).is_ok(); |
| 149 | + Ok(Response { |
| 150 | + success, |
| 151 | + jets: tracker.0, |
| 152 | + }) |
| 153 | +} |
0 commit comments