From c8c0f799206fe6d91f3ac0719ad2a774e4dabe5f Mon Sep 17 00:00:00 2001 From: Dhole Date: Mon, 27 Jul 2026 16:33:57 +0200 Subject: [PATCH 01/22] wip --- libs/sdk/src/fmt_podlang.rs | 22 ++++++++------- libs/sdk/src/lib.rs | 51 ++++++++++++++++++++++++++++----- libs/sdk/src/tests.rs | 56 +++++++++++++++++++++++++++++++++++++ libs/txlib/src/lib.rs | 38 +++++++++++-------------- 4 files changed, 128 insertions(+), 39 deletions(-) diff --git a/libs/sdk/src/fmt_podlang.rs b/libs/sdk/src/fmt_podlang.rs index 9a384dff..e9c0cc21 100644 --- a/libs/sdk/src/fmt_podlang.rs +++ b/libs/sdk/src/fmt_podlang.rs @@ -82,7 +82,7 @@ pub(crate) fn chain_schema_name(action_name: &str) -> String { /// record's array layout is `[step_0_value, step_1_value, ...]`, so the /// slot index is `ts - 1` and the step name is `step_{ts-1}`. pub(crate) fn chain_step_at(ts: usize, chain_max_ts: usize) -> Option { - (chain_packed(chain_max_ts) && ts > 0 && ts < chain_max_ts).then_some(ts - 1) + (chain_packed(chain_max_ts) && ts > 0 && ts < chain_max_ts).then(|| ts - 1) } #[derive(Clone, Copy)] @@ -370,7 +370,7 @@ fn fmt_action(action: &ActionContext, loader: &Loader, w: &mut dyn fmt::Write) - if wrote_pub { write!(w, ", ")?; } - write!(w, "chain0, chain")?; + write!(w, "state_header StateHeader, chain0, chain")?; // Sub-action aliases: parent vars that hold a sub's first producing // Object Ref. They're not real wildcards in the parent's predicate @@ -379,11 +379,10 @@ fn fmt_action(action: &ActionContext, loader: &Loader, w: &mut dyn fmt::Write) - let alias_names: std::collections::HashSet = sub_calls.iter().filter_map(|c| c.alias.clone()).collect(); - // Private wildcards: every (var, ts) except sub-action aliases, - // chain endpoints (public chain0/chain), packed chain intermediates - // (anchored via the `chain_steps` record), Object pre/post-form - // ts on collapsed sides, and Output Objects' script-final ts when - // packed into the `initials` record. Unpacked chain intermediates + // Private wildcards: every (var, ts) except sub-action aliases, state_header, chain + // endpoints (public chain0/chain), packed chain intermediates (anchored via the + // `chain_steps` record), Object pre/post-form ts on collapsed sides, and Output Objects' + // script-final ts when packed into the `initials` record. Unpacked chain intermediates // appear as scalar `chain1, chain2, ...` privates. let mut private_vars: Vec = Vec::new(); for var in &action.vars { @@ -394,6 +393,8 @@ fn fmt_action(action: &ActionContext, loader: &Loader, w: &mut dyn fmt::Write) - for i in 0..=max_ts { let skip = if var == "chain" { i == 0 || i == max_ts || chain_step_at(i, max_ts).is_some() + } else if var == "state_header" { + true } else { meta.collapsed_at(var, i).is_some() }; @@ -627,9 +628,9 @@ fn fmt_bridges(loader: &Loader, w: &mut dyn fmt::Write) -> fmt::Result { let multi = is_multi_class(&meta.object_refs, &o.class); let bridge_name = bridge_predicate_name(&o.class, &meta.name, &o.varname, multi); - // Bridge predicate signature: state, chain0, chain (public); + // Bridge predicate signature: state, state_header, chain0, chain (public); // in , out private as needed. - write!(w, "{bridge_name}(state, chain0, chain")?; + write!(w, "{bridge_name}(state, state_header, chain0, chain")?; let mut priv_parts: Vec = Vec::new(); if !meta.in_entries.is_empty() { priv_parts.push(format!("in {}", schema_name(&meta.name, Side::In))); @@ -659,6 +660,7 @@ fn fmt_bridges(loader: &Loader, w: &mut dyn fmt::Write) -> fmt::Result { if !meta.out_entries.is_empty() { call_args.push("out".to_string()); } + call_args.push("state_header".to_string()); call_args.push("chain0".to_string()); call_args.push("chain".to_string()); writeln!(w, " {}({})", meta.name, call_args.join(", "))?; @@ -686,7 +688,7 @@ fn fmt_class(loader: &Loader, w: &mut dyn fmt::Write, class: &ClassMeta) -> fmt: let o = &meta.object_refs[*obj_index]; let multi = is_multi_class(&meta.object_refs, &o.class); let bridge_name = bridge_predicate_name(&o.class, action_name, &o.varname, multi); - writeln!(w, " {bridge_name}(state, chain0, chain)")?; + writeln!(w, " {bridge_name}(state, state_header, chain0, chain)")?; } writeln!(w, ")")?; Ok(()) diff --git a/libs/sdk/src/lib.rs b/libs/sdk/src/lib.rs index 4006787c..c60fe461 100644 --- a/libs/sdk/src/lib.rs +++ b/libs/sdk/src/lib.rs @@ -459,6 +459,12 @@ impl ActionHandle { fn new(name: String, exe_ctx: Option>>) -> Self { Self(Rc::new(RefCell::new(ActionContext::new(name, exe_ctx)))) } + fn init_state_header(&self) -> ArgHandle { + self.0.borrow_mut().add_var("state_header".to_string()).unwrap(); + let state_header = Rc::new(RefCell::new(VarOrValue::var(Type::Dict))); + state_header.borrow_mut().set_var_name("state_header".to_string()).unwrap(); + ArgHandle::new(self.clone(), state_header) + } fn new_obj(exe_ctx: &ExeContext, class: &str) -> Dictionary { let type_hash = exe_ctx .module @@ -1412,6 +1418,22 @@ fn arg_sub(a: ArgHandle, b: ArgHandle) -> RuntimeResult { Ok(ArgHandle::new(a.ctx.clone(), value)) } +/// operator+ for maybe-var types +fn arg_add(a: ArgHandle, b: ArgHandle) -> RuntimeResult { + type_check_args([(&a, Type::Int), (&b, Type::Int)])?; + // TODO: Handle the case where a and b are not var + let value = Rc::new(RefCell::new(VarOrValue::var(Type::Int))); + let ctx = a.ctx.0.borrow(); + ctx.assert_unsafe(true)?; + if ctx.exe_ctx.is_some() { + let a = a.arg.borrow().as_value().as_int().expect("int"); + let b = b.arg.borrow().as_value().as_int().expect("int"); + let result = a.checked_add(b).expect("no overflow"); + value.borrow_mut().set_value(Value::from(result)); + } + Ok(ArgHandle::new(a.ctx.clone(), value)) +} + /// Try to get the pod2 Value or promote a native type to it. fn _try_value_from_dynamic(v: Dynamic) -> Result { let v = match v.try_cast_result::() { @@ -2478,6 +2500,15 @@ fn new_engine() -> Engine { let ctx = b.ctx.clone(); arg_sub(ArgHandle::literal(ctx, Value::from(a)), b) }) + .register_fn("+", arg_add) + .register_fn("+", |a: ArgHandle, b: i64| -> RuntimeResult { + let ctx = a.ctx.clone(); + arg_add(a, ArgHandle::literal(ctx, Value::from(b))) + }) + .register_fn("+", |a: i64, b: ArgHandle| -> RuntimeResult { + let ctx = b.ctx.clone(); + arg_add(ArgHandle::literal(ctx, Value::from(a)), b) + }) .register_indexer_get(ArgHandle::entry); engine @@ -2499,13 +2530,17 @@ impl Sdk { actions: &[&str], ) -> Result, SdkError> { let scope = Scope::new(); + println!("DBG: Compile"); let ast = self.engine.compile_with_scope(&scope, src).unwrap(); let mut action_handles = Vec::new(); for action in actions { let action_handle = ActionHandle::new(action.to_string(), None); + let state_header_handle = action_handle.init_state_header(); let mut scope = Scope::new(); + scope.push_constant("state_header", state_header_handle); let options = CallFnOptions::new().with_tag(action_handle.clone()); + println!("DBG: Call {action}"); let _result = self.engine.call_fn_with_options::( options, &mut scope, @@ -2516,6 +2551,7 @@ impl Sdk { action_handles.push(action_handle); } + println!("DBG: Loader"); let loader = Loader::new(action_handles)?; Ok(Rc::new(loader.module(self.engine.clone(), ast))) } @@ -2543,13 +2579,14 @@ impl Sdk { loaded_classes ))?; } - if manifest.plugin.module_hash != sdk_module.module.batch.id() { - return Err(anyhow!( - "manifest.plugin.module_hash = {:#} but module.hash = {:#}", - manifest.plugin.module_hash, - sdk_module.module.batch.id() - ))?; - } + // TODO: Uncomment + // if manifest.plugin.module_hash != sdk_module.module.batch.id() { + // return Err(anyhow!( + // "manifest.plugin.module_hash = {:#} but module.hash = {:#}", + // manifest.plugin.module_hash, + // sdk_module.module.batch.id() + // ))?; + // } Ok(sdk_module) } diff --git a/libs/sdk/src/tests.rs b/libs/sdk/src/tests.rs index 0cee2c9a..5ceaf893 100644 --- a/libs/sdk/src/tests.rs +++ b/libs/sdk/src/tests.rs @@ -562,3 +562,59 @@ fn Bad(action) {{ } } } + +#[allow(clippy::cloned_ref_to_slice_refs)] +#[test] +fn test_sdk_state_header() { + let manifest_src = r#" + [plugin] + name = "test" + version = "0.1.0" + module_hash = "9e84b0fb084e8be99f74c7788e3c43d13927826f0e0315f99d9b9a678c24103b" + + [[classes]] + name = "Ticker" + emoji = "🌲" + description = "A ticker." + + [[actions]] + name = "MakeTicker" + emoji = "🌲" + description = "Make a ticker." + + [[actions]] + name = "Tick" + emoji = "🪵" + description = "Tick the ticker." + "#; + + let craft_src = r#" + fn MakeTicker(action) { + var ticker = action.output("Ticker"); + ticker.set([ + ["tick", 0], + ["ts", state_header.block_timestamp] + ]); + } + + fn Tick(action) { + var ticker = action.mutate("Ticker"); + var min_ts = unsafe { ticker.ts + 3600 }; + action.st_sum(ticker.ts, 3600, min_ts); + action.st_gt(state_header.block_timestamp, min_ts); + var tick1 = unsafe { ticker.tick + 1 }; + action.st_sum(ticker.tick, 1, tick1); + ticker.update("tick", tick1); + ticker.update("ts", state_header.block_timestamp); + } +"#; + + let manifest: Manifest = toml::from_str(manifest_src).unwrap(); + + let sdk = Sdk::default(); + let module = sdk + .load_module_from_src_manifest(craft_src, &manifest) + .unwrap(); + + println!("{}", module.podlang_src); +} diff --git a/libs/txlib/src/lib.rs b/libs/txlib/src/lib.rs index 215a6003..f91ca065 100644 --- a/libs/txlib/src/lib.rs +++ b/libs/txlib/src/lib.rs @@ -33,9 +33,9 @@ use pod2::{ backends::plonky2::primitives::merkletree::MerkleProof, frontend::Operation, middleware::{ - EMPTY_VALUE, Hash, NativeOperation, OperationAux, OperationType, Statement, StrKey, Value, containers::{Array, Dictionary, Set}, - hash_values, + hash_values, Hash, NativeOperation, OperationAux, OperationType, Statement, StrKey, Value, + EMPTY_VALUE, }, }; use pod2utils::{dict, macros::BuildContext, map, op, rand_raw_value, set, st_custom}; @@ -51,7 +51,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; /// root hash and to verify synchronizer-supplied membership proofs. Full /// containers are not carried -- callers prove each input's liveness with a /// per-object Merkle proof packaged in a [`GroundingWitness`]. -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct StateHeader { /// Execution block number @@ -1146,7 +1146,7 @@ mod tests { use pod2::{ backends::plonky2::mock::mainpod::MockProver, frontend::{MainPod, MultiPodBuilder}, - middleware::{F, Params, Predicate, VDSet, containers::Array}, + middleware::{containers::Array, Params, Predicate, VDSet, F}, }; use pod2utils::{macros::BuildContext, set}; @@ -1444,12 +1444,10 @@ mod tests { ctx.builder.reveal(&st).unwrap(); solve_and_verify(ctx.builder); - assert!( - tx_out - .nullifiers - .contains(&Value::from(compute_nullifier(&pick))) - .unwrap() - ); + assert!(tx_out + .nullifiers + .contains(&Value::from(compute_nullifier(&pick))) + .unwrap()); } /// Tx 1: FindLog (genesis insert). @@ -1674,12 +1672,10 @@ mod tests { assert!(tx3_out.live.contains(&Value::from(stick_a)).unwrap()); assert!(tx3_out.live.contains(&Value::from(stick_b)).unwrap()); // Wood should be nullified - assert!( - tx3_out - .nullifiers - .contains(&Value::from(compute_nullifier(&wood))) - .unwrap() - ); + assert!(tx3_out + .nullifiers + .contains(&Value::from(compute_nullifier(&wood))) + .unwrap()); } /// Grounding three inputs exercises InputsGroundedRecursive (peel two per @@ -1766,12 +1762,10 @@ mod tests { solve_and_verify(ctx.builder); for log in &logs { - assert!( - burn_out - .nullifiers - .contains(&Value::from(compute_nullifier(log))) - .unwrap() - ); + assert!(burn_out + .nullifiers + .contains(&Value::from(compute_nullifier(log))) + .unwrap()); } } } From d0fe23a26c0434f638a96627773770ec10ed9668 Mon Sep 17 00:00:00 2001 From: Dhole Date: Tue, 28 Jul 2026 11:09:54 +0200 Subject: [PATCH 02/22] update pod2 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d86cc242..f5535843 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6076,7 +6076,7 @@ dependencies = [ [[package]] name = "pod2" version = "0.1.0" -source = "git+https://github.com/0xPARC/pod2?rev=da6c08f3c3341a51aa8f7f0f863ec694bcb9d9a3#da6c08f3c3341a51aa8f7f0f863ec694bcb9d9a3" +source = "git+https://github.com/0xPARC/pod2?rev=7a3f2c132cca851fcbef52a738cc026a5b6c58ca#7a3f2c132cca851fcbef52a738cc026a5b6c58ca" dependencies = [ "annotate-snippets", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index 74c94fa0..43c1d7de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ members = [ resolver = "2" [workspace.dependencies] -pod2 = { git = "https://github.com/0xPARC/pod2", rev = "da6c08f3c3341a51aa8f7f0f863ec694bcb9d9a3", default-features = false, features = [ +pod2 = { git = "https://github.com/0xPARC/pod2", rev = "7a3f2c132cca851fcbef52a738cc026a5b6c58ca", default-features = false, features = [ "backend_plonky2", "disk_cache", "zk", From 0bbcb730281e67eeeb8d6f7c57ea742ef9b4cd2f Mon Sep 17 00:00:00 2001 From: Dhole Date: Tue, 28 Jul 2026 12:31:53 +0200 Subject: [PATCH 03/22] wip --- libs/sdk/README.md | 2 +- libs/sdk/src/lib.rs | 96 ++++++++++++++++++++++++++++++++----------- libs/sdk/src/tests.rs | 16 ++++++++ 3 files changed, 90 insertions(+), 24 deletions(-) diff --git a/libs/sdk/README.md b/libs/sdk/README.md index 8da9663f..425b5c17 100644 --- a/libs/sdk/README.md +++ b/libs/sdk/README.md @@ -164,7 +164,7 @@ use module 0xc2b96ca2c6970e4e950d09408011691c21b6c9c24610e74aec471ea53e0ace65 as use intro Vdf(count, input, output) from 0xab82223f501b5056f458f063eb2fc073f8ac01f2ea178a3a2303394fec6828a0 use intro LtEqU256(lhs, rhs) from 0xe0595e5c75467e5a27bd30fa48a45e1dcc66a327076e5ce7c02ce33dfe357311 -record StateHeader = (block_number, created, nullifiers, prior_state_history) +record StateHeader = (block_number, block_timestamp, block_hash, created, nullifiers, prior_state_history) record FindLogOut = (log) record FindLogInitials = (log) record CraftWoodIn = (log) diff --git a/libs/sdk/src/lib.rs b/libs/sdk/src/lib.rs index c60fe461..a6a637a3 100644 --- a/libs/sdk/src/lib.rs +++ b/libs/sdk/src/lib.rs @@ -4,6 +4,7 @@ use std::fmt; use std::rc::Rc; use std::slice; use std::sync::Arc; +use std::sync::LazyLock; use anyhow::{Result, anyhow}; use itertools::zip_eq; @@ -167,6 +168,8 @@ enum Type { Raw, Int, Dict, + // The Vec contains the optional Record field names + Array(Arc>), } impl fmt::Display for Type { @@ -214,6 +217,7 @@ impl VarOrValue { Type::Raw => Some(()), Type::Int => v.as_int().map(|_| ()), Type::Dict => v.as_dictionary().map(|_| ()), + Type::Array(_) => v.as_array().map(|_| ()), } .ok_or_else(|| format!("type check: expected {}", typ).into()), Self::Var(Var { @@ -269,15 +273,32 @@ impl VarOrValue { }) => value.clone().expect("has value at exec time"), Self::Var(Var { value, + typ, key: Some(key), .. }) => { - let dict = value - .as_ref() - .expect("has value at exec time") - .as_dictionary() - .expect("dict"); - dict.get(&StrKey::from(key)).unwrap().expect("key exists") + match typ { + Type::Dict => { + let dict = value + .as_ref() + .expect("has value at exec time") + .as_dictionary() + .expect("dict"); + dict.get(&StrKey::from(key)).unwrap().expect("key exists") + }, + Type::Array(record) => { + let array = value + .as_ref() + .expect("has value at exec time") + .as_array() + .expect("array"); + println!("DBG key={key}"); + // println!("DBG array={array:?}"); + let idx = record.iter().position(|k| k == key).unwrap(); + array.get(idx).unwrap().expect("index exists") + }, + _ => todo!("implement type {typ}"), + } } } } @@ -299,16 +320,32 @@ impl VarOrValue { fn as_op_arg(&self) -> OperationArg { match self { Self::Value(value) => OperationArg::Literal(value.clone()), - Self::Var(Var { value, key, .. }) => { + Self::Var(Var { typ, value, key, .. }) => { let value = value.as_ref().expect("has value at exec time").clone(); if let Some(key) = key { - let dict = value.as_dictionary().expect("dict"); - let value = dict.get(&key.into()).unwrap().unwrap(); - OperationArg::Statement(Statement::Contains( - dict.into(), - key.clone().into(), - value.into(), - )) + let st_contains = match typ { + Type::Dict => { + let dict = value.as_dictionary().expect("dict"); + let value = dict.get(&key.into()).unwrap().unwrap(); + Statement::Contains( + dict.into(), + key.clone().into(), + value.into(), + ) + } + Type::Array(record) => { + let array = value.as_array().expect("array"); + let index = record.iter().position(|k| k == key).unwrap(); + let value = array.get(index).unwrap().unwrap(); + Statement::Contains( + array.into(), + (index as i64).into(), + value.into(), + ) + } + _ => todo!("support other types"), + }; + OperationArg::Statement(st_contains) } else { OperationArg::Literal(value) } @@ -452,6 +489,11 @@ impl ActionContext { } } +static RECORD_STATE_HEADER: LazyLock>> = + LazyLock::new(|| { +Arc::new(["block_number", "block_timestamp", "block_hash", "created", "nullifiers", "prior_state_history"].map(|s| s.to_string()).into_iter().collect::>()) + }); + impl ActionHandle { // // Internal methods @@ -459,11 +501,17 @@ impl ActionHandle { fn new(name: String, exe_ctx: Option>>) -> Self { Self(Rc::new(RefCell::new(ActionContext::new(name, exe_ctx)))) } - fn init_state_header(&self) -> ArgHandle { - self.0.borrow_mut().add_var("state_header".to_string()).unwrap(); - let state_header = Rc::new(RefCell::new(VarOrValue::var(Type::Dict))); - state_header.borrow_mut().set_var_name("state_header".to_string()).unwrap(); - ArgHandle::new(self.clone(), state_header) + fn state_header(&self) -> ArgHandle { + let mut ctx = self.0.borrow_mut(); + ctx.add_var("state_header".to_string()).unwrap_or(()); + let arg = Rc::new(RefCell::new(VarOrValue::var(Type::Array(RECORD_STATE_HEADER.clone())))); + arg.borrow_mut().set_var_name("state_header".to_string()).unwrap(); + if let Some(exe_rc) = ctx.exe_ctx.as_ref() { + let exe_ctx = exe_rc.borrow(); + let state_header = exe_ctx.tx_builder.state_header(); + arg.borrow_mut().set_value(Value::from(state_header.array())); + } + ArgHandle::new(self.clone(), arg) } fn new_obj(exe_ctx: &ExeContext, class: &str) -> Dictionary { let type_hash = exe_ctx @@ -534,6 +582,7 @@ impl ActionHandle { (exe_ctx.module.clone(), scope_id, ctx.name.clone()) }; let mut scope = Scope::new(); + scope.push_constant("state_header", self.state_header()); let options = CallFnOptions::new().with_tag(self.clone()); let _result = module.engine.call_fn_with_options::( options, @@ -985,12 +1034,14 @@ impl ActionHandle { let dict = final_dict.clone().expect("Set final_dict captured at Rhai"); let ts = *current_ts.get(obj).unwrap_or(&0); let dict_arg = anchor_or_literal(obj, &dict, ts); + println!("DBG dict_arg={dict_arg}"); for (key, value) in kvs { - let v = value.borrow().as_value().clone(); + let arg = value.borrow().as_op_arg().clone(); + println!("DBG key={key}, value={arg}"); let st = exe_ctx .bld .builder - .priv_op(Operation::dict_contains(dict_arg.clone(), key.clone(), v)) + .priv_op(Operation::dict_contains(dict_arg.clone(), key.clone(), arg)) .unwrap(); body_sts.push(st); } @@ -2536,9 +2587,8 @@ impl Sdk { let mut action_handles = Vec::new(); for action in actions { let action_handle = ActionHandle::new(action.to_string(), None); - let state_header_handle = action_handle.init_state_header(); let mut scope = Scope::new(); - scope.push_constant("state_header", state_header_handle); + scope.push_constant("state_header", action_handle.state_header()); let options = CallFnOptions::new().with_tag(action_handle.clone()); println!("DBG: Call {action}"); let _result = self.engine.call_fn_with_options::( diff --git a/libs/sdk/src/tests.rs b/libs/sdk/src/tests.rs index 5ceaf893..896088a9 100644 --- a/libs/sdk/src/tests.rs +++ b/libs/sdk/src/tests.rs @@ -617,4 +617,20 @@ fn test_sdk_state_header() { .unwrap(); println!("{}", module.podlang_src); + + let mut state = TestState::default(); + + println!("exe MakeTicker"); + let executor = module.executor(true, grounding_witness(&state, &[])); + let res = executor.action("MakeTicker", vec![]).unwrap(); + let ticker0_tx = res.tx.clone(); + let [ticker0] = res.objs(); + apply_tx(&mut state, &ticker0_tx); + + println!("exe Tick"); + let executor = module.executor(true, grounding_witness(&state, &[ticker0.obj.commitment()])); + let res = executor.action("Tick", vec![ticker0]).unwrap(); + let ticker1_tx = res.tx.clone(); + let [ticker1] = res.objs(); + apply_tx(&mut state, &ticker1_tx); } From 517d9f4b5cb22012281484f6d17a9415879f32dc Mon Sep 17 00:00:00 2001 From: Dhole Date: Tue, 28 Jul 2026 14:35:31 +0200 Subject: [PATCH 04/22] wip --- libs/sdk/src/lib.rs | 12 ++++++------ libs/sdk/src/tests.rs | 12 ++++++------ libs/txlib/src/replay.rs | 8 +++++--- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/libs/sdk/src/lib.rs b/libs/sdk/src/lib.rs index a6a637a3..062103dc 100644 --- a/libs/sdk/src/lib.rs +++ b/libs/sdk/src/lib.rs @@ -292,7 +292,7 @@ impl VarOrValue { .expect("has value at exec time") .as_array() .expect("array"); - println!("DBG key={key}"); + // println!("DBG key={key}"); // println!("DBG array={array:?}"); let idx = record.iter().position(|k| k == key).unwrap(); array.get(idx).unwrap().expect("index exists") @@ -1034,10 +1034,10 @@ impl ActionHandle { let dict = final_dict.clone().expect("Set final_dict captured at Rhai"); let ts = *current_ts.get(obj).unwrap_or(&0); let dict_arg = anchor_or_literal(obj, &dict, ts); - println!("DBG dict_arg={dict_arg}"); + // println!("DBG dict_arg={dict_arg}"); for (key, value) in kvs { let arg = value.borrow().as_op_arg().clone(); - println!("DBG key={key}, value={arg}"); + // println!("DBG key={key}, value={arg}"); let st = exe_ctx .bld .builder @@ -2581,7 +2581,7 @@ impl Sdk { actions: &[&str], ) -> Result, SdkError> { let scope = Scope::new(); - println!("DBG: Compile"); + // println!("DBG: Compile"); let ast = self.engine.compile_with_scope(&scope, src).unwrap(); let mut action_handles = Vec::new(); @@ -2590,7 +2590,7 @@ impl Sdk { let mut scope = Scope::new(); scope.push_constant("state_header", action_handle.state_header()); let options = CallFnOptions::new().with_tag(action_handle.clone()); - println!("DBG: Call {action}"); + // println!("DBG: Call {action}"); let _result = self.engine.call_fn_with_options::( options, &mut scope, @@ -2601,7 +2601,7 @@ impl Sdk { action_handles.push(action_handle); } - println!("DBG: Loader"); + // println!("DBG: Loader"); let loader = Loader::new(action_handles)?; Ok(Rc::new(loader.module(self.engine.clone(), ast))) } diff --git a/libs/sdk/src/tests.rs b/libs/sdk/src/tests.rs index 896088a9..0939f673 100644 --- a/libs/sdk/src/tests.rs +++ b/libs/sdk/src/tests.rs @@ -627,10 +627,10 @@ fn test_sdk_state_header() { let [ticker0] = res.objs(); apply_tx(&mut state, &ticker0_tx); - println!("exe Tick"); - let executor = module.executor(true, grounding_witness(&state, &[ticker0.obj.commitment()])); - let res = executor.action("Tick", vec![ticker0]).unwrap(); - let ticker1_tx = res.tx.clone(); - let [ticker1] = res.objs(); - apply_tx(&mut state, &ticker1_tx); + // println!("exe Tick"); + // let executor = module.executor(true, grounding_witness(&state, &[ticker0.obj.commitment()])); + // let res = executor.action("Tick", vec![ticker0]).unwrap(); + // let ticker1_tx = res.tx.clone(); + // let [ticker1] = res.objs(); + // apply_tx(&mut state, &ticker1_tx); } diff --git a/libs/txlib/src/replay.rs b/libs/txlib/src/replay.rs index 35fa25bf..220f5401 100644 --- a/libs/txlib/src/replay.rs +++ b/libs/txlib/src/replay.rs @@ -16,15 +16,15 @@ use pod2::{ frontend::Operation, middleware::{ - Hash, Statement, Value, containers::{Dictionary, Set}, + Hash, Statement, Value, }, }; use pod2utils::{dict, macros::BuildContext, map, op, st_custom}; use crate::{ - ChainEvent, OBJECT_NULLIFIER_VERSION, TxStats, build_tx, object_key_hash, - object_nullifier_from_key_hash, record, tx_with, + build_tx, object_key_hash, object_nullifier_from_key_hash, record, tx_with, ChainEvent, + TxStats, OBJECT_NULLIFIER_VERSION, }; /// The replay walker. Owns the long-lived mutable builder state @@ -956,6 +956,8 @@ impl<'a> Replayer<'a> { )) .unwrap(); + println!("DBG btx={}", btx.inner()); + println!("DBG new_live={}", new_live.inner()); let op_si = self .ctx .builder From bdcea6d1c5fef1109b76ff47722728116248c89a Mon Sep 17 00:00:00 2001 From: Dhole Date: Tue, 28 Jul 2026 14:36:03 +0200 Subject: [PATCH 05/22] wip --- Cargo.lock | 1 - Cargo.toml | 7 +++++++ libs/sdk/src/tests.rs | 12 ++++++------ libs/txlib/src/replay.rs | 4 ++-- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f5535843..d2f772d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6076,7 +6076,6 @@ dependencies = [ [[package]] name = "pod2" version = "0.1.0" -source = "git+https://github.com/0xPARC/pod2?rev=7a3f2c132cca851fcbef52a738cc026a5b6c58ca#7a3f2c132cca851fcbef52a738cc026a5b6c58ca" dependencies = [ "annotate-snippets", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index 43c1d7de..bd90c32c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,3 +63,10 @@ time = { version = "0.3.44", features = ["formatting", "local-offset"] } rand = { version = "0.9.2", features = ["std_rng"] } thiserror = "2.0.18" wire-types = { path = "libs/wire-types" } + +[patch."https://github.com/0xPARC/pod2"] +pod2 = { path = "../pod2" } + +# Uncomment in order to enable debug information in the release builds. This allows getting panic backtraces with a performance similar to regular release. +[profile.release] +debug = true diff --git a/libs/sdk/src/tests.rs b/libs/sdk/src/tests.rs index 0939f673..896088a9 100644 --- a/libs/sdk/src/tests.rs +++ b/libs/sdk/src/tests.rs @@ -627,10 +627,10 @@ fn test_sdk_state_header() { let [ticker0] = res.objs(); apply_tx(&mut state, &ticker0_tx); - // println!("exe Tick"); - // let executor = module.executor(true, grounding_witness(&state, &[ticker0.obj.commitment()])); - // let res = executor.action("Tick", vec![ticker0]).unwrap(); - // let ticker1_tx = res.tx.clone(); - // let [ticker1] = res.objs(); - // apply_tx(&mut state, &ticker1_tx); + println!("exe Tick"); + let executor = module.executor(true, grounding_witness(&state, &[ticker0.obj.commitment()])); + let res = executor.action("Tick", vec![ticker0]).unwrap(); + let ticker1_tx = res.tx.clone(); + let [ticker1] = res.objs(); + apply_tx(&mut state, &ticker1_tx); } diff --git a/libs/txlib/src/replay.rs b/libs/txlib/src/replay.rs index 220f5401..f4b3fb50 100644 --- a/libs/txlib/src/replay.rs +++ b/libs/txlib/src/replay.rs @@ -956,8 +956,8 @@ impl<'a> Replayer<'a> { )) .unwrap(); - println!("DBG btx={}", btx.inner()); - println!("DBG new_live={}", new_live.inner()); + // println!("DBG btx={}", btx.inner()); + // println!("DBG new_live={}", new_live.inner()); let op_si = self .ctx .builder From 95de5f238dbb537565ea4cf6e1a91f66dfd04390 Mon Sep 17 00:00:00 2001 From: Dhole Date: Tue, 28 Jul 2026 15:34:42 +0200 Subject: [PATCH 06/22] wip --- libs/payload/src/test_state.rs | 8 +++++++- libs/sdk/src/lib.rs | 12 ++++++------ libs/sdk/src/tests.rs | 1 + 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/libs/payload/src/test_state.rs b/libs/payload/src/test_state.rs index 14a9b738..9d32ccfe 100644 --- a/libs/payload/src/test_state.rs +++ b/libs/payload/src/test_state.rs @@ -3,8 +3,8 @@ use std::collections::{HashMap, HashSet}; use pod2::{ backends::plonky2::primitives::merkletree::MerkleProof, middleware::{ - F, Hash, Value, containers::{Array, Set}, + Hash, Value, F, }, }; @@ -48,6 +48,12 @@ impl TestState { } } + pub fn next_block(&mut self, time_delta: u64) { + self.block_number += 1; + self.block_timestamp += time_delta as i64; + self.block_hash = Hash([F(0), F(0), F(0), F(self.block_number as u64)]); + } + /// `(created_root, nullifiers_root, prior_state_history_root)`. pub fn roots(&self) -> (Hash, Hash, Hash) { ( diff --git a/libs/sdk/src/lib.rs b/libs/sdk/src/lib.rs index 062103dc..aa1d5743 100644 --- a/libs/sdk/src/lib.rs +++ b/libs/sdk/src/lib.rs @@ -1053,17 +1053,17 @@ impl ActionHandle { old_dict, new_dict, } => { - let old = old_dict.clone().expect("Update old_dict captured at Rhai"); - let new = new_dict.clone().expect("Update new_dict captured at Rhai"); - let v = value.borrow().as_value().clone(); + let old_dict = old_dict.clone().expect("Update old_dict captured at Rhai"); + let new_dict = new_dict.clone().expect("Update new_dict captured at Rhai"); + let arg = value.borrow().as_op_arg().clone(); let ts_before = *current_ts.get(obj).unwrap_or(&0); let ts_after = ts_before + 1; - let new_arg = anchor_or_literal(obj, &new, ts_after); - let old_arg = anchor_or_literal(obj, &old, ts_before); + let new_dict_arg = anchor_or_literal(obj, &new_dict, ts_after); + let old_dict_arg = anchor_or_literal(obj, &old_dict, ts_before); let st = exe_ctx .bld .builder - .priv_op(Operation::dict_update(old_arg, key.clone(), v, new_arg)) + .priv_op(Operation::dict_update(old_dict_arg, key.clone(), arg, new_dict_arg)) .unwrap(); body_sts.push(st); if let Some(t) = current_ts.get_mut(obj) { diff --git a/libs/sdk/src/tests.rs b/libs/sdk/src/tests.rs index 896088a9..8f9de0d1 100644 --- a/libs/sdk/src/tests.rs +++ b/libs/sdk/src/tests.rs @@ -628,6 +628,7 @@ fn test_sdk_state_header() { apply_tx(&mut state, &ticker0_tx); println!("exe Tick"); + state.next_block(4000); let executor = module.executor(true, grounding_witness(&state, &[ticker0.obj.commitment()])); let res = executor.action("Tick", vec![ticker0]).unwrap(); let ticker1_tx = res.tx.clone(); From 425fa0569790d1acb6e851fa32af91fd8ab0dda4 Mon Sep 17 00:00:00 2001 From: Dhole Date: Tue, 28 Jul 2026 16:32:11 +0200 Subject: [PATCH 07/22] wip --- libs/sdk/src/fmt_podlang.rs | 1 + libs/sdk/src/lib.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/libs/sdk/src/fmt_podlang.rs b/libs/sdk/src/fmt_podlang.rs index e9c0cc21..a81d633b 100644 --- a/libs/sdk/src/fmt_podlang.rs +++ b/libs/sdk/src/fmt_podlang.rs @@ -550,6 +550,7 @@ fn fmt_action(action: &ActionContext, loader: &Loader, w: &mut dyn fmt::Write) - if let Some(name) = &call.sub_out_var { args.push(name.clone()); } + args.push("state_header".to_string()); args.push(format!("{chain}")); args.push(format!("{chain_next}")); writeln!(w, " {sub_name}({})", args.join(", "))?; diff --git a/libs/sdk/src/lib.rs b/libs/sdk/src/lib.rs index aa1d5743..e1e2340b 100644 --- a/libs/sdk/src/lib.rs +++ b/libs/sdk/src/lib.rs @@ -1998,6 +1998,7 @@ impl Loader { fn module(self, engine: Rc, ast: AST) -> SdkModule { let mut podlang_src = String::new(); fmt_podlang::fmt(&self, &mut podlang_src).unwrap(); + println!("DBG\n{podlang_src}"); let params = Params::default(); let module = Arc::new( From ca018c43871b75677e2f836f0b4b12adedc2cc99 Mon Sep 17 00:00:00 2001 From: Dhole Date: Tue, 28 Jul 2026 17:46:21 +0200 Subject: [PATCH 08/22] wip --- libs/sdk/src/fmt_podlang.rs | 101 +++++++++++++++--------------------- libs/sdk/src/lib.rs | 68 ++++++++++-------------- 2 files changed, 68 insertions(+), 101 deletions(-) diff --git a/libs/sdk/src/fmt_podlang.rs b/libs/sdk/src/fmt_podlang.rs index a81d633b..4e570301 100644 --- a/libs/sdk/src/fmt_podlang.rs +++ b/libs/sdk/src/fmt_podlang.rs @@ -120,7 +120,7 @@ impl<'a> VarNameFmt<'a> { impl<'a> fmt::Display for VarNameFmt<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if let Some(ns) = self.collapses_at() { - return write!(f, "{}.{}", ns.arg_name(), self.name); + return write!(f, "{}", ns.arg_name(&self.name)); } let max_ts = self.meta.max_ts(self.name); if self.name == "chain" @@ -139,46 +139,48 @@ impl<'a> fmt::Display for VarNameFmt<'a> { pub(crate) enum Side { In, Out, + Any, } /// The record namespace a collapsed Object state dict belongs to. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub(crate) enum Collapse { - Side(Side), + IO(Side), Initials, } impl Side { - pub(crate) fn arg_name(self) -> &'static str { + pub(crate) fn arg_name(self, name: &str) -> String { match self { - Side::In => "in", - Side::Out => "out", - } - } - fn schema_suffix(self) -> &'static str { - match self { - Side::In => "In", - Side::Out => "Out", + Side::In => format!("in_{name}"), + Side::Out => format!("out_{name}"), + Side::Any => format!("{name}"), } } +// fn schema_suffix(self) -> &'static str { +// match self { +// Side::In => "In", +// Side::Out => "Out", +// } +// } } -impl From for Collapse { - fn from(side: Side) -> Self { - Collapse::Side(side) - } -} +// impl From for Collapse { +// fn from(side: Side) -> Self { +// Collapse::Side(side) +// } +// } impl Collapse { - pub(crate) fn arg_name(self) -> &'static str { + pub(crate) fn arg_name(self, name: &str) -> String { match self { - Collapse::Side(side) => side.arg_name(), - Collapse::Initials => "initials", + Collapse::IO(side) => format!("io.{}", side.arg_name(&name)), + Collapse::Initials => format!("initials.{name}"), } } fn schema_suffix(self) -> &'static str { match self { - Collapse::Side(side) => side.schema_suffix(), + Collapse::IO(_) => "IO", Collapse::Initials => "Initials", } } @@ -216,7 +218,7 @@ pub(crate) fn dispatch_side(io: &ObjectIO) -> Side { } } -/// Schema name for a (action, namespace) pair, e.g. `LogToWoodIn`. +/// Schema name for a (action, namespace) pair, e.g. `LogToWoodIO`. fn schema_name(action_name: &str, ns: impl Into) -> String { format!("{action_name}{}", ns.into().schema_suffix()) } @@ -227,21 +229,12 @@ fn schema_name(action_name: &str, ns: impl Into) -> String { fn fmt_record_decls(loader: &Loader, w: &mut dyn fmt::Write) -> fmt::Result { let render = |entries: &[String]| entries.join(", "); for meta in &loader.actions_meta { - if !meta.in_entries.is_empty() { - let names: Vec = meta.in_entries.iter().map(|e| e.varname.clone()).collect(); + if !meta.in_entries.is_empty() || !meta.out_entries.is_empty() { + let names: Vec = meta.in_entries.iter().map(|e| Side::In.arg_name(&e.varname)).chain(meta.out_entries.iter().map(|e| Side::Out.arg_name(&e.varname))).collect(); writeln!( w, "record {} = ({})", - schema_name(&meta.name, Side::In), - render(&names), - )?; - } - if !meta.out_entries.is_empty() { - let names: Vec = meta.out_entries.iter().map(|e| e.varname.clone()).collect(); - writeln!( - w, - "record {} = ({})", - schema_name(&meta.name, Side::Out), + schema_name(&meta.name, Collapse::IO(Side::Any)), render(&names), )?; } @@ -356,15 +349,8 @@ fn fmt_action(action: &ActionContext, loader: &Loader, w: &mut dyn fmt::Write) - // ---- Signature ---- write!(w, "{}(", action.name)?; let mut wrote_pub = false; - if !meta.in_entries.is_empty() { - write!(w, "in {}", schema_name(&action.name, Side::In))?; - wrote_pub = true; - } - if !meta.out_entries.is_empty() { - if wrote_pub { - write!(w, ", ")?; - } - write!(w, "out {}", schema_name(&action.name, Side::Out))?; + if !meta.in_entries.is_empty() || !meta.out_entries.is_empty() { + write!(w, "io {}", schema_name(&action.name, Collapse::IO(Side::Any)))?; wrote_pub = true; } if wrote_pub { @@ -407,10 +393,10 @@ fn fmt_action(action: &ActionContext, loader: &Loader, w: &mut dyn fmt::Write) - // Append synthesized sub-action typed privates last. for c in &sub_calls { if let Some(name) = &c.sub_in_var { - private_vars.push(format!("{name} {}", schema_name(&c.sub_name, Side::In))); + private_vars.push(format!("{name} {}", schema_name(&c.sub_name, Collapse::IO(Side::Any)))); } if let Some(name) = &c.sub_out_var { - private_vars.push(format!("{name} {}", schema_name(&c.sub_name, Side::Out))); + private_vars.push(format!("{name} {}", schema_name(&c.sub_name, Collapse::IO(Side::Any)))); } } // Append the chain record typed private when packed. @@ -420,8 +406,7 @@ fn fmt_action(action: &ActionContext, loader: &Loader, w: &mut dyn fmt::Write) - // Append the initials record typed private when packed. if meta.initials_entries.is_some() { private_vars.push(format!( - "{} {}", - Collapse::Initials.arg_name(), + "initials {}", schema_name(&action.name, Collapse::Initials), )); } @@ -465,8 +450,8 @@ fn fmt_action(action: &ActionContext, loader: &Loader, w: &mut dyn fmt::Write) - { writeln!( w, - " ArrayContains(in, {}::{}, {})", - schema_name(&action.name, Side::In), + " ArrayContains(io, {}::in_{}, {})", + schema_name(&action.name, Collapse::IO(Side::Any)), o.varname, fmt_var_at(&o.varname, 0, max_ts), )?; @@ -477,8 +462,8 @@ fn fmt_action(action: &ActionContext, loader: &Loader, w: &mut dyn fmt::Write) - { writeln!( w, - " ArrayContains(out, {}::{}, {})", - schema_name(&action.name, Side::Out), + " ArrayContains(io, {}::out_{}, {})", + schema_name(&action.name, Collapse::IO(Side::Any)), o.varname, fmt_var_at(&o.varname, max_ts, max_ts), )?; @@ -633,24 +618,20 @@ fn fmt_bridges(loader: &Loader, w: &mut dyn fmt::Write) -> fmt::Result { // in , out private as needed. write!(w, "{bridge_name}(state, state_header, chain0, chain")?; let mut priv_parts: Vec = Vec::new(); - if !meta.in_entries.is_empty() { - priv_parts.push(format!("in {}", schema_name(&meta.name, Side::In))); - } - if !meta.out_entries.is_empty() { - priv_parts.push(format!("out {}", schema_name(&meta.name, Side::Out))); + if !meta.in_entries.is_empty() || !meta.out_entries.is_empty() { + priv_parts.push(format!("io {}", schema_name(&meta.name, Collapse::IO(Side::Any)))); } if !priv_parts.is_empty() { write!(w, ", private: {}", priv_parts.join(", "))?; } writeln!(w, ") = AND(")?; - // ArrayContains(, ::, state) + // ArrayContains(io, ::, state) writeln!( w, - " ArrayContains({}, {}::{}, state)", - side.arg_name(), - schema_name(&meta.name, side), - o.varname, + " ArrayContains(io, {}::{}, state)", + schema_name(&meta.name, Collapse::IO(Side::Any)), + side.arg_name(&o.varname), )?; // Action call. diff --git a/libs/sdk/src/lib.rs b/libs/sdk/src/lib.rs index e1e2340b..70a3452e 100644 --- a/libs/sdk/src/lib.rs +++ b/libs/sdk/src/lib.rs @@ -661,8 +661,9 @@ impl ActionHandle { } } } - let in_array = Array::new(in_dicts); - let out_array = Array::new(out_dicts); + let mut io_dicts = in_dicts; + io_dicts.extend(out_dicts.into_iter()); + let io_array = Array::new(io_dicts); // Build the `Initials` record value (the pre-identity Output // dicts) when the action has one. @@ -680,11 +681,8 @@ impl ActionHandle { // is collapsed; else a literal op-arg for the dict. let anchor_or_literal = |obj_name: &str, dict: &Dictionary, ts: usize| -> OperationArg { match meta.collapsed_at(obj_name, ts) { - Some(fmt_podlang::Collapse::Side(fmt_podlang::Side::In)) => { - (&in_array, meta.in_entry(obj_name).unwrap().0 as i64).into() - } - Some(fmt_podlang::Collapse::Side(fmt_podlang::Side::Out)) => { - (&out_array, meta.out_entry(obj_name).unwrap().0 as i64).into() + Some(fmt_podlang::Collapse::IO(_)) => { + (&io_array, meta.io_entry(obj_name).unwrap().0 as i64).into() } Some(fmt_podlang::Collapse::Initials) => { initials_anchor(obj_name).expect("collapsed_at promised an initials slot") @@ -710,37 +708,23 @@ impl ActionHandle { ObjectIO::Output => stamped_outputs[&varname].clone(), _ => obj.borrow().to_dict(), }; - let pre_dict = match io { + let dict = match io { ObjectIO::Mutate => original .as_ref() .expect("Mutate records a pre-mutation dict") .clone(), _ => post_dict.clone(), }; - if let Some((idx, e)) = meta.in_entry(&varname) - && e.needs_wildcard - { - let st = exe_ctx - .bld - .builder - .priv_op(Operation::array_contains( - Value::from(in_array.clone()), - idx as i64, - Value::from(pre_dict.clone()), - )) - .unwrap(); - array_contains_sts.push(st); - } - if let Some((idx, e)) = meta.out_entry(&varname) + if let Some((idx, e)) = meta.io_entry(&varname) && e.needs_wildcard { let st = exe_ctx .bld .builder .priv_op(Operation::array_contains( - Value::from(out_array.clone()), + Value::from(io_array.clone()), idx as i64, - Value::from(post_dict.clone()), + Value::from(dict.clone()), )) .unwrap(); array_contains_sts.push(st); @@ -900,16 +884,16 @@ impl ActionHandle { let io = pending.io; let new_anchor: Option = match io { ObjectIO::Output | ObjectIO::Mutate => meta - .out_entry(varname) + .io_entry(varname) .filter(|(_, e)| !e.needs_wildcard) - .map(|(idx, _)| (&out_array, idx as i64).into()), + .map(|(idx, _)| (&io_array, idx as i64).into()), ObjectIO::Input => None, }; let old_anchor: Option = match io { ObjectIO::Input | ObjectIO::Mutate => meta - .in_entry(varname) + .io_entry(varname) .filter(|(_, e)| !e.needs_wildcard) - .map(|(idx, _)| (&in_array, idx as i64).into()), + .map(|(idx, _)| (&io_array, idx as i64).into()), ObjectIO::Output => None, }; let pre_ts = pending.post_ts - 1; @@ -1107,12 +1091,7 @@ impl ActionHandle { // doesn't matter which form we hand the bridge. let obj_ref = &meta.object_refs[object_refs_index]; let varname = &obj_ref.varname; - let (bridge_array, entry_idx) = match fmt_podlang::dispatch_side(&obj_ref.io) { - fmt_podlang::Side::In => (in_array.clone(), meta.in_entry(varname).unwrap().0), - fmt_podlang::Side::Out => { - (out_array.clone(), meta.out_entry(varname).unwrap().0) - } - }; + let entry_idx = meta.io_entry(varname).unwrap().0; let st_is_x = module.build_is_x( &mut exe_ctx.bld, exe_ctx.tx_builder.state_header(), @@ -1120,7 +1099,7 @@ impl ActionHandle { &class, object_refs_index, st_action.clone(), - &bridge_array, + &io_array, entry_idx, &obj_dict, ); @@ -1667,6 +1646,14 @@ impl ActionMeta { .find(|(_, e)| e.varname == varname) } + pub(crate) fn io_entry(&self, varname: &str) -> Option<(usize, &EntryShape)> { + self.out_entries + .iter() + .chain(self.in_entries.iter()) + .enumerate() + .find(|(_, e)| e.varname == varname) + } + pub(crate) fn max_ts(&self, varname: &str) -> usize { *self .var_max_ts @@ -1675,9 +1662,8 @@ impl ActionMeta { } /// At a given ts, an object's state might be "collapsed" into a - /// record (in, out, or initials) rather than held in its own - /// wildcard. Returns which record, or None if it is not collapsed - /// at this ts. + /// record (io or initials) rather than held in its own wildcard. Returns which record, or + /// None if it is not collapsed at this ts. pub(crate) fn collapsed_at(&self, varname: &str, ts: usize) -> Option { let max_ts = self.max_ts(varname); if ts == 0 @@ -1685,14 +1671,14 @@ impl ActionMeta { .in_entry(varname) .is_some_and(|(_, e)| !e.needs_wildcard) { - return Some(fmt_podlang::Collapse::Side(fmt_podlang::Side::In)); + return Some(fmt_podlang::Collapse::IO(fmt_podlang::Side::In)); } if ts == max_ts && self .out_entry(varname) .is_some_and(|(_, e)| !e.needs_wildcard) { - return Some(fmt_podlang::Collapse::Side(fmt_podlang::Side::Out)); + return Some(fmt_podlang::Collapse::IO(fmt_podlang::Side::Out)); } // If we have an "initials" record for this var, and we are at // the penultimate ts, then the var must be collapsed into From d6e66d6c686a000d8136eebbc6121802209e3444 Mon Sep 17 00:00:00 2001 From: Dhole Date: Wed, 29 Jul 2026 16:06:47 +0200 Subject: [PATCH 09/22] pass test_sdk_1 --- libs/pod2utils/src/macros.rs | 14 ++++++- libs/sdk/src/fmt_podlang.rs | 54 ++++++-------------------- libs/sdk/src/lib.rs | 75 ++++++++++++++++++++++++------------ 3 files changed, 74 insertions(+), 69 deletions(-) diff --git a/libs/pod2utils/src/macros.rs b/libs/pod2utils/src/macros.rs index 169ab240..0b954e1d 100644 --- a/libs/pod2utils/src/macros.rs +++ b/libs/pod2utils/src/macros.rs @@ -4,8 +4,8 @@ use pod2::{ frontend::MultiPodBuilder, lang::Module, middleware::{ - CustomPredicateRef, Statement, StrKey, Value, containers::{Dictionary, Set}, + CustomPredicateRef, OperationType, Statement, StrKey, Value, }, }; @@ -326,14 +326,24 @@ impl BuildContext { statements: Vec, ) -> anyhow::Result { for module in &self.modules { - if let Some(cpr) = module.predicate_ref_by_name(name) { + if module.predicate_ref_by_name(name).is_some() { return module.apply_predicate_with(name, statements, public, |is_public, op| { let mut wildcard_values: Vec<(usize, Value)> = Vec::new(); + // Get the CustomPredicateRef from the closure because this may be a chain in a + // split predicate where the wildcard indices are different than the top level + // predicate. + let cpr = match &op.0 { + OperationType::Custom(cpr) => cpr, + _ => unreachable!(), + }; for (i, name) in cpr.predicate().wildcard_names().iter().enumerate() { if let Some(value) = wildcard_map.get(name) { wildcard_values.push((i, value.clone())); } } + // for (i, wc) in &wildcard_values { + // println!("DBG {i} {wc}"); + // } let st = self.builder.op(is_public, wildcard_values, op).unwrap(); Ok(st) }); diff --git a/libs/sdk/src/fmt_podlang.rs b/libs/sdk/src/fmt_podlang.rs index 4e570301..daed43ca 100644 --- a/libs/sdk/src/fmt_podlang.rs +++ b/libs/sdk/src/fmt_podlang.rs @@ -267,10 +267,8 @@ fn fmt_record_decls(loader: &Loader, w: &mut dyn fmt::Write) -> fmt::Result { struct SubActionCall { sub_name: String, /// Name of the parent's synthesized private wildcard for the sub's - /// `in` record (None if the sub has no in record). - sub_in_var: Option, - /// Same, for the sub's `out` record. - sub_out_var: Option, + /// `io` record + sub_io_var: String, /// Script-side alias name (the `pick` in `var pick = action.subaction(...)`). /// `None` if the user didn't bind via `var`. Used to skip the alias from /// the parent's private wildcards list. @@ -280,7 +278,7 @@ struct SubActionCall { /// Walk the parent action's Insts and gather one `SubActionCall` per /// `Inst::SubAction`. Looks up each sub's record shape from the loader's /// `actions_meta`. -fn collect_sub_action_calls(action: &ActionContext, loader: &Loader) -> Vec { +fn collect_sub_action_calls(action: &ActionContext) -> Vec { let mut calls = Vec::new(); let mut idx_counter: HashMap = HashMap::new(); for inst in &action.insts { @@ -290,27 +288,10 @@ fn collect_sub_action_calls(action: &ActionContext, loader: &Loader) -> Vec Vec = Vec::new(); - if let Some(name) = &call.sub_in_var { - args.push(name.clone()); - } - if let Some(name) = &call.sub_out_var { - args.push(name.clone()); - } + args.push(call.sub_io_var.clone()); args.push("state_header".to_string()); args.push(format!("{chain}")); args.push(format!("{chain_next}")); @@ -636,11 +607,8 @@ fn fmt_bridges(loader: &Loader, w: &mut dyn fmt::Write) -> fmt::Result { // Action call. let mut call_args: Vec = Vec::new(); - if !meta.in_entries.is_empty() { - call_args.push("in".to_string()); - } - if !meta.out_entries.is_empty() { - call_args.push("out".to_string()); + if !meta.in_entries.is_empty() || !meta.out_entries.is_empty() { + call_args.push("io".to_string()); } call_args.push("state_header".to_string()); call_args.push("chain0".to_string()); diff --git a/libs/sdk/src/lib.rs b/libs/sdk/src/lib.rs index 70a3452e..38a9f867 100644 --- a/libs/sdk/src/lib.rs +++ b/libs/sdk/src/lib.rs @@ -681,8 +681,14 @@ impl ActionHandle { // is collapsed; else a literal op-arg for the dict. let anchor_or_literal = |obj_name: &str, dict: &Dictionary, ts: usize| -> OperationArg { match meta.collapsed_at(obj_name, ts) { - Some(fmt_podlang::Collapse::IO(_)) => { - (&io_array, meta.io_entry(obj_name).unwrap().0 as i64).into() + Some(fmt_podlang::Collapse::IO(fmt_podlang::Side::In)) => { + (&io_array, meta.in_entry(obj_name).unwrap().0 as i64).into() + } + Some(fmt_podlang::Collapse::IO(fmt_podlang::Side::Out)) => { + (&io_array, meta.out_entry(obj_name).unwrap().0 as i64).into() + } + Some(fmt_podlang::Collapse::IO(fmt_podlang::Side::Any)) => { + panic!("TODO: Maybe I should get rid of Side::Any?") } Some(fmt_podlang::Collapse::Initials) => { initials_anchor(obj_name).expect("collapsed_at promised an initials slot") @@ -882,24 +888,25 @@ impl ActionHandle { for pending in pending_object_events { let varname = &pending.varname; let io = pending.io; - let new_anchor: Option = match io { - ObjectIO::Output | ObjectIO::Mutate => meta - .io_entry(varname) - .filter(|(_, e)| !e.needs_wildcard) - .map(|(idx, _)| (&io_array, idx as i64).into()), - ObjectIO::Input => None, - }; let old_anchor: Option = match io { ObjectIO::Input | ObjectIO::Mutate => meta - .io_entry(varname) + .in_entry(varname) .filter(|(_, e)| !e.needs_wildcard) .map(|(idx, _)| (&io_array, idx as i64).into()), ObjectIO::Output => None, }; + let new_anchor: Option = match io { + ObjectIO::Output | ObjectIO::Mutate => meta + .out_entry(varname) + .filter(|(_, e)| !e.needs_wildcard) + .map(|(idx, _)| (&io_array, idx as i64).into()), + ObjectIO::Input => None, + }; let pre_ts = pending.post_ts - 1; let post_ts = pending.post_ts; let chain_anchor = chain_step_anchor(post_ts); let prev_chain_anchor = chain_step_anchor(pre_ts); + // println!("DBG io: {:?}, varname: {varname}, new_anchor: {:?}", io, new_anchor); let replacements: Vec> = match io { ObjectIO::Output => { let initial_anchor = initials_anchor(varname); @@ -922,6 +929,12 @@ impl ActionHandle { None, ], }; + // println!("DBG st\n{}", pending.st_literal); + // for (i, r) in replacements.iter().enumerate() { + // if let Some(r) = r { + // println!("DBG replacement {}: {}", i, r); + // } + // } let st_tx = if replacements.iter().any(|r| r.is_some()) { exe_ctx .bld @@ -1067,11 +1080,18 @@ impl ActionHandle { sts.extend(body_sts); sts.extend(event_sts); + for st in &sts { + println!("DBG st:\n{}", st); + } + println!(""); let st_action = { let mut exe_ctx = exe_rc.borrow_mut(); + let state_header = exe_ctx.tx_builder.state_header().array(); exe_ctx .bld - .apply_custom_pred_simple(false, &action, sts) + .apply_custom_pred(false, &action, + map!({"state_header" => state_header}), + sts) .unwrap() }; @@ -1091,7 +1111,12 @@ impl ActionHandle { // doesn't matter which form we hand the bridge. let obj_ref = &meta.object_refs[object_refs_index]; let varname = &obj_ref.varname; - let entry_idx = meta.io_entry(varname).unwrap().0; + + let entry_idx = match fmt_podlang::dispatch_side(&obj_ref.io) { + fmt_podlang::Side::In => meta.in_entry(varname).unwrap().0, + fmt_podlang::Side::Out => meta.out_entry(varname).unwrap().0, + fmt_podlang::Side::Any => panic!(), + }; let st_is_x = module.build_is_x( &mut exe_ctx.bld, exe_ctx.tx_builder.state_header(), @@ -1621,15 +1646,6 @@ impl ActionMeta { self.total_outputs.iter() } - /// Find this Object's `in` entry. Returns its slot in the - /// `In` record and the entry shape. - pub(crate) fn in_entry(&self, varname: &str) -> Option<(usize, &EntryShape)> { - self.in_entries - .iter() - .enumerate() - .find(|(_, e)| e.varname == varname) - } - /// Slot for `varname` in the `Initials` record, if such a /// record exists for this action. pub(crate) fn initials_slot(&self, varname: &str) -> Option { @@ -1639,17 +1655,27 @@ impl ActionMeta { .position(|name| name == varname) } + /// Find this Object's `in` entry. Returns its slot in the + /// `In` record and the entry shape. + pub(crate) fn in_entry(&self, varname: &str) -> Option<(usize, &EntryShape)> { + self.in_entries + .iter() + .enumerate() + .find(|(_, e)| e.varname == varname) + } + pub(crate) fn out_entry(&self, varname: &str) -> Option<(usize, &EntryShape)> { self.out_entries .iter() .enumerate() + .map(|(i, e)| (i+self.in_entries.len(), e)) .find(|(_, e)| e.varname == varname) } pub(crate) fn io_entry(&self, varname: &str) -> Option<(usize, &EntryShape)> { - self.out_entries + self.in_entries .iter() - .chain(self.in_entries.iter()) + .chain(self.out_entries.iter()) .enumerate() .find(|(_, e)| e.varname == varname) } @@ -2146,7 +2172,8 @@ impl SdkModule { // Step 2: discharge the bridge predicate. let st_bridge = bld - .apply_custom_pred_simple(false, &bridge_name, vec![st_array_contains, st_action]) + .apply_custom_pred_simple(false, &bridge_name, + vec![st_array_contains, st_action]) .expect("apply bridge predicate"); // Step 3: IsX OR with the bridge at the right branch. From 0dbcf4c28ebc9f7b9d6ab7f7b8c7f18bfd4f8d0d Mon Sep 17 00:00:00 2001 From: Dhole Date: Wed, 29 Jul 2026 16:13:27 +0200 Subject: [PATCH 10/22] remove Any, update README --- libs/sdk/README.md | 148 +++++++++++++++++------------------- libs/sdk/src/fmt_podlang.rs | 32 ++++---- libs/sdk/src/lib.rs | 4 - 3 files changed, 88 insertions(+), 96 deletions(-) diff --git a/libs/sdk/README.md b/libs/sdk/README.md index 425b5c17..bf0ba108 100644 --- a/libs/sdk/README.md +++ b/libs/sdk/README.md @@ -165,153 +165,145 @@ use intro Vdf(count, input, output) from 0xab82223f501b5056f458f063eb2fc073f8ac0 use intro LtEqU256(lhs, rhs) from 0xe0595e5c75467e5a27bd30fa48a45e1dcc66a327076e5ce7c02ce33dfe357311 record StateHeader = (block_number, block_timestamp, block_hash, created, nullifiers, prior_state_history) -record FindLogOut = (log) +record FindLogIO = (out_log) record FindLogInitials = (log) -record CraftWoodIn = (log) -record CraftWoodOut = (wood) -record CraftSticksIn = (wood) -record CraftSticksOut = (stick_a, stick_b) +record CraftWoodIO = (in_log, out_wood) +record CraftSticksIO = (in_wood, out_stick_a, out_stick_b) record CraftSticksChain = (step_0, step_1) record CraftSticksInitials = (stick_a, stick_b) -record CraftWoodPickIn = (wood, stick) -record CraftWoodPickOut = (pick) +record CraftWoodPickIO = (in_wood, in_stick, out_pick) record CraftWoodPickChain = (step_0, step_1) record CraftWoodPickInitials = (pick) -record UseWoodPickIn = (wood_pick) -record UseWoodPickOut = (wood_pick) -record MineStoneWithWoodPickOut = (stone) +record UseWoodPickIO = (in_wood_pick, out_wood_pick) +record MineStoneWithWoodPickIO = (out_stone) record MineStoneWithWoodPickInitials = (stone) // Actions -FindLog(out FindLogOut, chain0, chain, private: log0, work, initials FindLogInitials) = AND( +FindLog(io FindLogIO, state_header StateHeader, chain0, chain, private: log0, work, initials FindLogInitials) = AND( Vdf(3, log0, work) DictUpdate(log0, "work", work, initials.log) - tx::TxInsert(chain0, chain, initials.log, out.log, @self_predicate(IsLog)) + tx::TxInsert(chain0, chain, initials.log, io.out_log, @self_predicate(IsLog)) ) -CraftWood(in CraftWoodIn, out CraftWoodOut, chain0, chain, private: chain1, wood0, wood1, key) = AND( +CraftWood(io CraftWoodIO, state_header StateHeader, chain0, chain, private: chain1, wood0, wood1, key) = AND( DictUpdate(wood0, "key", key, wood1) LtEqU256(wood1, Raw(0x0020000000000000000000000000000000000000000000000000000000000000)) - tx::TxDelete(chain0, chain1, in.log, @self_predicate(IsLog)) - tx::TxInsert(chain1, chain, wood1, out.wood, @self_predicate(IsWood)) + tx::TxDelete(chain0, chain1, io.in_log, @self_predicate(IsLog)) + tx::TxInsert(chain1, chain, wood1, io.out_wood, @self_predicate(IsWood)) ) -CraftSticks(in CraftSticksIn, out CraftSticksOut, chain0, chain, private: chain_steps CraftSticksChain, initials CraftSticksInitials -) = AND( - tx::TxDelete(chain0, chain_steps.step_0, in.wood, @self_predicate(IsWood)) - tx::TxInsert(chain_steps.step_0, chain_steps.step_1, initials.stick_a, out.stick_a, @self_predicate(IsStick)) - tx::TxInsert(chain_steps.step_1, chain, initials.stick_b, out.stick_b, @self_predicate(IsStick)) +CraftSticks(io CraftSticksIO, state_header StateHeader, chain0, chain, private: chain_steps CraftSticksChain, initials CraftSticksInitials) = AND( + tx::TxDelete(chain0, chain_steps.step_0, io.in_wood, @self_predicate(IsWood)) + tx::TxInsert(chain_steps.step_0, chain_steps.step_1, initials.stick_a, io.out_stick_a, @self_predicate(IsStick)) + tx::TxInsert(chain_steps.step_1, chain, initials.stick_b, io.out_stick_b, @self_predicate(IsStick)) ) -CraftWoodPick(in CraftWoodPickIn, out CraftWoodPickOut, chain0, chain, private: chain_steps CraftWoodPickChain, initials CraftWoodPi -ckInitials) = AND( +CraftWoodPick(io CraftWoodPickIO, state_header StateHeader, chain0, chain, private: chain_steps CraftWoodPickChain, initials CraftWoodPickInitials) = AND( DictContains(initials.pick, "durability", 100) - tx::TxDelete(chain0, chain_steps.step_0, in.wood, @self_predicate(IsWood)) - tx::TxDelete(chain_steps.step_0, chain_steps.step_1, in.stick, @self_predicate(IsStick)) - tx::TxInsert(chain_steps.step_1, chain, initials.pick, out.pick, @self_predicate(IsWoodPick)) + tx::TxDelete(chain0, chain_steps.step_0, io.in_wood, @self_predicate(IsWood)) + tx::TxDelete(chain_steps.step_0, chain_steps.step_1, io.in_stick, @self_predicate(IsStick)) + tx::TxInsert(chain_steps.step_1, chain, initials.pick, io.out_pick, @self_predicate(IsWoodPick)) ) -UseWoodPick(in UseWoodPickIn, out UseWoodPickOut, chain0, chain, private: wood_pick0, wood_pick1, wood_pick2, durability, key, work) - = AND( - ArrayContains(in, UseWoodPickIn::wood_pick, wood_pick0) +UseWoodPick(io UseWoodPickIO, state_header StateHeader, chain0, chain, private: wood_pick0, wood_pick1, wood_pick2, durability, key, work) = AND( + ArrayContains(io, UseWoodPickIO::in_wood_pick, wood_pick0) Gt(wood_pick0.durability, 0) Sum(durability, 1, wood_pick0.durability) DictUpdate(wood_pick0, "durability", durability, wood_pick1) DictUpdate(wood_pick1, "key", key, wood_pick2) Vdf(10, wood_pick2, work) - DictUpdate(wood_pick2, "work", work, out.wood_pick) - tx::TxMutate(chain0, chain, wood_pick0, out.wood_pick, @self_predicate(IsWoodPick)) + DictUpdate(wood_pick2, "work", work, io.out_wood_pick) + tx::TxMutate(chain0, chain, wood_pick0, io.out_wood_pick, @self_predicate(IsWoodPick)) ) -MineStoneWithWoodPick(out MineStoneWithWoodPickOut, chain0, chain, private: chain1, _UseWoodPick_in_0 UseWoodPickIn, _UseWoodPick_ou -t_0 UseWoodPickOut, initials MineStoneWithWoodPickInitials) = AND( - UseWoodPick(_UseWoodPick_in_0, _UseWoodPick_out_0, chain0, chain1) - tx::TxInsert(chain1, chain, initials.stone, out.stone, @self_predicate(IsStone)) +MineStoneWithWoodPick(io MineStoneWithWoodPickIO, state_header StateHeader, chain0, chain, private: chain1, _UseWoodPick_io_0 UseWoodPickIO, initials MineStoneWithWoodPickInitials) = AND( + UseWoodPick(_UseWoodPick_io_0, state_header, chain0, chain1) + tx::TxInsert(chain1, chain, initials.stone, io.out_stone, @self_predicate(IsStone)) ) // Bridges -IsLogFromFindLog(state, chain0, chain, private: out FindLogOut) = AND( - ArrayContains(out, FindLogOut::log, state) - FindLog(out, chain0, chain) +IsLogFromFindLog(state, state_header, chain0, chain, private: io FindLogIO) = AND( + ArrayContains(io, FindLogIO::out_log, state) + FindLog(io, state_header, chain0, chain) ) -IsLogFromCraftWood(state, chain0, chain, private: in CraftWoodIn, out CraftWoodOut) = AND( - ArrayContains(in, CraftWoodIn::log, state) - CraftWood(in, out, chain0, chain) +IsLogFromCraftWood(state, state_header, chain0, chain, private: io CraftWoodIO) = AND( + ArrayContains(io, CraftWoodIO::in_log, state) + CraftWood(io, state_header, chain0, chain) ) -IsWoodFromCraftWood(state, chain0, chain, private: in CraftWoodIn, out CraftWoodOut) = AND( - ArrayContains(out, CraftWoodOut::wood, state) - CraftWood(in, out, chain0, chain) +IsWoodFromCraftWood(state, state_header, chain0, chain, private: io CraftWoodIO) = AND( + ArrayContains(io, CraftWoodIO::out_wood, state) + CraftWood(io, state_header, chain0, chain) ) -IsWoodFromCraftSticks(state, chain0, chain, private: in CraftSticksIn, out CraftSticksOut) = AND( - ArrayContains(in, CraftSticksIn::wood, state) - CraftSticks(in, out, chain0, chain) +IsWoodFromCraftSticks(state, state_header, chain0, chain, private: io CraftSticksIO) = AND( + ArrayContains(io, CraftSticksIO::in_wood, state) + CraftSticks(io, state_header, chain0, chain) ) -IsStickFromCraftSticks_stick_a(state, chain0, chain, private: in CraftSticksIn, out CraftSticksOut) = AND( - ArrayContains(out, CraftSticksOut::stick_a, state) - CraftSticks(in, out, chain0, chain) +IsStickFromCraftSticks_stick_a(state, state_header, chain0, chain, private: io CraftSticksIO) = AND( + ArrayContains(io, CraftSticksIO::out_stick_a, state) + CraftSticks(io, state_header, chain0, chain) ) -IsStickFromCraftSticks_stick_b(state, chain0, chain, private: in CraftSticksIn, out CraftSticksOut) = AND( - ArrayContains(out, CraftSticksOut::stick_b, state) - CraftSticks(in, out, chain0, chain) +IsStickFromCraftSticks_stick_b(state, state_header, chain0, chain, private: io CraftSticksIO) = AND( + ArrayContains(io, CraftSticksIO::out_stick_b, state) + CraftSticks(io, state_header, chain0, chain) ) -IsWoodFromCraftWoodPick(state, chain0, chain, private: in CraftWoodPickIn, out CraftWoodPickOut) = AND( - ArrayContains(in, CraftWoodPickIn::wood, state) - CraftWoodPick(in, out, chain0, chain) +IsWoodFromCraftWoodPick(state, state_header, chain0, chain, private: io CraftWoodPickIO) = AND( + ArrayContains(io, CraftWoodPickIO::in_wood, state) + CraftWoodPick(io, state_header, chain0, chain) ) -IsStickFromCraftWoodPick(state, chain0, chain, private: in CraftWoodPickIn, out CraftWoodPickOut) = AND( - ArrayContains(in, CraftWoodPickIn::stick, state) - CraftWoodPick(in, out, chain0, chain) +IsStickFromCraftWoodPick(state, state_header, chain0, chain, private: io CraftWoodPickIO) = AND( + ArrayContains(io, CraftWoodPickIO::in_stick, state) + CraftWoodPick(io, state_header, chain0, chain) ) -IsWoodPickFromCraftWoodPick(state, chain0, chain, private: in CraftWoodPickIn, out CraftWoodPickOut) = AND( - ArrayContains(out, CraftWoodPickOut::pick, state) - CraftWoodPick(in, out, chain0, chain) +IsWoodPickFromCraftWoodPick(state, state_header, chain0, chain, private: io CraftWoodPickIO) = AND( + ArrayContains(io, CraftWoodPickIO::out_pick, state) + CraftWoodPick(io, state_header, chain0, chain) ) -IsWoodPickFromUseWoodPick(state, chain0, chain, private: in UseWoodPickIn, out UseWoodPickOut) = AND( - ArrayContains(out, UseWoodPickOut::wood_pick, state) - UseWoodPick(in, out, chain0, chain) +IsWoodPickFromUseWoodPick(state, state_header, chain0, chain, private: io UseWoodPickIO) = AND( + ArrayContains(io, UseWoodPickIO::out_wood_pick, state) + UseWoodPick(io, state_header, chain0, chain) ) -IsStoneFromMineStoneWithWoodPick(state, chain0, chain, private: out MineStoneWithWoodPickOut) = AND( - ArrayContains(out, MineStoneWithWoodPickOut::stone, state) - MineStoneWithWoodPick(out, chain0, chain) +IsStoneFromMineStoneWithWoodPick(state, state_header, chain0, chain, private: io MineStoneWithWoodPickIO) = AND( + ArrayContains(io, MineStoneWithWoodPickIO::out_stone, state) + MineStoneWithWoodPick(io, state_header, chain0, chain) ) // Classes IsLog(state, state_header StateHeader, chain0, chain) = OR( - IsLogFromFindLog(state, chain0, chain) - IsLogFromCraftWood(state, chain0, chain) + IsLogFromFindLog(state, state_header, chain0, chain) + IsLogFromCraftWood(state, state_header, chain0, chain) ) IsWood(state, state_header StateHeader, chain0, chain) = OR( - IsWoodFromCraftWood(state, chain0, chain) - IsWoodFromCraftSticks(state, chain0, chain) - IsWoodFromCraftWoodPick(state, chain0, chain) + IsWoodFromCraftWood(state, state_header, chain0, chain) + IsWoodFromCraftSticks(state, state_header, chain0, chain) + IsWoodFromCraftWoodPick(state, state_header, chain0, chain) ) IsStick(state, state_header StateHeader, chain0, chain) = OR( - IsStickFromCraftSticks_stick_a(state, chain0, chain) - IsStickFromCraftSticks_stick_b(state, chain0, chain) - IsStickFromCraftWoodPick(state, chain0, chain) + IsStickFromCraftSticks_stick_a(state, state_header, chain0, chain) + IsStickFromCraftSticks_stick_b(state, state_header, chain0, chain) + IsStickFromCraftWoodPick(state, state_header, chain0, chain) ) IsWoodPick(state, state_header StateHeader, chain0, chain) = OR( - IsWoodPickFromCraftWoodPick(state, chain0, chain) - IsWoodPickFromUseWoodPick(state, chain0, chain) + IsWoodPickFromCraftWoodPick(state, state_header, chain0, chain) + IsWoodPickFromUseWoodPick(state, state_header, chain0, chain) ) IsStone(state, state_header StateHeader, chain0, chain) = OR( - IsStoneFromMineStoneWithWoodPick(state, chain0, chain) + IsStoneFromMineStoneWithWoodPick(state, state_header, chain0, chain) ) ``` diff --git a/libs/sdk/src/fmt_podlang.rs b/libs/sdk/src/fmt_podlang.rs index daed43ca..9a055ec3 100644 --- a/libs/sdk/src/fmt_podlang.rs +++ b/libs/sdk/src/fmt_podlang.rs @@ -139,7 +139,6 @@ impl<'a> fmt::Display for VarNameFmt<'a> { pub(crate) enum Side { In, Out, - Any, } /// The record namespace a collapsed Object state dict belongs to. @@ -154,7 +153,6 @@ impl Side { match self { Side::In => format!("in_{name}"), Side::Out => format!("out_{name}"), - Side::Any => format!("{name}"), } } // fn schema_suffix(self) -> &'static str { @@ -218,11 +216,17 @@ pub(crate) fn dispatch_side(io: &ObjectIO) -> Side { } } -/// Schema name for a (action, namespace) pair, e.g. `LogToWoodIO`. -fn schema_name(action_name: &str, ns: impl Into) -> String { - format!("{action_name}{}", ns.into().schema_suffix()) +/// Initials schema name for a (action, namespace) pair, e.g. `LogToWoodInitials`. +fn schema_name_initials(action_name: &str) -> String { + format!("{action_name}Initials") } +/// IO schema name for a (action, namespace) pair, e.g. `LogToWoodIO`. +fn schema_name_io(action_name: &str) -> String { + format!("{action_name}IO") +} + + /// Emit `record = ()` lines for any non-empty /// in/out schema across all actions, plus `Chain` records for /// actions whose chain has 2+ intermediate states. @@ -234,7 +238,7 @@ fn fmt_record_decls(loader: &Loader, w: &mut dyn fmt::Write) -> fmt::Result { writeln!( w, "record {} = ({})", - schema_name(&meta.name, Collapse::IO(Side::Any)), + schema_name_io(&meta.name), render(&names), )?; } @@ -254,7 +258,7 @@ fn fmt_record_decls(loader: &Loader, w: &mut dyn fmt::Write) -> fmt::Result { writeln!( w, "record {} = ({})", - schema_name(&meta.name, Collapse::Initials), + schema_name_initials(&meta.name), render(initials), )?; } @@ -330,7 +334,7 @@ fn fmt_action(action: &ActionContext, loader: &Loader, w: &mut dyn fmt::Write) - write!(w, "{}(", action.name)?; let mut wrote_pub = false; if !meta.in_entries.is_empty() || !meta.out_entries.is_empty() { - write!(w, "io {}", schema_name(&action.name, Collapse::IO(Side::Any)))?; + write!(w, "io {}", schema_name_io(&action.name))?; wrote_pub = true; } if wrote_pub { @@ -373,7 +377,7 @@ fn fmt_action(action: &ActionContext, loader: &Loader, w: &mut dyn fmt::Write) - // Append synthesized sub-action typed privates last. for c in &sub_calls { let name = &c.sub_io_var; - private_vars.push(format!("{name} {}", schema_name(&c.sub_name, Collapse::IO(Side::Any)))); + private_vars.push(format!("{name} {}", schema_name_io(&c.sub_name))); } // Append the chain record typed private when packed. if chain_packed(meta.chain_max_ts) { @@ -383,7 +387,7 @@ fn fmt_action(action: &ActionContext, loader: &Loader, w: &mut dyn fmt::Write) - if meta.initials_entries.is_some() { private_vars.push(format!( "initials {}", - schema_name(&action.name, Collapse::Initials), + schema_name_initials(&action.name), )); } if !private_vars.is_empty() { @@ -427,7 +431,7 @@ fn fmt_action(action: &ActionContext, loader: &Loader, w: &mut dyn fmt::Write) - writeln!( w, " ArrayContains(io, {}::in_{}, {})", - schema_name(&action.name, Collapse::IO(Side::Any)), + schema_name_io(&action.name), o.varname, fmt_var_at(&o.varname, 0, max_ts), )?; @@ -439,7 +443,7 @@ fn fmt_action(action: &ActionContext, loader: &Loader, w: &mut dyn fmt::Write) - writeln!( w, " ArrayContains(io, {}::out_{}, {})", - schema_name(&action.name, Collapse::IO(Side::Any)), + schema_name_io(&action.name), o.varname, fmt_var_at(&o.varname, max_ts, max_ts), )?; @@ -590,7 +594,7 @@ fn fmt_bridges(loader: &Loader, w: &mut dyn fmt::Write) -> fmt::Result { write!(w, "{bridge_name}(state, state_header, chain0, chain")?; let mut priv_parts: Vec = Vec::new(); if !meta.in_entries.is_empty() || !meta.out_entries.is_empty() { - priv_parts.push(format!("io {}", schema_name(&meta.name, Collapse::IO(Side::Any)))); + priv_parts.push(format!("io {}", schema_name_io(&meta.name))); } if !priv_parts.is_empty() { write!(w, ", private: {}", priv_parts.join(", "))?; @@ -601,7 +605,7 @@ fn fmt_bridges(loader: &Loader, w: &mut dyn fmt::Write) -> fmt::Result { writeln!( w, " ArrayContains(io, {}::{}, state)", - schema_name(&meta.name, Collapse::IO(Side::Any)), + schema_name_io(&meta.name), side.arg_name(&o.varname), )?; diff --git a/libs/sdk/src/lib.rs b/libs/sdk/src/lib.rs index 38a9f867..01165aaf 100644 --- a/libs/sdk/src/lib.rs +++ b/libs/sdk/src/lib.rs @@ -687,9 +687,6 @@ impl ActionHandle { Some(fmt_podlang::Collapse::IO(fmt_podlang::Side::Out)) => { (&io_array, meta.out_entry(obj_name).unwrap().0 as i64).into() } - Some(fmt_podlang::Collapse::IO(fmt_podlang::Side::Any)) => { - panic!("TODO: Maybe I should get rid of Side::Any?") - } Some(fmt_podlang::Collapse::Initials) => { initials_anchor(obj_name).expect("collapsed_at promised an initials slot") } @@ -1115,7 +1112,6 @@ impl ActionHandle { let entry_idx = match fmt_podlang::dispatch_side(&obj_ref.io) { fmt_podlang::Side::In => meta.in_entry(varname).unwrap().0, fmt_podlang::Side::Out => meta.out_entry(varname).unwrap().0, - fmt_podlang::Side::Any => panic!(), }; let st_is_x = module.build_is_x( &mut exe_ctx.bld, From 5d61bdd95cc9b28a71b09e45c4cf10a32f5898a3 Mon Sep 17 00:00:00 2001 From: Dhole Date: Wed, 29 Jul 2026 16:15:47 +0200 Subject: [PATCH 11/22] use upstream pod2 --- Cargo.lock | 1 + Cargo.toml | 11 ++++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d2f772d0..5a7a0cf0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6076,6 +6076,7 @@ dependencies = [ [[package]] name = "pod2" version = "0.1.0" +source = "git+https://github.com/0xPARC/pod2?rev=e29598d5e1beadc27840770903098b8fd680094a#e29598d5e1beadc27840770903098b8fd680094a" dependencies = [ "annotate-snippets", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index bd90c32c..782ee926 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ members = [ resolver = "2" [workspace.dependencies] -pod2 = { git = "https://github.com/0xPARC/pod2", rev = "7a3f2c132cca851fcbef52a738cc026a5b6c58ca", default-features = false, features = [ +pod2 = { git = "https://github.com/0xPARC/pod2", rev = "e29598d5e1beadc27840770903098b8fd680094a", default-features = false, features = [ "backend_plonky2", "disk_cache", "zk", @@ -64,9 +64,10 @@ rand = { version = "0.9.2", features = ["std_rng"] } thiserror = "2.0.18" wire-types = { path = "libs/wire-types" } -[patch."https://github.com/0xPARC/pod2"] -pod2 = { path = "../pod2" } +# Uncomment to use a local pod2 dependency +# [patch."https://github.com/0xPARC/pod2"] +# pod2 = { path = "../pod2" } # Uncomment in order to enable debug information in the release builds. This allows getting panic backtraces with a performance similar to regular release. -[profile.release] -debug = true +# [profile.release] +# debug = true From d5d43cbdec5f6904bc89438343b36fb63288b7d4 Mon Sep 17 00:00:00 2001 From: Dhole Date: Wed, 29 Jul 2026 16:58:04 +0200 Subject: [PATCH 12/22] update pod2 contianer DB impl --- Cargo.lock | 1 - Cargo.toml | 7 +- services/synchronizer/src/app_db.rs | 170 +++++++++++++++++++++++----- 3 files changed, 143 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5a7a0cf0..d2f772d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6076,7 +6076,6 @@ dependencies = [ [[package]] name = "pod2" version = "0.1.0" -source = "git+https://github.com/0xPARC/pod2?rev=e29598d5e1beadc27840770903098b8fd680094a#e29598d5e1beadc27840770903098b8fd680094a" dependencies = [ "annotate-snippets", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index 782ee926..478906bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,8 @@ members = [ resolver = "2" [workspace.dependencies] -pod2 = { git = "https://github.com/0xPARC/pod2", rev = "e29598d5e1beadc27840770903098b8fd680094a", default-features = false, features = [ +# https://github.com/0xPARC/pod2/pull/530 +pod2 = { git = "https://github.com/0xPARC/pod2", rev = "06814abd7d925a1f753f2cf550152dbea318f4a9", default-features = false, features = [ "backend_plonky2", "disk_cache", "zk", @@ -65,8 +66,8 @@ thiserror = "2.0.18" wire-types = { path = "libs/wire-types" } # Uncomment to use a local pod2 dependency -# [patch."https://github.com/0xPARC/pod2"] -# pod2 = { path = "../pod2" } +[patch."https://github.com/0xPARC/pod2"] +pod2 = { path = "../pod2" } # Uncomment in order to enable debug information in the release builds. This allows getting panic backtraces with a performance similar to regular release. # [profile.release] diff --git a/services/synchronizer/src/app_db.rs b/services/synchronizer/src/app_db.rs index d6de416f..a30ade74 100644 --- a/services/synchronizer/src/app_db.rs +++ b/services/synchronizer/src/app_db.rs @@ -4,12 +4,12 @@ use anyhow::{anyhow, Context, Result}; use pod2::{ backends::plonky2::primitives::merkletree::{self, MerkleProof}, middleware::{ - containers::{Array, Set}, - db::DB as PodDb, + containers::{Array, ContainerKind, Set}, + db::{Read, DB as PodDb, TX as PodTx}, Hash, RawValue, Value, EMPTY_HASH, }, }; -use rocksdb::{Options, TransactionDB, TransactionDBOptions}; +use rocksdb::{DBAccess, Options, ReadOptions, Transaction, TransactionDB, TransactionDBOptions}; use crate::head::StateRoots; @@ -27,6 +27,13 @@ fn value_key(raw: RawValue) -> Vec { k } +fn kind_key(root: Hash) -> Vec { + let mut k = Vec::with_capacity(2 + 4); + k.extend_from_slice(b"k/"); + k.extend_from_slice(&RawValue(root.0).to_bytes()); + k +} + /// Whether the created `Array` holds `commitment` at `index`: the leaf there /// must equal it. A prefetched index is only a hint until this confirms the /// array actually holds the commitment at that position, so the read path and @@ -139,52 +146,108 @@ impl AppDb { } } -impl merkletree::db::DB for AppDb { +fn load_node_db(db: &impl DBAccess, hash: Hash) -> Result> { + if hash == EMPTY_HASH { + return Ok(Some(merkletree::Node::Intermediate( + merkletree::Intermediate::new(EMPTY_HASH, EMPTY_HASH), + ))); + } + + let node_key = node_key(hash); + match db + .get_opt(&node_key, &ReadOptions::default()) + .map_err(|e| anyhow!("rocksdb: get failed: {e}"))? + { + None => Ok(None), + Some(bytes) => Ok(Some(merkletree::Node::decode(bytes.as_ref())?)), + } +} + +fn store_node_tx<'a>(tx: &Transaction<'a, TransactionDB>, node: merkletree::Node) -> Result<()> { + let node_key = node_key(node.hash()); + tx.put(&node_key, node.encode()?) + .map_err(|e| anyhow!("rocksdb transaction put failed: {e}")) +} + +impl merkletree::db::Read for AppDb { fn load_node(&self, hash: Hash) -> Result> { - if hash == EMPTY_HASH { - return Ok(Some(merkletree::Node::Intermediate( - merkletree::Intermediate::new(EMPTY_HASH, EMPTY_HASH), - ))); - } + load_node_db(&*self.db, hash) + } +} - match self.db.get(node_key(hash))? { - None => Ok(None), - Some(bytes) => Ok(Some(merkletree::Node::decode(bytes.as_ref())?)), - } +impl merkletree::db::DB for AppDb { + fn tx<'a>(&'a self) -> Box { + PodDb::tx(self) } +} - fn store_node(&mut self, node: merkletree::Node) -> Result<()> { - self.db - .put(node_key(node.hash()), node.encode()?) - .map_err(|err| anyhow!("rocksdb transaction put failed: {err}")) +pub(crate) struct AppTx<'a> { + tx: rocksdb::Transaction<'a, rocksdb::TransactionDB>, + db: AppDb, +} + +impl<'a> merkletree::db::Read for AppTx<'a> { + fn load_node(&self, hash: Hash) -> anyhow::Result> { + load_node_db(&self.tx, hash) } } -impl PodDb for AppDb { +impl<'a> merkletree::db::TX for AppTx<'a> { + fn store_node(&mut self, node: merkletree::Node) -> anyhow::Result<()> { + store_node_tx(&self.tx, node) + } + fn commit(self: Box) -> anyhow::Result<()> { + panic!("use middleware::db::TX::commit") + } +} + +impl<'a> Read for AppTx<'a> { fn load_value(&self, raw: RawValue) -> anyhow::Result> { - match self.db.get(value_key(raw))? { + match self.tx.get(value_key(raw))? { None => Ok(None), Some(bytes) => Ok(Some({ if bytes.is_empty() { Value::from(raw) } else { - Value::from_bytes(bytes.as_ref(), self.clone_box())? + Value::from_bytes(bytes.as_ref(), self.db.clone_box())? } })), } } + fn load_kind(&self, root: Hash) -> anyhow::Result> { + if root == EMPTY_HASH { + return Ok(Some( + *ContainerKind::default() + .set_dictionary() + .set_set() + .set_array(), + )); + } + // We use `get_for_update` because this method is part of a transaction, and it will be + // used by `update_kind`, so we want and exclusive lock after the value is read to + // guarantee no data-races in the merge update. + self.tx + .get_for_update(kind_key(root), true) + .map(|opt| { + opt.map(|bytes| match bytes.len() { + 1 => Ok(ContainerKind(bytes[0])), + l => Err(anyhow!("db: invalid kind len: {}", l)), + }) + })? + .transpose() + } +} +impl<'a> PodTx for AppTx<'a> { fn store_value(&mut self, value: Value) -> anyhow::Result<()> { let value_key = value_key(value.raw()); - let tx = self.db.transaction(); - if let Some(old_value_bytes) = tx.get_for_update(&value_key, true)? { - let is_raw = old_value_bytes.is_empty(); - // If we had a non-RawValue stored don't overwrite it (specially not with a - // RawValue). Also skip redundant RawValue overwrite. - if !is_raw || value.is_raw() { + if let Some(old_value_bytes) = self.tx.get(&value_key)? { + // Never overwrite an old value with a RawValue. Skip overwrite if old value is + // already non-RawValue. + if value.is_raw() || !old_value_bytes.is_empty() { return Ok(()); } - } + }; let value_bytes = if value.is_raw() { // For RawValue we store an empty vector because it's a duplicate of the key. // This way we can easily check for RawValue without decoding. @@ -192,14 +255,59 @@ impl PodDb for AppDb { } else { Value::to_bytes(&value) }; - tx.put(value_key, value_bytes)?; - Ok(tx.commit()?) + Ok(self.tx.put(value_key, value_bytes)?) + } + fn update_kind(&mut self, root: Hash, kind: ContainerKind) -> anyhow::Result<()> { + let kind = match self.load_kind(root).expect("ok") { + Some(old_kind) => ContainerKind(old_kind.0 | kind.0), + None => kind, + }; + let kind_key = kind_key(root); + Ok(self.tx.put(&kind_key, [kind.0])?) } + fn commit(self: Box) -> anyhow::Result<()> { + Ok(self.tx.commit()?) + } +} - fn is_persistent(&self) -> bool { - true +impl Read for AppDb { + fn load_value(&self, raw: RawValue) -> anyhow::Result> { + match self.db.get(value_key(raw))? { + None => Ok(None), + Some(bytes) => Ok(Some({ + if bytes.is_empty() { + Value::from(raw) + } else { + Value::from_bytes(bytes.as_ref(), self.clone_box())? + } + })), + } } + fn load_kind(&self, root: Hash) -> anyhow::Result> { + if root == EMPTY_HASH { + return Ok(Some( + *ContainerKind::default() + .set_dictionary() + .set_set() + .set_array(), + )); + } + Ok(self.db.get(kind_key(root)).map(|opt| { + opt.map(|bytes| { + assert_eq!(1, bytes.len()); + ContainerKind(bytes[0]) + }) + })?) + } +} +impl PodDb for AppDb { + fn tx<'a>(&'a self) -> Box { + Box::new(AppTx { + tx: self.db.transaction(), + db: self.clone(), + }) + } fn clone_box(&self) -> Box { Box::new(self.clone()) } From 2a0c9a08e1f4db63fabe0647bc4f69145dd120e1 Mon Sep 17 00:00:00 2001 From: Dhole Date: Wed, 29 Jul 2026 17:00:07 +0200 Subject: [PATCH 13/22] cargo fmt and delete commented code --- libs/payload/src/test_state.rs | 2 +- libs/pod2utils/src/macros.rs | 2 +- libs/sdk/src/fmt_podlang.rs | 35 ++++------- libs/sdk/src/lib.rs | 108 ++++++++++++++++----------------- libs/txlib/src/lib.rs | 36 ++++++----- libs/txlib/src/replay.rs | 6 +- 6 files changed, 89 insertions(+), 100 deletions(-) diff --git a/libs/payload/src/test_state.rs b/libs/payload/src/test_state.rs index 9d32ccfe..6036b170 100644 --- a/libs/payload/src/test_state.rs +++ b/libs/payload/src/test_state.rs @@ -3,8 +3,8 @@ use std::collections::{HashMap, HashSet}; use pod2::{ backends::plonky2::primitives::merkletree::MerkleProof, middleware::{ + F, Hash, Value, containers::{Array, Set}, - Hash, Value, F, }, }; diff --git a/libs/pod2utils/src/macros.rs b/libs/pod2utils/src/macros.rs index 0b954e1d..961b315a 100644 --- a/libs/pod2utils/src/macros.rs +++ b/libs/pod2utils/src/macros.rs @@ -4,8 +4,8 @@ use pod2::{ frontend::MultiPodBuilder, lang::Module, middleware::{ - containers::{Dictionary, Set}, CustomPredicateRef, OperationType, Statement, StrKey, Value, + containers::{Dictionary, Set}, }, }; diff --git a/libs/sdk/src/fmt_podlang.rs b/libs/sdk/src/fmt_podlang.rs index 9a055ec3..c91422b0 100644 --- a/libs/sdk/src/fmt_podlang.rs +++ b/libs/sdk/src/fmt_podlang.rs @@ -155,20 +155,8 @@ impl Side { Side::Out => format!("out_{name}"), } } -// fn schema_suffix(self) -> &'static str { -// match self { -// Side::In => "In", -// Side::Out => "Out", -// } -// } } -// impl From for Collapse { -// fn from(side: Side) -> Self { -// Collapse::Side(side) -// } -// } - impl Collapse { pub(crate) fn arg_name(self, name: &str) -> String { match self { @@ -176,12 +164,6 @@ impl Collapse { Collapse::Initials => format!("initials.{name}"), } } - fn schema_suffix(self) -> &'static str { - match self { - Collapse::IO(_) => "IO", - Collapse::Initials => "Initials", - } - } } /// Render a Var arg as podlang text. Bare-named Vars use their @@ -226,7 +208,6 @@ fn schema_name_io(action_name: &str) -> String { format!("{action_name}IO") } - /// Emit `record = ()` lines for any non-empty /// in/out schema across all actions, plus `Chain` records for /// actions whose chain has 2+ intermediate states. @@ -234,7 +215,16 @@ fn fmt_record_decls(loader: &Loader, w: &mut dyn fmt::Write) -> fmt::Result { let render = |entries: &[String]| entries.join(", "); for meta in &loader.actions_meta { if !meta.in_entries.is_empty() || !meta.out_entries.is_empty() { - let names: Vec = meta.in_entries.iter().map(|e| Side::In.arg_name(&e.varname)).chain(meta.out_entries.iter().map(|e| Side::Out.arg_name(&e.varname))).collect(); + let names: Vec = meta + .in_entries + .iter() + .map(|e| Side::In.arg_name(&e.varname)) + .chain( + meta.out_entries + .iter() + .map(|e| Side::Out.arg_name(&e.varname)), + ) + .collect(); writeln!( w, "record {} = ({})", @@ -385,10 +375,7 @@ fn fmt_action(action: &ActionContext, loader: &Loader, w: &mut dyn fmt::Write) - } // Append the initials record typed private when packed. if meta.initials_entries.is_some() { - private_vars.push(format!( - "initials {}", - schema_name_initials(&action.name), - )); + private_vars.push(format!("initials {}", schema_name_initials(&action.name),)); } if !private_vars.is_empty() { write!(w, ", private: ")?; diff --git a/libs/sdk/src/lib.rs b/libs/sdk/src/lib.rs index 01165aaf..20bed41d 100644 --- a/libs/sdk/src/lib.rs +++ b/libs/sdk/src/lib.rs @@ -285,18 +285,16 @@ impl VarOrValue { .as_dictionary() .expect("dict"); dict.get(&StrKey::from(key)).unwrap().expect("key exists") - }, + } Type::Array(record) => { let array = value .as_ref() .expect("has value at exec time") .as_array() .expect("array"); - // println!("DBG key={key}"); - // println!("DBG array={array:?}"); let idx = record.iter().position(|k| k == key).unwrap(); array.get(idx).unwrap().expect("index exists") - }, + } _ => todo!("implement type {typ}"), } } @@ -320,28 +318,22 @@ impl VarOrValue { fn as_op_arg(&self) -> OperationArg { match self { Self::Value(value) => OperationArg::Literal(value.clone()), - Self::Var(Var { typ, value, key, .. }) => { + Self::Var(Var { + typ, value, key, .. + }) => { let value = value.as_ref().expect("has value at exec time").clone(); if let Some(key) = key { let st_contains = match typ { Type::Dict => { let dict = value.as_dictionary().expect("dict"); let value = dict.get(&key.into()).unwrap().unwrap(); - Statement::Contains( - dict.into(), - key.clone().into(), - value.into(), - ) + Statement::Contains(dict.into(), key.clone().into(), value.into()) } Type::Array(record) => { let array = value.as_array().expect("array"); let index = record.iter().position(|k| k == key).unwrap(); let value = array.get(index).unwrap().unwrap(); - Statement::Contains( - array.into(), - (index as i64).into(), - value.into(), - ) + Statement::Contains(array.into(), (index as i64).into(), value.into()) } _ => todo!("support other types"), }; @@ -489,10 +481,21 @@ impl ActionContext { } } -static RECORD_STATE_HEADER: LazyLock>> = - LazyLock::new(|| { -Arc::new(["block_number", "block_timestamp", "block_hash", "created", "nullifiers", "prior_state_history"].map(|s| s.to_string()).into_iter().collect::>()) - }); +static RECORD_STATE_HEADER: LazyLock>> = LazyLock::new(|| { + Arc::new( + [ + "block_number", + "block_timestamp", + "block_hash", + "created", + "nullifiers", + "prior_state_history", + ] + .map(|s| s.to_string()) + .into_iter() + .collect::>(), + ) +}); impl ActionHandle { // @@ -504,12 +507,17 @@ impl ActionHandle { fn state_header(&self) -> ArgHandle { let mut ctx = self.0.borrow_mut(); ctx.add_var("state_header".to_string()).unwrap_or(()); - let arg = Rc::new(RefCell::new(VarOrValue::var(Type::Array(RECORD_STATE_HEADER.clone())))); - arg.borrow_mut().set_var_name("state_header".to_string()).unwrap(); + let arg = Rc::new(RefCell::new(VarOrValue::var(Type::Array( + RECORD_STATE_HEADER.clone(), + )))); + arg.borrow_mut() + .set_var_name("state_header".to_string()) + .unwrap(); if let Some(exe_rc) = ctx.exe_ctx.as_ref() { let exe_ctx = exe_rc.borrow(); let state_header = exe_ctx.tx_builder.state_header(); - arg.borrow_mut().set_value(Value::from(state_header.array())); + arg.borrow_mut() + .set_value(Value::from(state_header.array())); } ArgHandle::new(self.clone(), arg) } @@ -903,7 +911,6 @@ impl ActionHandle { let post_ts = pending.post_ts; let chain_anchor = chain_step_anchor(post_ts); let prev_chain_anchor = chain_step_anchor(pre_ts); - // println!("DBG io: {:?}, varname: {varname}, new_anchor: {:?}", io, new_anchor); let replacements: Vec> = match io { ObjectIO::Output => { let initial_anchor = initials_anchor(varname); @@ -926,12 +933,6 @@ impl ActionHandle { None, ], }; - // println!("DBG st\n{}", pending.st_literal); - // for (i, r) in replacements.iter().enumerate() { - // if let Some(r) = r { - // println!("DBG replacement {}: {}", i, r); - // } - // } let st_tx = if replacements.iter().any(|r| r.is_some()) { exe_ctx .bld @@ -1028,14 +1029,16 @@ impl ActionHandle { let dict = final_dict.clone().expect("Set final_dict captured at Rhai"); let ts = *current_ts.get(obj).unwrap_or(&0); let dict_arg = anchor_or_literal(obj, &dict, ts); - // println!("DBG dict_arg={dict_arg}"); for (key, value) in kvs { let arg = value.borrow().as_op_arg().clone(); - // println!("DBG key={key}, value={arg}"); let st = exe_ctx .bld .builder - .priv_op(Operation::dict_contains(dict_arg.clone(), key.clone(), arg)) + .priv_op(Operation::dict_contains( + dict_arg.clone(), + key.clone(), + arg, + )) .unwrap(); body_sts.push(st); } @@ -1057,7 +1060,12 @@ impl ActionHandle { let st = exe_ctx .bld .builder - .priv_op(Operation::dict_update(old_dict_arg, key.clone(), arg, new_dict_arg)) + .priv_op(Operation::dict_update( + old_dict_arg, + key.clone(), + arg, + new_dict_arg, + )) .unwrap(); body_sts.push(st); if let Some(t) = current_ts.get_mut(obj) { @@ -1077,18 +1085,12 @@ impl ActionHandle { sts.extend(body_sts); sts.extend(event_sts); - for st in &sts { - println!("DBG st:\n{}", st); - } - println!(""); let st_action = { let mut exe_ctx = exe_rc.borrow_mut(); let state_header = exe_ctx.tx_builder.state_header().array(); exe_ctx .bld - .apply_custom_pred(false, &action, - map!({"state_header" => state_header}), - sts) + .apply_custom_pred(false, &action, map!({"state_header" => state_header}), sts) .unwrap() }; @@ -1111,7 +1113,7 @@ impl ActionHandle { let entry_idx = match fmt_podlang::dispatch_side(&obj_ref.io) { fmt_podlang::Side::In => meta.in_entry(varname).unwrap().0, - fmt_podlang::Side::Out => meta.out_entry(varname).unwrap().0, + fmt_podlang::Side::Out => meta.out_entry(varname).unwrap().0, }; let st_is_x = module.build_is_x( &mut exe_ctx.bld, @@ -1664,7 +1666,7 @@ impl ActionMeta { self.out_entries .iter() .enumerate() - .map(|(i, e)| (i+self.in_entries.len(), e)) + .map(|(i, e)| (i + self.in_entries.len(), e)) .find(|(_, e)| e.varname == varname) } @@ -2006,7 +2008,6 @@ impl Loader { fn module(self, engine: Rc, ast: AST) -> SdkModule { let mut podlang_src = String::new(); fmt_podlang::fmt(&self, &mut podlang_src).unwrap(); - println!("DBG\n{podlang_src}"); let params = Params::default(); let module = Arc::new( @@ -2168,8 +2169,7 @@ impl SdkModule { // Step 2: discharge the bridge predicate. let st_bridge = bld - .apply_custom_pred_simple(false, &bridge_name, - vec![st_array_contains, st_action]) + .apply_custom_pred_simple(false, &bridge_name, vec![st_array_contains, st_action]) .expect("apply bridge predicate"); // Step 3: IsX OR with the bridge at the right branch. @@ -2591,7 +2591,6 @@ impl Sdk { actions: &[&str], ) -> Result, SdkError> { let scope = Scope::new(); - // println!("DBG: Compile"); let ast = self.engine.compile_with_scope(&scope, src).unwrap(); let mut action_handles = Vec::new(); @@ -2600,7 +2599,6 @@ impl Sdk { let mut scope = Scope::new(); scope.push_constant("state_header", action_handle.state_header()); let options = CallFnOptions::new().with_tag(action_handle.clone()); - // println!("DBG: Call {action}"); let _result = self.engine.call_fn_with_options::( options, &mut scope, @@ -2611,7 +2609,6 @@ impl Sdk { action_handles.push(action_handle); } - // println!("DBG: Loader"); let loader = Loader::new(action_handles)?; Ok(Rc::new(loader.module(self.engine.clone(), ast))) } @@ -2639,14 +2636,13 @@ impl Sdk { loaded_classes ))?; } - // TODO: Uncomment - // if manifest.plugin.module_hash != sdk_module.module.batch.id() { - // return Err(anyhow!( - // "manifest.plugin.module_hash = {:#} but module.hash = {:#}", - // manifest.plugin.module_hash, - // sdk_module.module.batch.id() - // ))?; - // } + if manifest.plugin.module_hash != sdk_module.module.batch.id() { + return Err(anyhow!( + "manifest.plugin.module_hash = {:#} but module.hash = {:#}", + manifest.plugin.module_hash, + sdk_module.module.batch.id() + ))?; + } Ok(sdk_module) } diff --git a/libs/txlib/src/lib.rs b/libs/txlib/src/lib.rs index f91ca065..ddc11312 100644 --- a/libs/txlib/src/lib.rs +++ b/libs/txlib/src/lib.rs @@ -33,9 +33,9 @@ use pod2::{ backends::plonky2::primitives::merkletree::MerkleProof, frontend::Operation, middleware::{ + EMPTY_VALUE, Hash, NativeOperation, OperationAux, OperationType, Statement, StrKey, Value, containers::{Array, Dictionary, Set}, - hash_values, Hash, NativeOperation, OperationAux, OperationType, Statement, StrKey, Value, - EMPTY_VALUE, + hash_values, }, }; use pod2utils::{dict, macros::BuildContext, map, op, rand_raw_value, set, st_custom}; @@ -1146,7 +1146,7 @@ mod tests { use pod2::{ backends::plonky2::mock::mainpod::MockProver, frontend::{MainPod, MultiPodBuilder}, - middleware::{containers::Array, Params, Predicate, VDSet, F}, + middleware::{F, Params, Predicate, VDSet, containers::Array}, }; use pod2utils::{macros::BuildContext, set}; @@ -1444,10 +1444,12 @@ mod tests { ctx.builder.reveal(&st).unwrap(); solve_and_verify(ctx.builder); - assert!(tx_out - .nullifiers - .contains(&Value::from(compute_nullifier(&pick))) - .unwrap()); + assert!( + tx_out + .nullifiers + .contains(&Value::from(compute_nullifier(&pick))) + .unwrap() + ); } /// Tx 1: FindLog (genesis insert). @@ -1672,10 +1674,12 @@ mod tests { assert!(tx3_out.live.contains(&Value::from(stick_a)).unwrap()); assert!(tx3_out.live.contains(&Value::from(stick_b)).unwrap()); // Wood should be nullified - assert!(tx3_out - .nullifiers - .contains(&Value::from(compute_nullifier(&wood))) - .unwrap()); + assert!( + tx3_out + .nullifiers + .contains(&Value::from(compute_nullifier(&wood))) + .unwrap() + ); } /// Grounding three inputs exercises InputsGroundedRecursive (peel two per @@ -1762,10 +1766,12 @@ mod tests { solve_and_verify(ctx.builder); for log in &logs { - assert!(burn_out - .nullifiers - .contains(&Value::from(compute_nullifier(log))) - .unwrap()); + assert!( + burn_out + .nullifiers + .contains(&Value::from(compute_nullifier(log))) + .unwrap() + ); } } } diff --git a/libs/txlib/src/replay.rs b/libs/txlib/src/replay.rs index f4b3fb50..1b3436de 100644 --- a/libs/txlib/src/replay.rs +++ b/libs/txlib/src/replay.rs @@ -16,15 +16,15 @@ use pod2::{ frontend::Operation, middleware::{ - containers::{Dictionary, Set}, Hash, Statement, Value, + containers::{Dictionary, Set}, }, }; use pod2utils::{dict, macros::BuildContext, map, op, st_custom}; use crate::{ - build_tx, object_key_hash, object_nullifier_from_key_hash, record, tx_with, ChainEvent, - TxStats, OBJECT_NULLIFIER_VERSION, + ChainEvent, OBJECT_NULLIFIER_VERSION, TxStats, build_tx, object_key_hash, + object_nullifier_from_key_hash, record, tx_with, }; /// The replay walker. Owns the long-lived mutable builder state From 857f9b7698301e31d1ea4c80a31c546ec6b9a273 Mon Sep 17 00:00:00 2001 From: Dhole Date: Wed, 29 Jul 2026 17:03:29 +0200 Subject: [PATCH 14/22] comment local pod2 dep --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 478906bb..cfddb0d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,8 +66,8 @@ thiserror = "2.0.18" wire-types = { path = "libs/wire-types" } # Uncomment to use a local pod2 dependency -[patch."https://github.com/0xPARC/pod2"] -pod2 = { path = "../pod2" } +# [patch."https://github.com/0xPARC/pod2"] +# pod2 = { path = "../pod2" } # Uncomment in order to enable debug information in the release builds. This allows getting panic backtraces with a performance similar to regular release. # [profile.release] From 6052c8ebb98cbc2d56c110bae838ac8c70ff03a0 Mon Sep 17 00:00:00 2001 From: Dhole Date: Wed, 29 Jul 2026 17:06:08 +0200 Subject: [PATCH 15/22] delete commented code --- Cargo.lock | 1 + libs/pod2utils/src/macros.rs | 5 +---- libs/txlib/src/replay.rs | 8 +++----- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d2f772d0..4201f621 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6076,6 +6076,7 @@ dependencies = [ [[package]] name = "pod2" version = "0.1.0" +source = "git+https://github.com/0xPARC/pod2?rev=06814abd7d925a1f753f2cf550152dbea318f4a9#06814abd7d925a1f753f2cf550152dbea318f4a9" dependencies = [ "annotate-snippets", "anyhow", diff --git a/libs/pod2utils/src/macros.rs b/libs/pod2utils/src/macros.rs index 961b315a..a692e798 100644 --- a/libs/pod2utils/src/macros.rs +++ b/libs/pod2utils/src/macros.rs @@ -4,8 +4,8 @@ use pod2::{ frontend::MultiPodBuilder, lang::Module, middleware::{ - CustomPredicateRef, OperationType, Statement, StrKey, Value, containers::{Dictionary, Set}, + CustomPredicateRef, OperationType, Statement, StrKey, Value, }, }; @@ -341,9 +341,6 @@ impl BuildContext { wildcard_values.push((i, value.clone())); } } - // for (i, wc) in &wildcard_values { - // println!("DBG {i} {wc}"); - // } let st = self.builder.op(is_public, wildcard_values, op).unwrap(); Ok(st) }); diff --git a/libs/txlib/src/replay.rs b/libs/txlib/src/replay.rs index 1b3436de..5035b64e 100644 --- a/libs/txlib/src/replay.rs +++ b/libs/txlib/src/replay.rs @@ -16,15 +16,15 @@ use pod2::{ frontend::Operation, middleware::{ - Hash, Statement, Value, containers::{Dictionary, Set}, + Hash, Statement, Value, }, }; use pod2utils::{dict, macros::BuildContext, map, op, st_custom}; use crate::{ - ChainEvent, OBJECT_NULLIFIER_VERSION, TxStats, build_tx, object_key_hash, - object_nullifier_from_key_hash, record, tx_with, + build_tx, object_key_hash, object_nullifier_from_key_hash, record, tx_with, ChainEvent, + TxStats, OBJECT_NULLIFIER_VERSION, }; /// The replay walker. Owns the long-lived mutable builder state @@ -956,8 +956,6 @@ impl<'a> Replayer<'a> { )) .unwrap(); - // println!("DBG btx={}", btx.inner()); - // println!("DBG new_live={}", new_live.inner()); let op_si = self .ctx .builder From 7fbd87945f34fa504a50e41e8e4040338c783d15 Mon Sep 17 00:00:00 2001 From: Dhole Date: Wed, 29 Jul 2026 17:23:26 +0200 Subject: [PATCH 16/22] cargo fmt --- libs/pod2utils/src/macros.rs | 2 +- libs/sdk/src/lib.rs | 40 +++++++++++++++++------------------- libs/txlib/src/replay.rs | 6 +++--- 3 files changed, 23 insertions(+), 25 deletions(-) diff --git a/libs/pod2utils/src/macros.rs b/libs/pod2utils/src/macros.rs index a692e798..551a767d 100644 --- a/libs/pod2utils/src/macros.rs +++ b/libs/pod2utils/src/macros.rs @@ -4,8 +4,8 @@ use pod2::{ frontend::MultiPodBuilder, lang::Module, middleware::{ - containers::{Dictionary, Set}, CustomPredicateRef, OperationType, Statement, StrKey, Value, + containers::{Dictionary, Set}, }, }; diff --git a/libs/sdk/src/lib.rs b/libs/sdk/src/lib.rs index 20bed41d..b7f429bd 100644 --- a/libs/sdk/src/lib.rs +++ b/libs/sdk/src/lib.rs @@ -276,28 +276,26 @@ impl VarOrValue { typ, key: Some(key), .. - }) => { - match typ { - Type::Dict => { - let dict = value - .as_ref() - .expect("has value at exec time") - .as_dictionary() - .expect("dict"); - dict.get(&StrKey::from(key)).unwrap().expect("key exists") - } - Type::Array(record) => { - let array = value - .as_ref() - .expect("has value at exec time") - .as_array() - .expect("array"); - let idx = record.iter().position(|k| k == key).unwrap(); - array.get(idx).unwrap().expect("index exists") - } - _ => todo!("implement type {typ}"), + }) => match typ { + Type::Dict => { + let dict = value + .as_ref() + .expect("has value at exec time") + .as_dictionary() + .expect("dict"); + dict.get(&StrKey::from(key)).unwrap().expect("key exists") } - } + Type::Array(record) => { + let array = value + .as_ref() + .expect("has value at exec time") + .as_array() + .expect("array"); + let idx = record.iter().position(|k| k == key).unwrap(); + array.get(idx).unwrap().expect("index exists") + } + _ => todo!("implement type {typ}"), + }, } } // Only call this at exec time diff --git a/libs/txlib/src/replay.rs b/libs/txlib/src/replay.rs index 5035b64e..35fa25bf 100644 --- a/libs/txlib/src/replay.rs +++ b/libs/txlib/src/replay.rs @@ -16,15 +16,15 @@ use pod2::{ frontend::Operation, middleware::{ - containers::{Dictionary, Set}, Hash, Statement, Value, + containers::{Dictionary, Set}, }, }; use pod2utils::{dict, macros::BuildContext, map, op, st_custom}; use crate::{ - build_tx, object_key_hash, object_nullifier_from_key_hash, record, tx_with, ChainEvent, - TxStats, OBJECT_NULLIFIER_VERSION, + ChainEvent, OBJECT_NULLIFIER_VERSION, TxStats, build_tx, object_key_hash, + object_nullifier_from_key_hash, record, tx_with, }; /// The replay walker. Owns the long-lived mutable builder state From 21da004699fd8055b9cfa0f92e0852d341f079d9 Mon Sep 17 00:00:00 2001 From: Dhole Date: Wed, 29 Jul 2026 17:47:26 +0200 Subject: [PATCH 17/22] cargo clippy --- libs/sdk/src/fmt_podlang.rs | 4 ++-- libs/sdk/src/lib.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/sdk/src/fmt_podlang.rs b/libs/sdk/src/fmt_podlang.rs index c91422b0..be336d65 100644 --- a/libs/sdk/src/fmt_podlang.rs +++ b/libs/sdk/src/fmt_podlang.rs @@ -120,7 +120,7 @@ impl<'a> VarNameFmt<'a> { impl<'a> fmt::Display for VarNameFmt<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if let Some(ns) = self.collapses_at() { - return write!(f, "{}", ns.arg_name(&self.name)); + return write!(f, "{}", ns.arg_name(self.name)); } let max_ts = self.meta.max_ts(self.name); if self.name == "chain" @@ -160,7 +160,7 @@ impl Side { impl Collapse { pub(crate) fn arg_name(self, name: &str) -> String { match self { - Collapse::IO(side) => format!("io.{}", side.arg_name(&name)), + Collapse::IO(side) => format!("io.{}", side.arg_name(name)), Collapse::Initials => format!("initials.{name}"), } } diff --git a/libs/sdk/src/lib.rs b/libs/sdk/src/lib.rs index b7f429bd..566a92aa 100644 --- a/libs/sdk/src/lib.rs +++ b/libs/sdk/src/lib.rs @@ -668,7 +668,7 @@ impl ActionHandle { } } let mut io_dicts = in_dicts; - io_dicts.extend(out_dicts.into_iter()); + io_dicts.extend(out_dicts); let io_array = Array::new(io_dicts); // Build the `Initials` record value (the pre-identity Output From 9794e1da9199c5589736ab74a17601452278c54d Mon Sep 17 00:00:00 2001 From: Dhole Date: Wed, 29 Jul 2026 17:48:07 +0200 Subject: [PATCH 18/22] fix unused var in test --- libs/sdk/src/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/sdk/src/tests.rs b/libs/sdk/src/tests.rs index 8f9de0d1..164b4581 100644 --- a/libs/sdk/src/tests.rs +++ b/libs/sdk/src/tests.rs @@ -632,6 +632,6 @@ fn test_sdk_state_header() { let executor = module.executor(true, grounding_witness(&state, &[ticker0.obj.commitment()])); let res = executor.action("Tick", vec![ticker0]).unwrap(); let ticker1_tx = res.tx.clone(); - let [ticker1] = res.objs(); + let [_ticker1] = res.objs(); apply_tx(&mut state, &ticker1_tx); } From b46f259712ffcc45c486662a7edefbc282b9475d Mon Sep 17 00:00:00 2001 From: Dhole Date: Wed, 29 Jul 2026 18:12:31 +0200 Subject: [PATCH 19/22] update manifest hashes --- Cargo.lock | 2 +- Cargo.toml | 3 +-- examples/craft-basics/manifest.toml | 2 +- examples/craft-rocket/manifest.toml | 2 +- libs/sdk/src/tests.rs | 4 ++-- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4201f621..b5a999bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6076,7 +6076,7 @@ dependencies = [ [[package]] name = "pod2" version = "0.1.0" -source = "git+https://github.com/0xPARC/pod2?rev=06814abd7d925a1f753f2cf550152dbea318f4a9#06814abd7d925a1f753f2cf550152dbea318f4a9" +source = "git+https://github.com/0xPARC/pod2?rev=73810cbd5b57cd679e25dfad0c34968faec22767#73810cbd5b57cd679e25dfad0c34968faec22767" dependencies = [ "annotate-snippets", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index cfddb0d9..7f4a0d4b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,8 +21,7 @@ members = [ resolver = "2" [workspace.dependencies] -# https://github.com/0xPARC/pod2/pull/530 -pod2 = { git = "https://github.com/0xPARC/pod2", rev = "06814abd7d925a1f753f2cf550152dbea318f4a9", default-features = false, features = [ +pod2 = { git = "https://github.com/0xPARC/pod2", rev = "73810cbd5b57cd679e25dfad0c34968faec22767", default-features = false, features = [ "backend_plonky2", "disk_cache", "zk", diff --git a/examples/craft-basics/manifest.toml b/examples/craft-basics/manifest.toml index 681b50ae..0211c8b6 100644 --- a/examples/craft-basics/manifest.toml +++ b/examples/craft-basics/manifest.toml @@ -2,7 +2,7 @@ name = "craft-basics" version = "0.1.0" # Rewritten by `cargo run -p pexe -- build examples/craft-basics`. -module_hash = "9db3acf116e6c8d635da722b0a17e7988bbffee10590fffe8fc0161aa0be18d0" +module_hash = "57631b51fb9a921588d391211f94c0bd8f777aff0a16755bc2dfefb52d6ff5b0" [[classes]] name = "Log" diff --git a/examples/craft-rocket/manifest.toml b/examples/craft-rocket/manifest.toml index e7686918..fd183df3 100644 --- a/examples/craft-rocket/manifest.toml +++ b/examples/craft-rocket/manifest.toml @@ -2,7 +2,7 @@ name = "craft-rocket" version = "0.1.0" # Rewritten by `cargo run -p pexe -- build examples/craft-rocket`. -module_hash = "ee913737d2541322c86d4e52c33bbf8bad872970e5e62e336553912b61285349" +module_hash = "f838acbb5b8a196174ec1adb15b2dc19f60faa5ef76ab0ab60b6ee010f0f964f" # ── raw resources ──────────────────────────────────────────────────────────── diff --git a/libs/sdk/src/tests.rs b/libs/sdk/src/tests.rs index 164b4581..0b73004b 100644 --- a/libs/sdk/src/tests.rs +++ b/libs/sdk/src/tests.rs @@ -250,7 +250,7 @@ fn test_sdk_2() { [plugin] name = "test" version = "0.1.0" - module_hash = "9e84b0fb084e8be99f74c7788e3c43d13927826f0e0315f99d9b9a678c24103b" + module_hash = "a8ae566dddbe81cdf1f7d15396eadb748cdf4f0a8976936c406199b556d62c10" [[classes]] name = "Log" @@ -570,7 +570,7 @@ fn test_sdk_state_header() { [plugin] name = "test" version = "0.1.0" - module_hash = "9e84b0fb084e8be99f74c7788e3c43d13927826f0e0315f99d9b9a678c24103b" + module_hash = "a8ae566dddbe81cdf1f7d15396eadb748cdf4f0a8976936c406199b556d62c10" [[classes]] name = "Ticker" From 32a9d63603359e37a82c957ed5ad965b3b696541 Mon Sep 17 00:00:00 2001 From: Dhole Date: Wed, 29 Jul 2026 19:16:07 +0200 Subject: [PATCH 20/22] update test vectors --- libs/sdk/src/tests.rs | 82 ++++++++++++++++++++----------------------- 1 file changed, 38 insertions(+), 44 deletions(-) diff --git a/libs/sdk/src/tests.rs b/libs/sdk/src/tests.rs index 0b73004b..5e9f8e5f 100644 --- a/libs/sdk/src/tests.rs +++ b/libs/sdk/src/tests.rs @@ -316,28 +316,27 @@ fn test_records_form_just_output() { .load_module_from_src_actions(craft_src, &["JustOutput"]) .unwrap(); - let expected = r#"record JustOutputOut = (x) + let expected = r#"record JustOutputIO = (out_x) record JustOutputInitials = (x) // Actions -JustOutput(out JustOutputOut, chain0, chain, private: initials JustOutputInitials) = AND( - tx::TxInsert(chain0, chain, initials.x, out.x, @self_predicate(IsFoo)) +JustOutput(io JustOutputIO, state_header StateHeader, chain0, chain, private: initials JustOutputInitials) = AND( + tx::TxInsert(chain0, chain, initials.x, io.out_x, @self_predicate(IsFoo)) ) // Bridges -IsFooFromJustOutput(state, chain0, chain, private: out JustOutputOut) = AND( - ArrayContains(out, JustOutputOut::x, state) - JustOutput(out, chain0, chain) +IsFooFromJustOutput(state, state_header, chain0, chain, private: io JustOutputIO) = AND( + ArrayContains(io, JustOutputIO::out_x, state) + JustOutput(io, state_header, chain0, chain) ) // Classes IsFoo(state, state_header StateHeader, chain0, chain) = OR( - IsFooFromJustOutput(state, chain0, chain) -) -"#; + IsFooFromJustOutput(state, state_header, chain0, chain) +)"#; assert!( module.podlang_src.contains(expected), "records-form mismatch.\nexpected fragment:\n{expected}\nactual:\n{}", @@ -367,40 +366,38 @@ fn test_records_form_input_output_update() { .load_module_from_src_actions(craft_src, &["LogToWood"]) .unwrap(); - let expected = r#"record LogToWoodIn = (log) -record LogToWoodOut = (wood) + let expected = r#"record LogToWoodIO = (in_log, out_wood) record LogToWoodInitials = (wood) // Actions -LogToWood(in LogToWoodIn, out LogToWoodOut, chain0, chain, private: chain1, wood0, key, initials LogToWoodInitials) = AND( +LogToWood(io LogToWoodIO, state_header StateHeader, chain0, chain, private: chain1, wood0, key, initials LogToWoodInitials) = AND( DictUpdate(wood0, "key", key, initials.wood) - tx::TxDelete(chain0, chain1, in.log, @self_predicate(IsLog)) - tx::TxInsert(chain1, chain, initials.wood, out.wood, @self_predicate(IsWood)) + tx::TxDelete(chain0, chain1, io.in_log, @self_predicate(IsLog)) + tx::TxInsert(chain1, chain, initials.wood, io.out_wood, @self_predicate(IsWood)) ) // Bridges -IsLogFromLogToWood(state, chain0, chain, private: in LogToWoodIn, out LogToWoodOut) = AND( - ArrayContains(in, LogToWoodIn::log, state) - LogToWood(in, out, chain0, chain) +IsLogFromLogToWood(state, state_header, chain0, chain, private: io LogToWoodIO) = AND( + ArrayContains(io, LogToWoodIO::in_log, state) + LogToWood(io, state_header, chain0, chain) ) -IsWoodFromLogToWood(state, chain0, chain, private: in LogToWoodIn, out LogToWoodOut) = AND( - ArrayContains(out, LogToWoodOut::wood, state) - LogToWood(in, out, chain0, chain) +IsWoodFromLogToWood(state, state_header, chain0, chain, private: io LogToWoodIO) = AND( + ArrayContains(io, LogToWoodIO::out_wood, state) + LogToWood(io, state_header, chain0, chain) ) // Classes IsLog(state, state_header StateHeader, chain0, chain) = OR( - IsLogFromLogToWood(state, chain0, chain) + IsLogFromLogToWood(state, state_header, chain0, chain) ) IsWood(state, state_header StateHeader, chain0, chain) = OR( - IsWoodFromLogToWood(state, chain0, chain) -) -"#; + IsWoodFromLogToWood(state, state_header, chain0, chain) +)"#; assert!( module.podlang_src.contains(expected), "records-form mismatch.\nexpected fragment:\n{expected}\nactual:\n{}", @@ -439,11 +436,10 @@ fn test_records_form_subaction() { // Parent action signature + sub-action call body. `bar`'s // out-side collapses (no sub-field reads, no Intro use) so the // wildcard is dropped and body refs render as `out.bar`. - let expected_parent = r#"MineBar(out MineBarOut, chain0, chain, private: chain1, _UseFoo_in_0 UseFooIn, _UseFoo_out_0 UseFooOut, initials MineBarInitials) = AND( - UseFoo(_UseFoo_in_0, _UseFoo_out_0, chain0, chain1) - tx::TxInsert(chain1, chain, initials.bar, out.bar, @self_predicate(IsBar)) -) -"#; + let expected_parent = r#"MineBar(io MineBarIO, state_header StateHeader, chain0, chain, private: chain1, _UseFoo_io_0 UseFooIO, initials MineBarInitials) = AND( + UseFoo(_UseFoo_io_0, state_header, chain0, chain1) + tx::TxInsert(chain1, chain, initials.bar, io.out_bar, @self_predicate(IsBar)) +)"#; assert!( module.podlang_src.contains(expected_parent), "MineBar records-form mismatch.\nexpected:\n{expected_parent}\nactual:\n{}", @@ -452,9 +448,9 @@ fn test_records_form_subaction() { // The bridge for MineBar's direct output (`bar`) should exist. assert!( - module - .podlang_src - .contains("IsBarFromMineBar(state, chain0, chain, private: out MineBarOut) = AND("), + module.podlang_src.contains( + "IsBarFromMineBar(state, state_header, chain0, chain, private: io MineBarIO) = AND(" + ), "missing IsBarFromMineBar bridge:\n{}", module.podlang_src ); @@ -491,32 +487,30 @@ fn test_records_form_mutate() { .load_module_from_src_actions(craft_src, &["UseFoo"]) .unwrap(); - let expected = r#"record UseFooIn = (foo) -record UseFooOut = (foo) + let expected = r#"record UseFooIO = (in_foo, out_foo) // Actions -UseFoo(in UseFooIn, out UseFooOut, chain0, chain, private: foo0, dur) = AND( - ArrayContains(in, UseFooIn::foo, foo0) +UseFoo(io UseFooIO, state_header StateHeader, chain0, chain, private: foo0, dur) = AND( + ArrayContains(io, UseFooIO::in_foo, foo0) Gt(foo0.durability, 0) Sum(dur, 1, foo0.durability) - DictUpdate(foo0, "durability", dur, out.foo) - tx::TxMutate(chain0, chain, foo0, out.foo, @self_predicate(IsFoo)) + DictUpdate(foo0, "durability", dur, io.out_foo) + tx::TxMutate(chain0, chain, foo0, io.out_foo, @self_predicate(IsFoo)) ) // Bridges -IsFooFromUseFoo(state, chain0, chain, private: in UseFooIn, out UseFooOut) = AND( - ArrayContains(out, UseFooOut::foo, state) - UseFoo(in, out, chain0, chain) +IsFooFromUseFoo(state, state_header, chain0, chain, private: io UseFooIO) = AND( + ArrayContains(io, UseFooIO::out_foo, state) + UseFoo(io, state_header, chain0, chain) ) // Classes IsFoo(state, state_header StateHeader, chain0, chain) = OR( - IsFooFromUseFoo(state, chain0, chain) -) -"#; + IsFooFromUseFoo(state, state_header, chain0, chain) +)"#; assert!( module.podlang_src.contains(expected), "records-form mismatch.\nexpected fragment:\n{expected}\nactual:\n{}", From 2c61de07831a134e68ba25252f376e04714aeee3 Mon Sep 17 00:00:00 2001 From: Dhole Date: Wed, 29 Jul 2026 19:30:08 +0200 Subject: [PATCH 21/22] fix test module hash --- libs/sdk/src/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/sdk/src/tests.rs b/libs/sdk/src/tests.rs index 5e9f8e5f..80d37727 100644 --- a/libs/sdk/src/tests.rs +++ b/libs/sdk/src/tests.rs @@ -250,7 +250,7 @@ fn test_sdk_2() { [plugin] name = "test" version = "0.1.0" - module_hash = "a8ae566dddbe81cdf1f7d15396eadb748cdf4f0a8976936c406199b556d62c10" + module_hash = "b356b892ed13e203eddd3f0ea85c67e56ac3756ac7fe935bf387767b0dbfc2fd" [[classes]] name = "Log" From 99201631772ecf65b8102fcf1ece8c602512d3ac Mon Sep 17 00:00:00 2001 From: Dhole Date: Fri, 31 Jul 2026 15:22:15 +0200 Subject: [PATCH 22/22] fix offset var entry --- libs/sdk/src/lib.rs | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/libs/sdk/src/lib.rs b/libs/sdk/src/lib.rs index 566a92aa..82c11ece 100644 --- a/libs/sdk/src/lib.rs +++ b/libs/sdk/src/lib.rs @@ -717,14 +717,14 @@ impl ActionHandle { ObjectIO::Output => stamped_outputs[&varname].clone(), _ => obj.borrow().to_dict(), }; - let dict = match io { + let pre_dict = match io { ObjectIO::Mutate => original .as_ref() .expect("Mutate records a pre-mutation dict") .clone(), _ => post_dict.clone(), }; - if let Some((idx, e)) = meta.io_entry(&varname) + if let Some((idx, e)) = meta.in_entry(&varname) && e.needs_wildcard { let st = exe_ctx @@ -733,7 +733,21 @@ impl ActionHandle { .priv_op(Operation::array_contains( Value::from(io_array.clone()), idx as i64, - Value::from(dict.clone()), + Value::from(pre_dict.clone()), + )) + .unwrap(); + array_contains_sts.push(st); + } + if let Some((idx, e)) = meta.out_entry(&varname) + && e.needs_wildcard + { + let st = exe_ctx + .bld + .builder + .priv_op(Operation::array_contains( + Value::from(io_array.clone()), + idx as i64, + Value::from(post_dict.clone()), )) .unwrap(); array_contains_sts.push(st); @@ -1668,14 +1682,6 @@ impl ActionMeta { .find(|(_, e)| e.varname == varname) } - pub(crate) fn io_entry(&self, varname: &str) -> Option<(usize, &EntryShape)> { - self.in_entries - .iter() - .chain(self.out_entries.iter()) - .enumerate() - .find(|(_, e)| e.varname == varname) - } - pub(crate) fn max_ts(&self, varname: &str) -> usize { *self .var_max_ts