hal_simplicity/actions/simplicity/pset/
run.rs

1// Copyright 2025 Andrew Poelstra
2// SPDX-License-Identifier: CC0-1.0
3
4use serde::Serialize;
5
6use crate::hal_simplicity::Program;
7use crate::simplicity::bit_machine::{BitMachine, ExecTracker, FrameIter, NodeOutput};
8use crate::simplicity::Value;
9use crate::simplicity::{jet, node};
10
11use super::{execution_environment, PsetError};
12
13#[derive(Debug, thiserror::Error)]
14pub enum PsetRunError {
15	#[error(transparent)]
16	SharedError(#[from] PsetError),
17
18	#[error("invalid PSET: {0}")]
19	PsetDecode(elements::pset::ParseError),
20
21	#[error("invalid input index: {0}")]
22	InputIndexParse(std::num::ParseIntError),
23
24	#[error("invalid program: {0}")]
25	ProgramParse(simplicity::ParseError),
26
27	#[error("program does not have a redeem node")]
28	NoRedeemNode,
29
30	#[error("failed to construct bit machine: {0}")]
31	BitMachineConstruction(simplicity::bit_machine::LimitError),
32}
33
34#[derive(Serialize)]
35pub struct JetCall {
36	pub jet: String,
37	pub source_ty: String,
38	pub target_ty: String,
39	pub success: bool,
40	pub input_value: String,
41	pub output_value: String,
42	#[serde(skip_serializing_if = "Option::is_none")]
43	pub equality_check: Option<(String, String)>,
44}
45
46#[derive(Serialize)]
47pub struct RunResponse {
48	pub success: bool,
49	pub jets: Vec<JetCall>,
50}
51
52struct JetTracker(Vec<JetCall>);
53
54impl<J: jet::Jet> ExecTracker<J> for JetTracker {
55	fn visit_node(
56		&mut self,
57		node: &simplicity::RedeemNode<J>,
58		mut input: FrameIter,
59		output: NodeOutput,
60	) {
61		if let node::Inner::Jet(jet) = node.inner() {
62			let input_value = Value::from_padded_bits(&mut input, &node.arrow().source)
63				.expect("valid value from bit machine");
64
65			let (success, output_value) = match output {
66				NodeOutput::NonTerminal => unreachable!(),
67				NodeOutput::JetFailed => (false, Value::unit()),
68				NodeOutput::Success(mut iter) => (
69					true,
70					Value::from_padded_bits(&mut iter, &node.arrow().target)
71						.expect("valid value from bit machine"),
72				),
73			};
74
75			let jet_name = jet.to_string();
76			let equality_check = if jet_name.strip_prefix("eq_").is_some() {
77				let (left, right) = input_value.as_product().unwrap();
78				Some((left.to_value().to_string(), right.to_value().to_string()))
79			} else {
80				None
81			};
82
83			self.0.push(JetCall {
84				jet: jet_name,
85				source_ty: jet.source_ty().to_final().to_string(),
86				target_ty: jet.target_ty().to_final().to_string(),
87				success,
88				input_value: input_value.to_string(),
89				output_value: output_value.to_string(),
90				equality_check,
91			});
92		}
93	}
94}
95
96/// Run a Simplicity program in the context of a PSET input
97pub fn pset_run(
98	pset_b64: &str,
99	input_idx: &str,
100	program: &str,
101	witness: &str,
102	genesis_hash: Option<&str>,
103) -> Result<RunResponse, PsetRunError> {
104	// 1. Parse everything.
105	let pset: elements::pset::PartiallySignedTransaction =
106		pset_b64.parse().map_err(PsetRunError::PsetDecode)?;
107	let input_idx: u32 = input_idx.parse().map_err(PsetRunError::InputIndexParse)?;
108	let input_idx_usize = input_idx as usize; // 32->usize cast ok on almost all systems
109
110	let program = Program::<jet::Elements>::from_str(program, Some(witness))
111		.map_err(PsetRunError::ProgramParse)?;
112
113	// 2. Extract transaction environment.
114	let (tx_env, _control_block, _tap_leaf) =
115		execution_environment(&pset, input_idx_usize, program.cmr(), genesis_hash)?;
116
117	// 3. Prune program.
118	let redeem_node = program.redeem_node().ok_or(PsetRunError::NoRedeemNode)?;
119
120	let mut mac =
121		BitMachine::for_program(redeem_node).map_err(PsetRunError::BitMachineConstruction)?;
122	let mut tracker = JetTracker(vec![]);
123	// Eat success/failure. FIXME should probably report this to the user.
124	let success = mac.exec_with_tracker(redeem_node, &tx_env, &mut tracker).is_ok();
125	Ok(RunResponse {
126		success,
127		jets: tracker.0,
128	})
129}