From 1670a73474900f06d73fe45ecc5bba0b1e107da1 Mon Sep 17 00:00:00 2001 From: rmgaray Date: Thu, 30 Apr 2026 19:57:59 +0000 Subject: [PATCH 01/12] execute PeTTa as interpreter --- .../swipl/01-compile-metta.rho | 6 -- .../system-contract/swipl/01-swap.metta | 5 ++ .../system-contract/swipl/01-swap.rho | 6 ++ rholang/src/rust/interpreter/rho_runtime.rs | 35 ++++----- .../rust/interpreter/swi_prolog_service.rs | 75 +++++++++++++++---- .../src/rust/interpreter/system_processes.rs | 32 ++++---- 6 files changed, 105 insertions(+), 54 deletions(-) delete mode 100644 rholang/examples/system-contract/swipl/01-compile-metta.rho create mode 100644 rholang/examples/system-contract/swipl/01-swap.metta create mode 100644 rholang/examples/system-contract/swipl/01-swap.rho diff --git a/rholang/examples/system-contract/swipl/01-compile-metta.rho b/rholang/examples/system-contract/swipl/01-compile-metta.rho deleted file mode 100644 index cd75e78bc..000000000 --- a/rholang/examples/system-contract/swipl/01-compile-metta.rho +++ /dev/null @@ -1,6 +0,0 @@ -new compilePetta(`rho:petta:compile`), stdout(`rho:io:stdout`), retCh in { - compilePetta!("(= (swap (Pair $x $y)) (Pair $y $x))", *retCh) | - for(@ok <- retCh) { - stdout!(ok) - } -} \ No newline at end of file diff --git a/rholang/examples/system-contract/swipl/01-swap.metta b/rholang/examples/system-contract/swipl/01-swap.metta new file mode 100644 index 000000000..31c5d443c --- /dev/null +++ b/rholang/examples/system-contract/swipl/01-swap.metta @@ -0,0 +1,5 @@ +(= (swap (Pair $x $y)) (Pair $y $x)) + +!(swap (Pair 1 2)) + +!(swap (Pair x y)) diff --git a/rholang/examples/system-contract/swipl/01-swap.rho b/rholang/examples/system-contract/swipl/01-swap.rho new file mode 100644 index 000000000..6e0a32219 --- /dev/null +++ b/rholang/examples/system-contract/swipl/01-swap.rho @@ -0,0 +1,6 @@ +new executePetta(`rho:petta:execute`), stdout(`rho:io:stdout`), retCh in { + executePetta!("(= (swap (Pair $x $y)) (Pair $y $x)) !(swap (Pair 1 3)) !(swap (Pair x y))", *retCh) | + for(@ok <- retCh) { + stdout!(ok) + } +} diff --git a/rholang/src/rust/interpreter/rho_runtime.rs b/rholang/src/rust/interpreter/rho_runtime.rs index e3cd87314..8aa3209c4 100644 --- a/rholang/src/rust/interpreter/rho_runtime.rs +++ b/rholang/src/rust/interpreter/rho_runtime.rs @@ -1026,26 +1026,21 @@ fn std_rho_chroma_processes() -> Vec { } fn std_swipl_processes() -> Vec { - vec![ - Definition { - urn: "rho:petta:compile".to_string(), - fixed_channel: FixedChannels::swipl_compile_petta(), - arity: 2, - body_ref: BodyRefs::SWIPL_COMPILE_PETTA, - handler: Box::new(|ctx| { - Box::new(move |args| { - let ctx = ctx.clone(); - Box::pin(async move { - ctx.system_processes - .clone() - .swipl_compile_petta(args) - .await - }) - }) - }), - remainder: None, - }, - ] + vec![Definition { + urn: "rho:petta:execute".to_string(), + fixed_channel: FixedChannels::swipl_execute_petta(), + arity: 2, + body_ref: BodyRefs::SWIPL_EXECUTE_PETTA, + handler: Box::new(|ctx| { + Box::new(move |args| { + let ctx = ctx.clone(); + Box::pin( + async move { ctx.system_processes.clone().swipl_execute_petta(args).await }, + ) + }) + }), + remainder: None, + }] } fn dispatch_table_creator( diff --git a/rholang/src/rust/interpreter/swi_prolog_service.rs b/rholang/src/rust/interpreter/swi_prolog_service.rs index 7145463d3..5cc0df697 100644 --- a/rholang/src/rust/interpreter/swi_prolog_service.rs +++ b/rholang/src/rust/interpreter/swi_prolog_service.rs @@ -1,39 +1,88 @@ use std::env; use std::io::Write; -use std::path::Path; +use std::path::PathBuf; use std::process::Command; +use models::rhoapi::Par; +use serde_json::Value; use tempfile::NamedTempFile; +use crate::rust::interpreter::rho_type::{ + RhoBoolean, RhoList, RhoMap, RhoNil, RhoNumber, RhoString, +}; + use super::errors::InterpreterError; pub fn evaluate(_swi_prolog_code: &str) -> Result<(), InterpreterError> { Ok(()) } -pub fn petta_compile(metta_code: &str) -> Result { +pub fn petta_execute(metta_code: &str) -> Result { // Write the MeTTa code to a temp file let mut metta_file = NamedTempFile::new() .map_err(|_| InterpreterError::SwiplError("Can't open temp file".into()))?; - metta_file.write(metta_code.as_bytes()) + metta_file + .write(metta_code.as_bytes()) .map_err(|_| InterpreterError::SwiplError("Can't write MeTTa code to temp file".into()))?; + let metta_file_path = metta_file + .path() + .to_str() + .ok_or(InterpreterError::SwiplError( + "Can't convert metta_file path to string".into(), + ))?; + // Get the path to PeTTa - let petta_path = env::var("PETTA_PATH") - .unwrap_or("./PeTTa/src/main.pl".into()); - let petta_path = Path::new(&petta_path); - if !petta_path.exists() { + let metta_module_path: PathBuf = { + let petta_path = PathBuf::from(env::var("PETTA_PATH").unwrap_or("./PeTTa".into())); + [petta_path, PathBuf::from("src/metta.pl")].iter().collect() + }; + + if !metta_module_path.exists() { return Err(InterpreterError::SwiplError("Can't find PeTTa.".into())); } - // Execute PeTTa + let goal = format!( + r#"assertz(silent(true)), + load_metta_file('{metta_file_path}', Results), + use_module(library(json)), + json_write_dict(current_output, #{{results:Results}})."# + ); + let output = Command::new("swipl") - .arg(petta_path) - .arg(metta_file.path()) + .arg("-s") + .arg(metta_module_path) + .arg("-g") + .arg(goal) + .arg("-t") + .arg("halt") .output() .map_err(|_| InterpreterError::SwiplError("Can't translate MeTTa with PeTTa".into()))?; // Get output as string - String::from_utf8(output.stdout) - .map_err(|_| InterpreterError::SwiplError("Can't interpret PeTTa output".into())) -} \ No newline at end of file + let str_output = String::from_utf8(output.stdout) + .map_err(|_| InterpreterError::SwiplError("Can't interpret PeTTa output".into()))?; + + let value_output = serde_json::from_str::(str_output.as_str()).map_err(|_| { + InterpreterError::SwiplError("Can't parse JSON output from PeTTa execution".into()) + })?; + let par_output = value_to_par(value_output); + Ok(par_output) +} + +fn value_to_par(v: Value) -> Par { + match v { + Value::Null => RhoNil::create_par(), + Value::Bool(b) => RhoBoolean::create_par(b), + Value::Number(n) => RhoNumber::create_par(n.as_i64().unwrap()), + Value::String(s) => RhoString::create_par(s), + Value::Array(values) => RhoList::create_par(values.into_iter().map(value_to_par).collect()), + Value::Object(map) => { + let hashmap = map + .into_iter() + .map(|(k, v)| (RhoString::create_par(k), value_to_par(v))) + .collect(); + RhoMap::create_par(hashmap) + } + } +} diff --git a/rholang/src/rust/interpreter/system_processes.rs b/rholang/src/rust/interpreter/system_processes.rs index 6a676daee..f7fafee5b 100644 --- a/rholang/src/rust/interpreter/system_processes.rs +++ b/rholang/src/rust/interpreter/system_processes.rs @@ -1,5 +1,5 @@ use crate::rust::interpreter::chromadb_service::{ - CollectionEntries, Metadata, SharedChromaDBService + CollectionEntries, Metadata, SharedChromaDBService, }; use crate::rust::interpreter::rho_type::{Extractor, RhoList, RhoNil}; @@ -16,7 +16,7 @@ use super::rho_type::{ RhoBoolean, RhoByteArray, RhoDeployId, RhoDeployerId, RhoName, RhoNumber, RhoString, RhoSysAuthToken, RhoUri, }; -use super::swi_prolog_service::petta_compile; +use super::swi_prolog_service::petta_execute; use super::util::vault_address::VaultAddress; use crypto::rust::hash::blake2b256::Blake2b256; use crypto::rust::hash::keccak256::Keccak256; @@ -217,7 +217,7 @@ impl FixedChannels { byte_name(36) } - pub fn swipl_compile_petta() -> Par { + pub fn swipl_execute_petta() -> Par { byte_name(37) } } @@ -255,7 +255,7 @@ impl BodyRefs { pub const CHROMA_UPSERT_ENTRIES: i64 = 34; pub const CHROMA_QUERY: i64 = 35; pub const CHROMA_DELETE_DOCUMENTS: i64 = 36; - pub const SWIPL_COMPILE_PETTA: i64 = 37; + pub const SWIPL_EXECUTE_PETTA: i64 = 37; } pub fn non_deterministic_ops() -> HashSet { @@ -1663,7 +1663,10 @@ impl SystemProcesses { return Ok(previous_output); } - let meta = self.chromadb_service.get_collection_meta(&collection_name).await?; + let meta = self + .chromadb_service + .get_collection_meta(&collection_name) + .await?; let result_par = match meta { None => RhoNil::create_par(), Some(inner) => inner.into(), @@ -1735,7 +1738,8 @@ impl SystemProcesses { return Ok(previous_output); } - let res = self.chromadb_service + let res = self + .chromadb_service .query( &collection_name, doc_texts.iter().map(|s| s.as_ref()).collect(), @@ -1789,21 +1793,21 @@ impl SystemProcesses { // SWIPL section begin - pub async fn swipl_compile_petta( + pub async fn swipl_execute_petta( &self, contract_args: (Vec, bool, Vec), ) -> Result, InterpreterError> { let Some((produce, is_replay, previous_output, args)) = self.is_contract_call().unapply(contract_args) else { - return Err(illegal_argument_error("swipl_compile_petta")); + return Err(illegal_argument_error("swipl_execute_petta")); }; let [metta_code, ack] = args.as_slice() else { - return Err(illegal_argument_error("swipl_compile_petta")); + return Err(illegal_argument_error("swipl_execute_petta")); }; let Some(metta_code) = RhoString::unapply(metta_code) else { - return Err(illegal_argument_error("swipl_compile_petta")); + return Err(illegal_argument_error("swipl_execute_petta")); }; // Common piece of code. @@ -1812,12 +1816,10 @@ impl SystemProcesses { return Ok(previous_output); } - // Perform the compilation - let output = petta_compile(&metta_code)?; + // Perform the execution and wrap in vector + let output = petta_execute(&metta_code)?; + let output = vec![output]; - // Parse the output - let result_par = RhoString::create_par(output); - let output = vec![result_par]; produce(&output, &ack).await?; Ok(output) } From 5a143814e64755901cb942c61f0f0ca9ad0d9d78 Mon Sep 17 00:00:00 2001 From: rmgaray Date: Wed, 13 May 2026 19:58:44 +0000 Subject: [PATCH 02/12] fix: invalid numbers crashing the program --- .../rust/interpreter/swi_prolog_service.rs | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/rholang/src/rust/interpreter/swi_prolog_service.rs b/rholang/src/rust/interpreter/swi_prolog_service.rs index 5cc0df697..c66ed4f1c 100644 --- a/rholang/src/rust/interpreter/swi_prolog_service.rs +++ b/rholang/src/rust/interpreter/swi_prolog_service.rs @@ -66,23 +66,37 @@ pub fn petta_execute(metta_code: &str) -> Result { let value_output = serde_json::from_str::(str_output.as_str()).map_err(|_| { InterpreterError::SwiplError("Can't parse JSON output from PeTTa execution".into()) })?; - let par_output = value_to_par(value_output); + let par_output = value_to_par(value_output)?; Ok(par_output) } -fn value_to_par(v: Value) -> Par { +fn value_to_par(v: Value) -> Result { match v { - Value::Null => RhoNil::create_par(), - Value::Bool(b) => RhoBoolean::create_par(b), - Value::Number(n) => RhoNumber::create_par(n.as_i64().unwrap()), - Value::String(s) => RhoString::create_par(s), - Value::Array(values) => RhoList::create_par(values.into_iter().map(value_to_par).collect()), + Value::Null => Ok(RhoNil::create_par()), + Value::Bool(b) => Ok(RhoBoolean::create_par(b)), + Value::Number(n) => { + let n64 = n.as_i64().ok_or(InterpreterError::SwiplError( + "Could not parse number as i64".into(), + ))?; + Ok(RhoNumber::create_par(n64)) + } + Value::String(s) => Ok(RhoString::create_par(s)), + Value::Array(values) => { + let ps = values + .into_iter() + .map(value_to_par) + .collect::>()?; + Ok(RhoList::create_par(ps)) + } Value::Object(map) => { let hashmap = map .into_iter() - .map(|(k, v)| (RhoString::create_par(k), value_to_par(v))) - .collect(); - RhoMap::create_par(hashmap) + .map(|(k, v)| { + let p = value_to_par(v)?; + Ok((RhoString::create_par(k), p)) + }) + .collect::>()?; + Ok(RhoMap::create_par(hashmap)) } } } From 8f6712e17b5e25e35ccfbe8eaced3e1077afa198 Mon Sep 17 00:00:00 2001 From: rmgaray Date: Thu, 14 May 2026 19:08:24 +0000 Subject: [PATCH 03/12] spawn separate task for PeTTa execution and add a timeout --- .../system-contract/swipl/02-fib-long.metta | 13 +++++ .../system-contract/swipl/02-fib-long.rho | 6 +++ .../rust/interpreter/swi_prolog_service.rs | 49 +++++++++++++------ .../src/rust/interpreter/system_processes.rs | 2 +- 4 files changed, 53 insertions(+), 17 deletions(-) create mode 100644 rholang/examples/system-contract/swipl/02-fib-long.metta create mode 100644 rholang/examples/system-contract/swipl/02-fib-long.rho diff --git a/rholang/examples/system-contract/swipl/02-fib-long.metta b/rholang/examples/system-contract/swipl/02-fib-long.metta new file mode 100644 index 000000000..cc702e1e5 --- /dev/null +++ b/rholang/examples/system-contract/swipl/02-fib-long.metta @@ -0,0 +1,13 @@ +(= (fib-tr $n $a $b) + (if (== $n 0) + $a + (fib-tr (- $n 1) $b (+ $a $b)))) + +(= (fib $n) + (fib-tr $n 0 1)) + +(= (main $_) (fib 1000000)) + +!(main dummy) + + diff --git a/rholang/examples/system-contract/swipl/02-fib-long.rho b/rholang/examples/system-contract/swipl/02-fib-long.rho new file mode 100644 index 000000000..58bb2bdea --- /dev/null +++ b/rholang/examples/system-contract/swipl/02-fib-long.rho @@ -0,0 +1,6 @@ +new executePetta(`rho:petta:execute`), stdout(`rho:io:stdout`), retCh in { + executePetta!("(= (fib-tr $n $a $b) (if (== $n 0) $a (fib-tr (- $n 1) $b (+ $a $b)))) (= (fib $n) (fib-tr $n 0 1)) (= (main $x) (fib 1000000)) !(main dummy)", *retCh) | + for(@ok <- retCh) { + stdout!(ok) + } +} diff --git a/rholang/src/rust/interpreter/swi_prolog_service.rs b/rholang/src/rust/interpreter/swi_prolog_service.rs index c66ed4f1c..8cdff4f50 100644 --- a/rholang/src/rust/interpreter/swi_prolog_service.rs +++ b/rholang/src/rust/interpreter/swi_prolog_service.rs @@ -1,7 +1,8 @@ -use std::env; use std::io::Write; use std::path::PathBuf; -use std::process::Command; +use std::{env, time::Duration}; +// use std::process::Command; +use tokio::process::Command; use models::rhoapi::Par; use serde_json::Value; @@ -13,11 +14,7 @@ use crate::rust::interpreter::rho_type::{ use super::errors::InterpreterError; -pub fn evaluate(_swi_prolog_code: &str) -> Result<(), InterpreterError> { - Ok(()) -} - -pub fn petta_execute(metta_code: &str) -> Result { +pub async fn petta_execute(metta_code: &str) -> Result { // Write the MeTTa code to a temp file let mut metta_file = NamedTempFile::new() .map_err(|_| InterpreterError::SwiplError("Can't open temp file".into()))?; @@ -49,16 +46,36 @@ pub fn petta_execute(metta_code: &str) -> Result { json_write_dict(current_output, #{{results:Results}})."# ); - let output = Command::new("swipl") - .arg("-s") - .arg(metta_module_path) - .arg("-g") - .arg(goal) - .arg("-t") - .arg("halt") - .output() - .map_err(|_| InterpreterError::SwiplError("Can't translate MeTTa with PeTTa".into()))?; + // TODO: Make this a configuration parameter + let timeout_secs: u64 = 10; + let proc_handle = tokio::spawn(tokio::time::timeout( + Duration::from_secs(timeout_secs), + Command::new("swipl") + .arg("-s") + .arg(metta_module_path) + .arg("-g") + .arg(goal) + .arg("-t") + .arg("halt") + .output(), + )); + let output = proc_handle + .await + .map_err(|join_error| { + InterpreterError::SwiplError( + format!("Error while joining with the PeTTa task: {}", join_error).into(), + ) + })? + .map_err(|elapsed| { + InterpreterError::SwiplError( + format!("MeTTa execution timed out after {}", elapsed).into(), + ) + })? + .map_err(|e| { + InterpreterError::SwiplError(format!("MeTTa execution failed: {}", e).into()) + })?; + println!("{:?}", output); // Get output as string let str_output = String::from_utf8(output.stdout) .map_err(|_| InterpreterError::SwiplError("Can't interpret PeTTa output".into()))?; diff --git a/rholang/src/rust/interpreter/system_processes.rs b/rholang/src/rust/interpreter/system_processes.rs index f7fafee5b..bba3f926d 100644 --- a/rholang/src/rust/interpreter/system_processes.rs +++ b/rholang/src/rust/interpreter/system_processes.rs @@ -1817,7 +1817,7 @@ impl SystemProcesses { } // Perform the execution and wrap in vector - let output = petta_execute(&metta_code)?; + let output = petta_execute(&metta_code).await?; let output = vec![output]; produce(&output, &ack).await?; From b7ba32636d98995ff0ac6ed8bc6671b73109f54b Mon Sep 17 00:00:00 2001 From: rmgaray Date: Sun, 17 May 2026 18:58:47 +0000 Subject: [PATCH 04/12] fail if return code != 0 --- rholang/src/rust/interpreter/swi_prolog_service.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/rholang/src/rust/interpreter/swi_prolog_service.rs b/rholang/src/rust/interpreter/swi_prolog_service.rs index 8cdff4f50..059392194 100644 --- a/rholang/src/rust/interpreter/swi_prolog_service.rs +++ b/rholang/src/rust/interpreter/swi_prolog_service.rs @@ -75,7 +75,13 @@ pub async fn petta_execute(metta_code: &str) -> Result { .map_err(|e| { InterpreterError::SwiplError(format!("MeTTa execution failed: {}", e).into()) })?; - println!("{:?}", output); + + if !output.status.success() { + return Err(InterpreterError::SwiplError( + format!("PeTTa execution failed. {:#?}", output.stderr.as_slice()).into(), + )); + } + // Get output as string let str_output = String::from_utf8(output.stdout) .map_err(|_| InterpreterError::SwiplError("Can't interpret PeTTa output".into()))?; From 08df8faf77c15f389120ade45360fa9ba34626b3 Mon Sep 17 00:00:00 2001 From: rmgaray Date: Sun, 17 May 2026 19:40:45 +0000 Subject: [PATCH 05/12] provision swipl and PeTTa --- DEVELOPER.md | 8 ++++++++ flake.nix | 9 +++++++++ node/Dockerfile | 11 +++++++++++ 3 files changed, 28 insertions(+) diff --git a/DEVELOPER.md b/DEVELOPER.md index 13e6a722c..d8b66e6d0 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -5,6 +5,14 @@ __Note__ Successfully building from source requires attending to all of the prer ### Prerequisites * [Environment set up](README.md#installation). +* **SWI-Prolog** - Required runtime dependency for MeTTa smart contract execution via PeTTa submodule + - Nix/direnv users: Already provisioned in the development shell + - Manual installation: + - macOS: `brew install swi-prolog` + - Ubuntu/Debian: `apt-get install swi-prolog` + - Fedora: `dnf install pl` + - The `swipl` binary must be in your PATH + - PeTTa submodule must be initialized: `git submodule update --init --recursive`