From 274444fc04b0c3ef2240f35df4fc17727bd4706b Mon Sep 17 00:00:00 2001 From: Ahmad Date: Thu, 9 Oct 2025 16:37:44 +1000 Subject: [PATCH 1/6] Add reverse membership list POD endpoints --- ad-server/src/endpoints.rs | 57 +++++++++++++++++------ ad-server/src/queue.rs | 94 ++++++++++++++++---------------------- common/src/disk.rs | 4 ++ 3 files changed, 88 insertions(+), 67 deletions(-) diff --git a/ad-server/src/endpoints.rs b/ad-server/src/endpoints.rs index 1c1e16f..3a2c8df 100644 --- a/ad-server/src/endpoints.rs +++ b/ad-server/src/endpoints.rs @@ -1,8 +1,10 @@ -use std::sync::Arc; +use std::{path::Path, sync::Arc}; use app::Op; -use common::CustomError; -use pod2::middleware::Value; +use common::{ + CustomError, + disk::{load_pod, rev_membership_list_pod_file_name}, +}; use serde::{Deserialize, Serialize}; use uuid::Uuid; use warp::Filter; @@ -34,6 +36,21 @@ pub async fn handler_membership_list_get( Ok(warp::reply::json(&membership_list)) } +// GET /reverse_membership_list_pod/{id} +pub async fn handler_reverse_membership_list_pod_get( + id: i64, + ctx: Arc, +) -> Result { + let num = db::get_rev_membership_list(&ctx.db_pool, id) + .await + .map_err(|e| CustomError(e.to_string()))? + .num; + let rev_name = rev_membership_list_pod_file_name(id, num); + let reverse_index_pod = load_pod(Path::new(&ctx.cfg.pods_path), &rev_name) + .map_err(|e| CustomError(e.to_string()))?; + Ok(warp::reply::json(&reverse_index_pod)) +} + #[derive(Serialize, Deserialize)] pub struct QueueResp { req_id: Uuid, @@ -81,16 +98,12 @@ pub async fn handler_user_get( ctx: Arc, ) -> Result { let req_id = Uuid::now_v7(); - ctx.queue_state - .write() - .await - .insert(req_id, queue::State::Query(queue::StateQuery::Pending)); + ctx.queue_state.write().await.insert( + req_id, + queue::State::Query(Box::new(queue::StateQuery::Pending)), + ); ctx.queue_tx - .send(queue::Request::Query { - req_id, - id, - user: Value::from(user), - }) + .send(queue::Request::Query { req_id, id, user }) .await .map_err(|e| CustomError(e.to_string()))?; Ok(warp::reply::json(&QueueResp { req_id })) @@ -103,6 +116,7 @@ pub fn routes( ctx: Arc, ) -> impl Filter + Clone { membership_list_get(ctx.clone()) + .or(reverse_membership_list_pod_get(ctx.clone())) .or(request_get(ctx.clone())) .or(membership_list_create(ctx.clone())) .or(membership_list_update(ctx.clone())) @@ -124,6 +138,15 @@ fn membership_list_get( .and(with_ctx(ctx)) .and_then(handler_membership_list_get) } +fn reverse_membership_list_pod_get( + ctx: Arc, +) -> impl Filter + Clone { + warp::path!("reverse_membership_list_pod" / i64) + .and(warp::get()) + .and(with_ctx(ctx)) + .and_then(handler_reverse_membership_list_pod_get) +} + fn membership_list_create( ctx: Arc, ) -> impl Filter + Clone { @@ -333,7 +356,7 @@ mod tests { assert_eq!(res.status(), StatusCode::OK); let resp: queue::State = serde_json::from_slice(res.body()).expect(""); match resp { - queue::State::Query(state_query) => match state_query { + queue::State::Query(state_query) => match *state_query { queue::StateQuery::Complete { result } => { println!("{:?}", result); break; @@ -345,6 +368,14 @@ mod tests { } } + // Get reverse membership list POD + let res = warp::test::request() + .method("GET") + .path("/reverse_membership_list_pod/1") + .reply(&api) + .await; + assert_eq!(res.status(), StatusCode::OK); + // Delete Alice. helper_membership_list_update( &api, diff --git a/ad-server/src/queue.rs b/ad-server/src/queue.rs index 85aad41..5ab4b1b 100644 --- a/ad-server/src/queue.rs +++ b/ad-server/src/queue.rs @@ -1,11 +1,11 @@ -use std::{collections::HashMap, path::Path, str::FromStr, sync::Arc}; +use std::{path::Path, sync::Arc}; use alloy::primitives::TxHash; -use anyhow::{Result, anyhow}; -use app::{Group, Helper, Op, RevHelper}; +use anyhow::Result; +use app::{Helper, Op, RevHelper}; use common::{ ProofType, - disk::{load_pod, store_pod}, + disk::{load_pod, rev_membership_list_pod_file_name, store_pod}, groth, payload::{Payload, PayloadCreate, PayloadProof, PayloadUpdate}, set_from_value, @@ -15,7 +15,10 @@ use pod2::{ backends::plonky2::{mainpod::Prover, primitives::merkletree::MerkleClaimAndProof}, dict, frontend::MainPodBuilder, - middleware::{Hash, RawValue, Statement, TypedValue, Value, containers::Dictionary}, + middleware::{ + Hash, RawValue, Statement, TypedValue, Value, + containers::{Dictionary, Set}, + }, }; use serde::{Deserialize, Serialize}; use tokio::{sync::mpsc::Receiver, task}; @@ -29,7 +32,7 @@ pub enum State { Create(StateCreate), Update(StateUpdate), UpdateRev(StateUpdateRev), - Query(StateQuery), + Query(Box), } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -61,9 +64,7 @@ pub enum StateUpdateRev { #[derive(Clone, Debug, Serialize, Deserialize)] pub enum StateQuery { Pending, - Complete { - result: HashMap, - }, + Complete { result: (Set, MerkleClaimAndProof) }, Error(String), } @@ -72,7 +73,7 @@ pub enum Request { Create { req_id: Uuid }, Update { req_id: Uuid, id: i64, op: Op }, UpdateRev { req_id: Uuid, id: i64, num: i64 }, - Query { req_id: Uuid, id: i64, user: Value }, + Query { req_id: Uuid, id: i64, user: String }, } pub async fn handle_loop(ctx: Arc, mut queue_rx: Receiver) { @@ -120,10 +121,10 @@ pub async fn handle_req(ctx: Arc, req: Request) -> Result<()> { Request::Query { req_id, id, user } => { if let Err(err) = handle_query(ctx.clone(), req_id, id, user).await { debug!(req_id = format!("{}", req_id), err = format!("{}", err)); - ctx.queue_state - .write() - .await - .insert(req_id, State::Query(StateQuery::Error(err.to_string()))); + ctx.queue_state.write().await.insert( + req_id, + State::Query(Box::new(StateQuery::Error(err.to_string()))), + ); } } } @@ -291,7 +292,7 @@ async fn handle_update_rev(ctx: Arc, req_id: Uuid, id: i64, num: i64) - }; let (old_rev_state_pod, rev_state) = if num > 1 { - let rev_name = format!("{:08}-{:08}-rev_membership_list", id, num - 1); + let rev_name = rev_membership_list_pod_file_name(id, num - 1); let old_rev_state_pod = load_pod(Path::new(&ctx.cfg.pods_path), &rev_name)?; let rev_state = db::get_rev_membership_list(&ctx.db_pool, id).await?.state; (Some(old_rev_state_pod), rev_state.0) @@ -333,7 +334,7 @@ async fn handle_update_rev(ctx: Arc, req_id: Uuid, id: i64, num: i64) - store_pod( Path::new(&ctx.cfg.pods_path), - &format!("{:08}-{:08}-rev_membership_list", id, num), + &rev_membership_list_pod_file_name(id, num), &rev_state_pod, )?; @@ -342,51 +343,36 @@ async fn handle_update_rev(ctx: Arc, req_id: Uuid, id: i64, num: i64) - Ok(()) } -async fn handle_query(ctx: Arc, req_id: Uuid, id: i64, user: Value) -> Result<()> { +async fn handle_query(ctx: Arc, req_id: Uuid, id: i64, user: String) -> Result<()> { let set_req_state = async |req_state| { ctx.queue_state .write() .await - .insert(req_id, State::Query(req_state)); + .insert(req_id, State::Query(Box::new(req_state))); }; // get state from db - let state = db::get_membership_list(&ctx.db_pool, id).await?.state.0; - - let dict_kvs = state - .kvs() - .iter() - .map(|(group, v)| { - set_from_value(v).and_then(|s| { - Group::from_str(group.name()) - .map_err(|_| anyhow!("Invalid group: {}", group)) - .map(|group| (group, s)) - }) - }) - .collect::>>()?; - - let result = dict_kvs - .into_iter() - .filter_map(|(group, s)| { - s.contains(&user).then(|| { - s.prove(&user) - .map(|proof| { - ( - group, - MerkleClaimAndProof { - root: s.commitment(), - key: user.raw(), - value: user.raw(), - proof, - }, - ) - }) - .map_err(|e| e.into()) - }) - }) - .collect::>>()?; - - set_req_state(StateQuery::Complete { result }).await; + let state = db::get_rev_membership_list(&ctx.db_pool, id).await?.state.0; + + // Get Merkle proof + groups to which the user belongs + let pf_with_groups = state.prove(&user.clone().into()); + + match pf_with_groups { + Err(e) => set_req_state(StateQuery::Error(format!("{}", e))).await, + Ok((groups, proof)) => { + let result = ( + set_from_value(groups)?, + MerkleClaimAndProof { + root: state.commitment(), + key: Value::from(user).raw(), + value: groups.raw(), + proof, + }, + ); + + set_req_state(StateQuery::Complete { result }).await; + } + } Ok(()) } diff --git a/common/src/disk.rs b/common/src/disk.rs index 3e81576..fd93a0f 100644 --- a/common/src/disk.rs +++ b/common/src/disk.rs @@ -28,3 +28,7 @@ pub fn load_pod(path: &Path, name: &str) -> Result { let pod: MainPod = serde_json::from_slice(&pod_json)?; Ok(pod) } + +pub fn rev_membership_list_pod_file_name(id: i64, num: i64) -> String { + format!("{id:08}-{num:08}-rev_membership_list") +} From fcacc258470f9d595f03dc5ce462dba5561e0b8d Mon Sep 17 00:00:00 2001 From: Ahmad Date: Fri, 10 Oct 2025 19:25:55 +1000 Subject: [PATCH 2/6] Remove unnecessary comment --- ad-server/src/db.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/ad-server/src/db.rs b/ad-server/src/db.rs index 099c7e7..a7ab1a3 100644 --- a/ad-server/src/db.rs +++ b/ad-server/src/db.rs @@ -145,5 +145,3 @@ pub async fn update_rev_membership_list( .await?; Ok(()) } - -// user/NAME (get groups that user NAME belongs to): /user/MEMBER. Returns { "red": MERKLE_PF, ... } From 7a47fe65a5a809cbff5df98cdeea12c50c972815 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Fri, 10 Oct 2025 19:33:07 +1000 Subject: [PATCH 3/6] Test + Clippy --- ad-server/src/endpoints.rs | 5 ++++- ad-server/src/queue.rs | 9 +++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/ad-server/src/endpoints.rs b/ad-server/src/endpoints.rs index 3a2c8df..caf3f94 100644 --- a/ad-server/src/endpoints.rs +++ b/ad-server/src/endpoints.rs @@ -185,7 +185,9 @@ fn with_ctx( mod tests { use app::Group; use common::shrink::ShrunkMainPodSetup; - use pod2::{backends::plonky2::basetypes::DEFAULT_VD_SET, middleware::Params}; + use pod2::{ + backends::plonky2::basetypes::DEFAULT_VD_SET, frontend::MainPod, middleware::Params, + }; use tokio::{ sync::mpsc, task, @@ -375,6 +377,7 @@ mod tests { .reply(&api) .await; assert_eq!(res.status(), StatusCode::OK); + serde_json::from_slice::(res.body()).expect("Should be a MainPod."); // Delete Alice. helper_membership_list_update( diff --git a/ad-server/src/queue.rs b/ad-server/src/queue.rs index 5ab4b1b..915c37f 100644 --- a/ad-server/src/queue.rs +++ b/ad-server/src/queue.rs @@ -64,7 +64,9 @@ pub enum StateUpdateRev { #[derive(Clone, Debug, Serialize, Deserialize)] pub enum StateQuery { Pending, - Complete { result: (Set, MerkleClaimAndProof) }, + Complete { + result: Box<(Set, MerkleClaimAndProof)>, + }, Error(String), } @@ -370,7 +372,10 @@ async fn handle_query(ctx: Arc, req_id: Uuid, id: i64, user: String) -> }, ); - set_req_state(StateQuery::Complete { result }).await; + set_req_state(StateQuery::Complete { + result: Box::new(result), + }) + .await; } } From 38684b72efb6eed2c4ee40e730af887619538cd2 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Fri, 10 Oct 2025 20:16:05 +1000 Subject: [PATCH 4/6] Add requests to flow --- client.sh | 4 ++++ full-flow-requests.sh | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/client.sh b/client.sh index 10aec3c..32fe6f5 100755 --- a/client.sh +++ b/client.sh @@ -46,6 +46,10 @@ case "$1" in user=$3 resp=$(curl $CURL_OPTS -X GET "$BASE_URL/user/$ad_id/$user") ;; + reverse_membership_list_pod_get) + ad_id=$2 + resp=$(curl $CURL_OPTS -X GET "$BASE_URL/reverse_membership_list_pod/$ad_id") + ;; *) usage exit 1 diff --git a/full-flow-requests.sh b/full-flow-requests.sh index c8fe041..7a4c4a5 100755 --- a/full-flow-requests.sh +++ b/full-flow-requests.sh @@ -20,6 +20,12 @@ echo -e "\nadd to membership_list, response:" echo -e "\ngetting membership_list, response:" ./client.sh --wait-complete membership_list_get 1 +echo -e "\ngetting reverse membership list POD, response:" +./client.sh reverse_membership_list_pod_get 1 + +echo -e "\ngetting proof of membership, response:" +./client.sh --wait-complete user_get 1 alice + echo -e "\ndel from membership_list, response:" ./client.sh --wait-complete membership_list_update 1 '{"del":{"group":"blue","user":"alice"}}' From 1468e0059b77f00dd42a8a35de467f7a7a874545 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Mon, 13 Oct 2025 11:16:08 +1000 Subject: [PATCH 5/6] Expose query result as named fields --- ad-server/src/endpoints.rs | 9 ++++++--- ad-server/src/queue.rs | 16 ++++++++++++---- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/ad-server/src/endpoints.rs b/ad-server/src/endpoints.rs index caf3f94..63c463d 100644 --- a/ad-server/src/endpoints.rs +++ b/ad-server/src/endpoints.rs @@ -186,7 +186,9 @@ mod tests { use app::Group; use common::shrink::ShrunkMainPodSetup; use pod2::{ - backends::plonky2::basetypes::DEFAULT_VD_SET, frontend::MainPod, middleware::Params, + backends::plonky2::basetypes::DEFAULT_VD_SET, + frontend::MainPod, + middleware::{Params, Value}, }; use tokio::{ sync::mpsc, @@ -359,8 +361,9 @@ mod tests { let resp: queue::State = serde_json::from_slice(res.body()).expect(""); match resp { queue::State::Query(state_query) => match *state_query { - queue::StateQuery::Complete { result } => { - println!("{:?}", result); + queue::StateQuery::Complete { groups, proof } => { + assert_eq!(proof.value, Value::from(groups).raw()); + assert_eq!(proof.key, Value::from("alice").raw()); break; } queue::StateQuery::Error(e) => panic!("StateQuery::Error: {}", e), diff --git a/ad-server/src/queue.rs b/ad-server/src/queue.rs index 915c37f..b595ba5 100644 --- a/ad-server/src/queue.rs +++ b/ad-server/src/queue.rs @@ -65,7 +65,8 @@ pub enum StateUpdateRev { pub enum StateQuery { Pending, Complete { - result: Box<(Set, MerkleClaimAndProof)>, + groups: Set, + proof: Box, }, Error(String), } @@ -360,9 +361,15 @@ async fn handle_query(ctx: Arc, req_id: Uuid, id: i64, user: String) -> let pf_with_groups = state.prove(&user.clone().into()); match pf_with_groups { - Err(e) => set_req_state(StateQuery::Error(format!("{}", e))).await, + Err(_) => { + set_req_state(StateQuery::Error(format!( + r#"User "{}" is not a member of any group."#, + user + ))) + .await + } Ok((groups, proof)) => { - let result = ( + let (groups, proof) = ( set_from_value(groups)?, MerkleClaimAndProof { root: state.commitment(), @@ -373,7 +380,8 @@ async fn handle_query(ctx: Arc, req_id: Uuid, id: i64, user: String) -> ); set_req_state(StateQuery::Complete { - result: Box::new(result), + groups, + proof: Box::new(proof), }) .await; } From a7d2912096358b33a1a272e7e2dde0ea6787158c Mon Sep 17 00:00:00 2001 From: Ahmad Date: Tue, 14 Oct 2025 06:23:48 +1000 Subject: [PATCH 6/6] Code review --- client.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client.sh b/client.sh index 32fe6f5..0d42ba8 100755 --- a/client.sh +++ b/client.sh @@ -46,7 +46,7 @@ case "$1" in user=$3 resp=$(curl $CURL_OPTS -X GET "$BASE_URL/user/$ad_id/$user") ;; - reverse_membership_list_pod_get) + reverse_membership_list_pod_get) ad_id=$2 resp=$(curl $CURL_OPTS -X GET "$BASE_URL/reverse_membership_list_pod/$ad_id") ;;