MultiPodBuilder improvements (#473)

- Export MultiPodBuilder Error
- Redefine MultiPodBuilder error to wrap the frontend Error (this way we get better formatting instead of an embedded string)
- handle initial wildcard values in `MultiPodBuilder.op` just like the `MainPodBuilder` does
This commit is contained in:
Eduard S. 2026-02-06 10:53:01 +01:00 committed by GitHub
parent b04560c362
commit 2bd99ef322
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 52 additions and 49 deletions

View file

@ -28,7 +28,8 @@ mod serialization;
pub use custom::*;
pub use error::*;
pub use multi_pod::{
MultiPodBuilder, MultiPodResult, MultiPodSolution, Options as MultiPodOptions,
Error as MultiPodError, MultiPodBuilder, MultiPodResult, MultiPodSolution,
Options as MultiPodOptions,
};
pub use operation::*;
pub use pod_request::*;

View file

@ -51,7 +51,7 @@ use std::collections::{BTreeSet, HashMap};
use crate::{
frontend::{MainPod, MainPodBuilder, Operation, OperationArg},
middleware::{Hash, MainPodProver, Params, Statement, VDSet},
middleware::{Hash, MainPodProver, Params, Statement, VDSet, Value},
};
mod cost;
@ -63,10 +63,11 @@ use deps::{DependencyGraph, StatementSource};
pub use solver::MultiPodSolution;
/// Error type for multi-POD operations.
#[derive(Debug, Clone)]
#[derive(Debug, thiserror::Error)]
pub enum Error {
Custom(String),
/// Error from the frontend.
Frontend(String),
Frontend(#[from] crate::frontend::Error),
/// Error from the MILP solver.
Solver(String),
/// No solution exists (shouldn't happen with valid input).
@ -76,21 +77,14 @@ pub enum Error {
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::Frontend(msg) => write!(f, "Frontend error: {}", msg),
Error::Custom(msg) => write!(f, "Custom error: {}", msg),
Error::Frontend(e) => write!(f, "Frontend error: {}", e),
Error::Solver(msg) => write!(f, "Solver error: {}", msg),
Error::NoSolution => write!(f, "No solution exists"),
}
}
}
impl std::error::Error for Error {}
impl From<crate::frontend::Error> for Error {
fn from(e: crate::frontend::Error) -> Self {
Error::Frontend(e.to_string())
}
}
pub type Result<T> = std::result::Result<T, Error>;
/// Default maximum number of PODs the solver will consider.
@ -175,6 +169,8 @@ pub struct MultiPodBuilder {
statements: Vec<Statement>,
/// Operations that produce each statement.
operations: Vec<Operation>,
/// Optional initial wildcard values for custom operations
operations_wildcard_values: Vec<Vec<(usize, Value)>>,
/// Indices of statements that should be public in output PODs.
/// Uses Vec since max_public_statements is small (≤8); indices are naturally sorted.
output_public_indices: Vec<usize>,
@ -193,6 +189,7 @@ pub struct SolvedMultiPod {
input_pods: Vec<MainPod>,
statements: Vec<Statement>,
operations: Vec<Operation>,
operations_wildcard_values: Vec<Vec<(usize, Value)>>,
solution: MultiPodSolution,
deps: DependencyGraph,
}
@ -313,6 +310,7 @@ impl SolvedMultiPod {
for &stmt_idx in &statements_sorted {
let is_public = public_set.contains(&stmt_idx);
let mut op = self.operations[stmt_idx].clone();
let wildcard_values = self.operations_wildcard_values[stmt_idx].clone();
// Remap Statement arguments that reference locally-proved statements.
// For external dependencies (from input PODs including earlier generated PODs),
@ -330,17 +328,13 @@ impl SolvedMultiPod {
}
}
let stmt = builder
.op(is_public, vec![], op)
.map_err(|e| Error::Frontend(e.to_string()))?;
let stmt = builder.op(is_public, wildcard_values, op)?;
added_statements.insert(stmt_idx, stmt);
}
// Step 4: Prove the POD
let pod = builder
.prove(prover)
.map_err(|e| Error::Frontend(e.to_string()))?;
let pod = builder.prove(prover)?;
Ok(pod)
}
@ -370,34 +364,48 @@ impl MultiPodBuilder {
input_pods: Vec::new(),
statements: Vec::new(),
operations: Vec::new(),
operations_wildcard_values: Vec::new(),
output_public_indices: Vec::new(),
}
}
/// Add an external input POD.
pub fn add_pod(&mut self, pod: MainPod) -> Result<()> {
self.builder
.add_pod(pod.clone())
.map_err(|e| Error::Frontend(e.to_string()))?;
self.builder.add_pod(pod.clone())?;
self.input_pods.push(pod);
Ok(())
}
/// Add a public operation (statement will be public in output).
pub fn pub_op(&mut self, op: Operation) -> Result<Statement> {
let stmt = self.add_operation(op)?;
// Index is always new (just added), so push without duplicate check
self.output_public_indices.push(self.statements.len() - 1);
Ok(stmt)
self.op(true, vec![], op)
}
/// Add a private operation.
pub fn priv_op(&mut self, op: Operation) -> Result<Statement> {
self.add_operation(op)
self.op(false, vec![], op)
}
pub fn op(
&mut self,
public: bool,
wildcard_values: Vec<(usize, Value)>,
op: Operation,
) -> Result<Statement> {
let stmt = self.add_operation(wildcard_values, op)?;
if public {
// Index is always new (just added), so push without duplicate check
self.output_public_indices.push(self.statements.len() - 1);
}
Ok(stmt)
}
/// Internal: Add an operation and create its statement.
fn add_operation(&mut self, op: Operation) -> Result<Statement> {
fn add_operation(
&mut self,
wildcard_values: Vec<(usize, Value)>,
op: Operation,
) -> Result<Statement> {
// Get or create the cached builder
//
// NOTE: We clone input pods here because MainPodBuilder takes ownership.
@ -407,11 +415,11 @@ impl MultiPodBuilder {
// while existing code using MainPodBuilder (with the default) would be unaffected.
let stmt = self
.builder
.op(false, vec![], op.clone())
.map_err(|e| Error::Frontend(e.to_string()))?;
.op(false, wildcard_values.clone(), op.clone())?;
self.statements.push(stmt.clone());
self.operations.push(op);
self.operations_wildcard_values.push(wildcard_values);
Ok(stmt)
}
@ -427,7 +435,7 @@ impl MultiPodBuilder {
}
Ok(())
} else {
Err(Error::Frontend(
Err(Error::Custom(
"reveal() called with statement not found in builder".to_string(),
))
}
@ -511,6 +519,7 @@ impl MultiPodBuilder {
input_pods: self.input_pods,
statements: self.statements,
operations: self.operations,
operations_wildcard_values: self.operations_wildcard_values,
solution,
deps,
})
@ -570,10 +579,7 @@ mod tests {
assert!(result.intermediate_pods().is_empty());
// Verify the POD
result.pods[0]
.pod
.verify()
.map_err(|e| Error::Frontend(e.to_string()))?;
result.pods[0].pod.verify().unwrap();
Ok(())
}
@ -624,7 +630,7 @@ mod tests {
for (i, pod) in result.pods.iter().enumerate() {
pod.pod
.verify()
.map_err(|e| Error::Frontend(format!("POD {} verification failed: {}", i, e)))?;
.unwrap_or_else(|_| panic!("POD {} verification failed", i));
}
Ok(())
@ -745,7 +751,7 @@ mod tests {
for (i, pod) in result.pods.iter().enumerate() {
pod.pod
.verify()
.map_err(|e| Error::Frontend(format!("POD {} verification failed: {}", i, e)))?;
.unwrap_or_else(|_| panic!("POD {} verification failed", i));
}
Ok(())
@ -796,7 +802,7 @@ mod tests {
for (i, pod) in result.pods.iter().enumerate() {
pod.pod
.verify()
.map_err(|e| Error::Frontend(format!("POD {} verification failed: {}", i, e)))?;
.unwrap_or_else(|_| panic!("POD {} verification failed", i));
}
Ok(())
@ -947,7 +953,7 @@ mod tests {
for (i, pod) in result.pods.iter().enumerate() {
pod.pod
.verify()
.map_err(|e| Error::Frontend(format!("POD {} verification failed: {}", i, e)))?;
.unwrap_or_else(|_| panic!("POD {} verification failed", i));
}
Ok(())
@ -1187,11 +1193,7 @@ mod tests {
let result = solved.prove(&prover)?;
assert_eq!(result.pods.len(), 1);
result
.output_pod()
.pod
.verify()
.map_err(|e| Error::Frontend(format!("Output POD verification failed: {}", e)))?;
result.output_pod().pod.verify().unwrap();
Ok(())
}
@ -1243,7 +1245,7 @@ mod tests {
for (i, pod) in result.pods.iter().enumerate() {
pod.pod
.verify()
.map_err(|e| Error::Frontend(format!("POD {} verification failed: {}", i, e)))?;
.unwrap_or_else(|_| panic!("POD {} verification failed", i));
}
Ok(())
@ -1307,7 +1309,7 @@ mod tests {
for (i, pod) in result.pods.iter().enumerate() {
pod.pod
.verify()
.map_err(|e| Error::Frontend(format!("POD {} verification failed: {}", i, e)))?;
.unwrap_or_else(|_| panic!("POD {} verification failed", i));
}
Ok(())
@ -1366,7 +1368,7 @@ mod tests {
for (i, pod) in result.pods.iter().enumerate() {
pod.pod
.verify()
.map_err(|e| Error::Frontend(format!("POD {} verification failed: {}", i, e)))?;
.unwrap_or_else(|_| panic!("POD {} verification failed", i));
}
Ok(())
@ -1491,7 +1493,7 @@ mod tests {
for (i, pod) in result.pods.iter().enumerate() {
pod.pod
.verify()
.map_err(|e| Error::Frontend(format!("POD {} verification failed: {}", i, e)))?;
.unwrap_or_else(|_| panic!("POD {} verification failed", i));
}
Ok(())
@ -1608,7 +1610,7 @@ mod tests {
for (i, pod) in result.pods.iter().enumerate() {
pod.pod
.verify()
.map_err(|e| Error::Frontend(format!("POD {} verification failed: {}", i, e)))?;
.unwrap_or_else(|_| panic!("POD {} verification failed", i));
}
Ok(())