Skip to content

Commit d94b37f

Browse files
committed
Create multiple PODs where resource limits for a single POD are exceeded
1 parent 813a86c commit d94b37f

File tree

6 files changed

+1669
-0
lines changed

6 files changed

+1669
-0
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ serde_bytes = "0.11"
4040
serde_arrays = "0.2.0"
4141
sha2 = { version = "0.10.9" }
4242
rand_chacha = "0.3.1"
43+
good_lp = { version = "1.8", default-features = false, features = ["microlp"] }
4344

4445
# Uncomment for debugging with https://github.com/ed255/plonky2/ at branch `feat/debug`. The repo directory needs to be checked out next to the pod2 repo directory.
4546
# [patch."https://github.com/0xPARC/plonky2"]

src/frontend/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@ use crate::middleware::{
2121

2222
mod custom;
2323
mod error;
24+
pub mod multi_pod;
2425
mod operation;
2526
mod pod_request;
2627
mod serialization;
2728
pub use custom::*;
2829
pub use error::*;
30+
pub use multi_pod::{MultiPodBuilder, MultiPodResult, MultiPodSolution};
2931
pub use operation::*;
3032
pub use pod_request::*;
3133

src/frontend/multi_pod/cost.rs

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
//! Resource cost analysis for statements and operations.
2+
//!
3+
//! This module provides cost analysis for multi-POD packing. Each operation
4+
//! consumes various resources that have per-POD limits.
5+
6+
use std::collections::HashSet;
7+
8+
use crate::{
9+
frontend::Operation,
10+
middleware::{CustomPredicateBatch, Hash, NativeOperation, OperationType, Params},
11+
};
12+
13+
/// Unique identifier for a custom predicate batch.
14+
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
15+
pub struct CustomBatchId(pub Hash);
16+
17+
impl From<&CustomPredicateBatch> for CustomBatchId {
18+
fn from(batch: &CustomPredicateBatch) -> Self {
19+
Self(batch.id())
20+
}
21+
}
22+
23+
/// Resource costs for a single statement/operation.
24+
///
25+
/// Each field corresponds to a resource with a per-POD limit in `Params`.
26+
#[derive(Clone, Debug, Default)]
27+
pub struct StatementCost {
28+
/// Number of merkle proofs used (for Contains/NotContains).
29+
/// Limit: `params.max_merkle_proofs_containers`
30+
pub merkle_proofs: usize,
31+
32+
/// Number of merkle tree state transition proofs (for Insert/Update/Delete).
33+
/// Limit: `params.max_merkle_tree_state_transition_proofs_containers`
34+
pub merkle_state_transitions: usize,
35+
36+
/// Number of custom predicate verifications.
37+
/// Limit: `params.max_custom_predicate_verifications`
38+
pub custom_pred_verifications: usize,
39+
40+
/// Number of SignedBy operations.
41+
/// Limit: `params.max_signed_by`
42+
pub signed_by: usize,
43+
44+
/// Number of PublicKeyOf operations.
45+
/// Limit: `params.max_public_key_of`
46+
pub public_key_of: usize,
47+
48+
/// Custom predicate batches used (for batch cardinality constraint).
49+
/// Limit: `params.max_custom_predicate_batches` distinct batches per POD.
50+
pub custom_batch_ids: HashSet<CustomBatchId>,
51+
}
52+
53+
impl StatementCost {
54+
/// Compute the resource cost of an operation.
55+
pub fn from_operation(op: &Operation) -> Self {
56+
let mut cost = Self::default();
57+
58+
match &op.0 {
59+
OperationType::Native(native_op) => {
60+
match native_op {
61+
// Operations that use merkle proofs
62+
NativeOperation::ContainsFromEntries
63+
| NativeOperation::NotContainsFromEntries
64+
| NativeOperation::DictContainsFromEntries
65+
| NativeOperation::DictNotContainsFromEntries
66+
| NativeOperation::SetContainsFromEntries
67+
| NativeOperation::SetNotContainsFromEntries
68+
| NativeOperation::ArrayContainsFromEntries => {
69+
cost.merkle_proofs = 1;
70+
}
71+
72+
// Operations that use merkle state transitions
73+
NativeOperation::ContainerInsertFromEntries
74+
| NativeOperation::ContainerUpdateFromEntries
75+
| NativeOperation::ContainerDeleteFromEntries
76+
| NativeOperation::DictInsertFromEntries
77+
| NativeOperation::DictUpdateFromEntries
78+
| NativeOperation::DictDeleteFromEntries
79+
| NativeOperation::SetInsertFromEntries
80+
| NativeOperation::SetDeleteFromEntries
81+
| NativeOperation::ArrayUpdateFromEntries => {
82+
cost.merkle_state_transitions = 1;
83+
}
84+
85+
// SignedBy operation
86+
NativeOperation::SignedBy => {
87+
cost.signed_by = 1;
88+
}
89+
90+
// PublicKeyOf operation
91+
NativeOperation::PublicKeyOf => {
92+
cost.public_key_of = 1;
93+
}
94+
95+
// Operations with no special resource costs
96+
NativeOperation::None
97+
| NativeOperation::CopyStatement
98+
| NativeOperation::EqualFromEntries
99+
| NativeOperation::NotEqualFromEntries
100+
| NativeOperation::LtEqFromEntries
101+
| NativeOperation::LtFromEntries
102+
| NativeOperation::TransitiveEqualFromStatements
103+
| NativeOperation::LtToNotEqual
104+
| NativeOperation::SumOf
105+
| NativeOperation::ProductOf
106+
| NativeOperation::MaxOf
107+
| NativeOperation::HashOf
108+
// Syntactic sugar variants (lowered before proving)
109+
| NativeOperation::GtEqFromEntries
110+
| NativeOperation::GtFromEntries
111+
| NativeOperation::GtToNotEqual => {}
112+
}
113+
}
114+
OperationType::Custom(cpr) => {
115+
cost.custom_pred_verifications = 1;
116+
cost.custom_batch_ids
117+
.insert(CustomBatchId::from(&*cpr.batch));
118+
}
119+
}
120+
121+
cost
122+
}
123+
}
124+
125+
/// Aggregate costs for multiple statements.
126+
#[derive(Clone, Debug, Default)]
127+
pub struct AggregateCost {
128+
pub statements: usize,
129+
pub merkle_proofs: usize,
130+
pub merkle_state_transitions: usize,
131+
pub custom_pred_verifications: usize,
132+
pub signed_by: usize,
133+
pub public_key_of: usize,
134+
pub custom_batch_ids: HashSet<CustomBatchId>,
135+
}
136+
137+
impl AggregateCost {
138+
pub fn new() -> Self {
139+
Self::default()
140+
}
141+
142+
/// Add a statement's cost to the aggregate.
143+
pub fn add(&mut self, cost: &StatementCost) {
144+
self.statements += 1;
145+
self.merkle_proofs += cost.merkle_proofs;
146+
self.merkle_state_transitions += cost.merkle_state_transitions;
147+
self.custom_pred_verifications += cost.custom_pred_verifications;
148+
self.signed_by += cost.signed_by;
149+
self.public_key_of += cost.public_key_of;
150+
self.custom_batch_ids
151+
.extend(cost.custom_batch_ids.iter().cloned());
152+
}
153+
154+
/// Compute a lower bound on the number of PODs needed.
155+
///
156+
/// This is a quick heuristic that can be used for branch pruning
157+
/// in a logic solver without running the full MILP.
158+
pub fn lower_bound_pods(&self, params: &Params) -> usize {
159+
let by_statements = self.statements.div_ceil(params.max_statements);
160+
let by_merkle_proofs = self
161+
.merkle_proofs
162+
.div_ceil(params.max_merkle_proofs_containers);
163+
let by_merkle_transitions = self
164+
.merkle_state_transitions
165+
.div_ceil(params.max_merkle_tree_state_transition_proofs_containers);
166+
let by_custom_verifications = self
167+
.custom_pred_verifications
168+
.div_ceil(params.max_custom_predicate_verifications);
169+
let by_signed_by = self.signed_by.div_ceil(params.max_signed_by);
170+
let by_public_key_of = self.public_key_of.div_ceil(params.max_public_key_of);
171+
let by_batches = self
172+
.custom_batch_ids
173+
.len()
174+
.div_ceil(params.max_custom_predicate_batches);
175+
176+
[
177+
by_statements,
178+
by_merkle_proofs,
179+
by_merkle_transitions,
180+
by_custom_verifications,
181+
by_signed_by,
182+
by_public_key_of,
183+
by_batches,
184+
]
185+
.into_iter()
186+
.max()
187+
.unwrap_or(1)
188+
.max(1) // At least 1 POD
189+
}
190+
}
191+
192+
/// Quick estimate of POD count for a set of operations.
193+
///
194+
/// This is useful for the logic solver to estimate costs without
195+
/// running the full MILP solver.
196+
pub fn estimate_pod_count(operations: &[Operation], params: &Params) -> usize {
197+
let mut aggregate = AggregateCost::new();
198+
for op in operations {
199+
aggregate.add(&StatementCost::from_operation(op));
200+
}
201+
// Add a fudge factor for dependency overhead and public statement constraints
202+
let lower_bound = aggregate.lower_bound_pods(params);
203+
// Heuristic: add ~20% overhead for dependencies
204+
lower_bound + (lower_bound / 5).max(0)
205+
}
206+
207+
#[cfg(test)]
208+
mod tests {
209+
use super::*;
210+
use crate::{
211+
frontend::Operation as FrontendOp,
212+
middleware::{NativeOperation, OperationAux, OperationType},
213+
};
214+
215+
fn make_native_op(native_op: NativeOperation) -> FrontendOp {
216+
FrontendOp(OperationType::Native(native_op), vec![], OperationAux::None)
217+
}
218+
219+
#[test]
220+
fn test_cost_from_native_ops() {
221+
let _params = Params::default();
222+
223+
// Test merkle proof ops
224+
let contains_op = make_native_op(NativeOperation::ContainsFromEntries);
225+
let cost = StatementCost::from_operation(&contains_op);
226+
assert_eq!(cost.merkle_proofs, 1);
227+
assert_eq!(cost.merkle_state_transitions, 0);
228+
229+
// Test merkle state transition ops
230+
let insert_op = make_native_op(NativeOperation::ContainerInsertFromEntries);
231+
let cost = StatementCost::from_operation(&insert_op);
232+
assert_eq!(cost.merkle_proofs, 0);
233+
assert_eq!(cost.merkle_state_transitions, 1);
234+
235+
// Test signed_by
236+
let signed_op = make_native_op(NativeOperation::SignedBy);
237+
let cost = StatementCost::from_operation(&signed_op);
238+
assert_eq!(cost.signed_by, 1);
239+
240+
// Test public_key_of
241+
let pk_op = make_native_op(NativeOperation::PublicKeyOf);
242+
let cost = StatementCost::from_operation(&pk_op);
243+
assert_eq!(cost.public_key_of, 1);
244+
}
245+
246+
#[test]
247+
fn test_aggregate_cost() {
248+
let params = Params::default();
249+
250+
let mut aggregate = AggregateCost::new();
251+
252+
// Add some operations
253+
for _ in 0..10 {
254+
aggregate.add(&StatementCost::from_operation(&make_native_op(
255+
NativeOperation::ContainsFromEntries,
256+
)));
257+
}
258+
259+
assert_eq!(aggregate.statements, 10);
260+
assert_eq!(aggregate.merkle_proofs, 10);
261+
262+
// Should need at least 1 POD
263+
let lower_bound = aggregate.lower_bound_pods(&params);
264+
assert!(lower_bound >= 1);
265+
}
266+
267+
#[test]
268+
fn test_lower_bound_by_statements() {
269+
let params = Params {
270+
max_statements: 10,
271+
..Params::default()
272+
};
273+
274+
let mut aggregate = AggregateCost::new();
275+
aggregate.statements = 25;
276+
277+
// 25 statements / 10 per pod = 3 pods
278+
assert_eq!(aggregate.lower_bound_pods(&params), 3);
279+
}
280+
}

0 commit comments

Comments
 (0)