From dab1a4dd2cc286ca51e9411eec811734ab581dad Mon Sep 17 00:00:00 2001 From: Sosthene Date: Tue, 2 Sep 2025 12:51:42 +0200 Subject: [PATCH] More verbose error messages for is_valid() --- src/process.rs | 145 ++++++++++++++++++++++++++----------------------- 1 file changed, 78 insertions(+), 67 deletions(-) diff --git a/src/process.rs b/src/process.rs index de57944..6b31070 100644 --- a/src/process.rs +++ b/src/process.rs @@ -1,6 +1,5 @@ use anyhow::Result; use std::collections::{BTreeMap, HashMap, HashSet}; -use std::str::FromStr; use std::sync::{Mutex, MutexGuard, OnceLock}; use serde::{Deserialize, Serialize}; @@ -14,7 +13,7 @@ use crate::{ pcd::{Member, Pcd, PcdCommitments, RoleDefinition, Roles, ValidationRule}, serialization::{deserialize_hex, hex_array_btree, serialize_hex, OutPointMemberMap}, signature::{AnkHash, AnkValidationNoHash, AnkValidationYesHash, Proof}, - MutexExt, SpecialRoles, APOPHIS, PAIREDADDRESSES, PAIRING, + MutexExt, APOPHIS, PAIREDADDRESSES, PAIRING, }; #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, Tsify)] @@ -236,76 +235,88 @@ impl ProcessState { // If we don't update pairedAddresses, we don't need to bother about pairing } - // Check if each modified field satisfies at least one applicable rule across all roles - let all_fields_validated = !self.pcd_commitment.is_empty() - && self.pcd_commitment.keys().all(|field| { - // Collect applicable rules from all roles for the current field - let applicable_roles: Vec = self - .roles - .iter() - .filter_map(|(role_name, role_def)| { - if let Ok(special_role) = SpecialRoles::from_str(&role_name) { - match special_role { - // We allow for a special case with a role that works only for initial state - // That's optional though - SpecialRoles::Demiurge => { - // If we're not in initial state just ignore it - if previous_state.is_some() { - return None; - } else { - // We try to validate with demiurge - match self.handle_demiurge(role_def) { - Ok(role) => return Some(role), - Err(e) => { - log::error!("{}", e.to_string()); - return None; - } - } - } - // Otherwise we just continue normal validation - } - // We already handled other special roles - _ => return None, - } - } - let mut filtered_role_def = role_def.clone(); - let rules = filtered_role_def.get_applicable_rules(field); - filtered_role_def.validation_rules = - rules.into_iter().map(|r| r.clone()).collect(); - if filtered_role_def.validation_rules.is_empty() { - None - } else { - Some(filtered_role_def) - } - }) - .collect(); + if self.pcd_commitment.is_empty() { + return Err(anyhow::anyhow!("No fields to validate")); + } - if applicable_roles.is_empty() { - return false; // No rules apply to this field, consider it invalid + let mut roles = self.roles.clone(); + + // If we're initial state, we add the demiurge role, otherwise we can just ignore it + if previous_state.is_none() { + if let Some(demiurge) = roles.get(crate::DEMIURGE) { + match self.handle_demiurge(demiurge) { + Ok(role) => { + log::debug!("Adding demiurge role: {:?}", role); + roles.insert(crate::DEMIURGE.to_string(), role); + } + Err(e) => { + log::debug!("{}", e.to_string()); + } } + } else { + log::debug!("No demiurge role for initial state"); + } + } else { + log::debug!("Not initial state, ignoring demiurge role"); + } - applicable_roles.into_iter().any(|role_def| { - role_def.validation_rules.iter().any(|rule| { - let members: Vec<&Member> = role_def - .members - .iter() - .filter_map(|outpoint| members_list.0.get(outpoint)) - .collect(); - rule.is_satisfied( - field, - self.state_id, - &self.validation_tokens, - members.as_slice(), - ) - .is_ok() - }) + let mut error_msg = String::new(); + + log::debug!("Checking fields for roles: {:?}", roles); + + // Check if each modified field satisfies at least one applicable rule across all roles + let all_fields_validated: bool = self.pcd_commitment.keys().all(|field| { + // Collect applicable rules from all roles for the current field + let applicable_roles: Vec<(String, RoleDefinition)> = roles.clone() + .into_iter() + .filter_map(|(role_name, mut role_def)| { + let rules = role_def.get_applicable_rules(field); + role_def.validation_rules = + rules.into_iter().map(|r| r.clone()).collect(); + if role_def.validation_rules.is_empty() { + None + } else { + Some((role_name.clone(), role_def)) + } }) - }); + .collect(); + + if applicable_roles.is_empty() { + error_msg = format!("No relevant roles for field: {}", field); + return false; + } + + let mut tested_roles = vec![]; + + if let Some((role_name, _valid_role)) = applicable_roles.into_iter().find(|(role_name, role_def)| { + tested_roles.push(role_name.clone()); + role_def.validation_rules.iter().any(|rule| { + let members: Vec<&Member> = role_def + .members + .iter() + .filter_map(|outpoint| members_list.0.get(outpoint)) + .collect(); + rule.is_satisfied( + field, + self.state_id, + &self.validation_tokens, + members.as_slice(), + ) + .is_ok() + }) + }) { + log::debug!("Validated field {} with role: {}", field, role_name); + return true; + } else { + error_msg = format!("Failed validation for field {} in roles: {:?}", field, tested_roles); + return false; + } + }); if all_fields_validated { Ok(()) } else { - Err(anyhow::anyhow!("Not enough valid proofs")) + Err(anyhow::anyhow!("Not enough valid proofs: {}", error_msg)) } } @@ -960,7 +971,7 @@ mod tests { .push(Proof::new(message_hash, signing_key)); let result = state.is_valid(None, &OutPointMemberMap(get_members_map())); assert!(result.is_err()); - assert_eq!(result.unwrap_err().to_string(), "Not enough valid proofs"); + assert_eq!(result.unwrap_err().to_string(), "Not enough valid proofs: Failed validation for field field1 in roles: [\"demiurge\", \"role1\"]"); } #[test] @@ -975,7 +986,7 @@ mod tests { .push(Proof::new(message_hash, carol_key)); let result = state.is_valid(None, &OutPointMemberMap(get_members_map())); assert!(result.is_err()); - assert_eq!(result.unwrap_err().to_string(), "Not enough valid proofs"); + assert_eq!(result.unwrap_err().to_string(), "Not enough valid proofs: Failed validation for field field1 in roles: [\"demiurge\", \"role1\"]"); } #[test] @@ -1325,7 +1336,7 @@ mod tests { &OutPointMemberMap(get_members_map()), ); assert!(result.is_err()); - assert_eq!(result.unwrap_err().to_string(), "Not enough valid proofs"); + assert_eq!(result.unwrap_err().to_string(), "Not enough valid proofs: Failed validation for field field2 in roles: [\"role2\"]"); } #[test]