ProcessState validation tests + bug fixes
This commit is contained in:
parent
88fc785dee
commit
9be870549b
414
src/process.rs
414
src/process.rs
@ -9,7 +9,7 @@ use sp_client::{bitcoin::OutPoint, silentpayments::utils::SilentPaymentAddress};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
crypto::AnkSharedSecretHash,
|
crypto::AnkSharedSecretHash,
|
||||||
pcd::{AnkPcdHash, Pcd, RoleDefinition, ValidationRule},
|
pcd::{AnkPcdHash, Pcd, RoleDefinition},
|
||||||
prd::Prd,
|
prd::Prd,
|
||||||
signature::Proof,
|
signature::Proof,
|
||||||
MutexExt,
|
MutexExt,
|
||||||
@ -57,12 +57,11 @@ impl ProcessState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_valid(
|
pub fn is_valid(&self, previous_state: Option<&ProcessState>) -> anyhow::Result<()> {
|
||||||
&self,
|
|
||||||
previous_state: Option<&ProcessState>,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
if self.validation_tokens.is_empty() {
|
if self.validation_tokens.is_empty() {
|
||||||
return Err(anyhow::anyhow!("Can't validate a state with no proofs attached"));
|
return Err(anyhow::anyhow!(
|
||||||
|
"Can't validate a state with no proofs attached"
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute modified fields
|
// Compute modified fields
|
||||||
@ -97,35 +96,35 @@ impl ProcessState {
|
|||||||
|
|
||||||
// Check if each modified field satisfies at least one applicable rule across all roles
|
// Check if each modified field satisfies at least one applicable rule across all roles
|
||||||
let all_fields_validated = modified_fields.iter().all(|field| {
|
let all_fields_validated = modified_fields.iter().all(|field| {
|
||||||
println!("validating field: {}", field);
|
|
||||||
// Collect applicable rules from all roles for the current field
|
// Collect applicable rules from all roles for the current field
|
||||||
let applicable_roles: Vec<RoleDefinition> = roles2rules.iter()
|
let applicable_roles: Vec<RoleDefinition> = roles2rules
|
||||||
.map(|(_, role_def)| {
|
.iter()
|
||||||
|
.filter_map(|(_, role_def)| {
|
||||||
let mut filtered_role_def = role_def.clone();
|
let mut filtered_role_def = role_def.clone();
|
||||||
let rules = filtered_role_def.get_applicable_rules(field);
|
let rules = filtered_role_def.get_applicable_rules(field);
|
||||||
filtered_role_def.validation_rules = rules.into_iter().map(|r| r.clone()).collect();
|
filtered_role_def.validation_rules =
|
||||||
filtered_role_def
|
rules.into_iter().map(|r| r.clone()).collect();
|
||||||
})
|
if filtered_role_def.validation_rules.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(filtered_role_def)
|
||||||
|
}
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if applicable_roles.is_empty() {
|
if applicable_roles.is_empty() {
|
||||||
return false; // No rules apply to this field, consider it invalid
|
return false; // No rules apply to this field, consider it invalid
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("applicable_roles: {:?}", applicable_roles);
|
|
||||||
|
|
||||||
// Check if any applicable rule is satisfied
|
|
||||||
applicable_roles.into_iter().any(|role_def| {
|
applicable_roles.into_iter().any(|role_def| {
|
||||||
let res = false;
|
role_def.validation_rules.iter().any(|rule| {
|
||||||
for rule in role_def.validation_rules {
|
|
||||||
rule.is_satisfied(
|
rule.is_satisfied(
|
||||||
field,
|
field,
|
||||||
new_state_hash.clone(),
|
new_state_hash.clone(),
|
||||||
&self.validation_tokens,
|
&self.validation_tokens,
|
||||||
&role_def.members,
|
&role_def.members,
|
||||||
);
|
)
|
||||||
}
|
})
|
||||||
res
|
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -318,3 +317,378 @@ pub fn lock_processes() -> Result<MutexGuard<'static, HashMap<OutPoint, Process>
|
|||||||
.get_or_init(|| Mutex::new(HashMap::new()))
|
.get_or_init(|| Mutex::new(HashMap::new()))
|
||||||
.lock_anyhow()
|
.lock_anyhow()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use serde_json::json;
|
||||||
|
use sp_client::{
|
||||||
|
bitcoin::{secp256k1::SecretKey, Network},
|
||||||
|
spclient::{SpClient, SpWallet, SpendKey},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::pcd::{Member, ValidationRule};
|
||||||
|
use crate::signature::{AnkValidationNoHash, AnkValidationYesHash};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn create_alice_wallet() -> SpWallet {
|
||||||
|
SpWallet::new(
|
||||||
|
SpClient::new(
|
||||||
|
"default".to_owned(),
|
||||||
|
SecretKey::from_str(
|
||||||
|
"a67fb6bf5639efd0aeb19c1c584dd658bceda87660ef1088d4a29d2e77846973",
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
SpendKey::Secret(
|
||||||
|
SecretKey::from_str(
|
||||||
|
"a1e4e7947accf33567e716c9f4d186f26398660e36cf6d2e711af64b3518e65c",
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
Network::Signet,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
None,
|
||||||
|
vec![],
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_bob_wallet() -> SpWallet {
|
||||||
|
SpWallet::new(
|
||||||
|
SpClient::new(
|
||||||
|
"default".to_owned(),
|
||||||
|
SecretKey::from_str(
|
||||||
|
"4d9f62b2340de3f0bafd671b78b19edcfded918c4106baefd34512f12f520e9b",
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
SpendKey::Secret(
|
||||||
|
SecretKey::from_str(
|
||||||
|
"dafb99602721577997a6fe3da54f86fd113b1b58f0c9a04783d486f87083a32e",
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
Network::Signet,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
None,
|
||||||
|
vec![],
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_carol_wallet() -> SpWallet {
|
||||||
|
SpWallet::new(
|
||||||
|
SpClient::new(
|
||||||
|
"default".to_owned(),
|
||||||
|
SecretKey::from_str(
|
||||||
|
"e4a5906eaa1a7ab24d5fc8d9b600d47f79caa6511c056c111677b7a33e62c5e9",
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
SpendKey::Secret(
|
||||||
|
SecretKey::from_str(
|
||||||
|
"e4c282e14668af1435e39df78403a7b406a791e3c6e666295496a6a865ade162",
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
Network::Signet,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
None,
|
||||||
|
vec![],
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dummy_process_state() -> ProcessState {
|
||||||
|
let alice_wallet = create_alice_wallet();
|
||||||
|
let bob_wallet = create_bob_wallet();
|
||||||
|
let carol_wallet = create_carol_wallet();
|
||||||
|
|
||||||
|
let alice_address =
|
||||||
|
SilentPaymentAddress::try_from(alice_wallet.get_client().get_receiving_address())
|
||||||
|
.unwrap();
|
||||||
|
let bob_address =
|
||||||
|
SilentPaymentAddress::try_from(bob_wallet.get_client().get_receiving_address())
|
||||||
|
.unwrap();
|
||||||
|
let carol_address =
|
||||||
|
SilentPaymentAddress::try_from(carol_wallet.get_client().get_receiving_address())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let alice_bob = Member::new(vec![alice_address, bob_address]).unwrap();
|
||||||
|
let carol = Member::new(vec![carol_address]).unwrap();
|
||||||
|
|
||||||
|
let validation_rule1 =
|
||||||
|
ValidationRule::new(1.0, vec!["field1".to_owned(), "roles".to_owned()], 0.5).unwrap();
|
||||||
|
let validation_rule2 = ValidationRule::new(1.0, vec!["field2".to_owned()], 0.5).unwrap();
|
||||||
|
|
||||||
|
let encrypted_pcd = json!({
|
||||||
|
"field1": "value1",
|
||||||
|
"field2": "value2",
|
||||||
|
"roles": {
|
||||||
|
"role1": {
|
||||||
|
"members": [alice_bob],
|
||||||
|
"validation_rules": [validation_rule1]
|
||||||
|
},
|
||||||
|
"role2": {
|
||||||
|
"members": [carol],
|
||||||
|
"validation_rules": [validation_rule2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut fields2keys = Map::new();
|
||||||
|
let mut fields2cipher = Map::new();
|
||||||
|
// let field_to_encrypt: Vec<String> = encrypted_pcd.as_object().unwrap().keys().map(|k| k.clone()).collect();
|
||||||
|
let field_to_encrypt = vec!["field1".to_string(), "field2".to_string()];
|
||||||
|
|
||||||
|
encrypted_pcd
|
||||||
|
.encrypt_fields(&field_to_encrypt, &mut fields2keys, &mut fields2cipher)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
ProcessState {
|
||||||
|
commited_in: OutPoint::null(),
|
||||||
|
encrypted_pcd,
|
||||||
|
keys: Map::new(),
|
||||||
|
validation_tokens: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_validation_token(state: &mut ProcessState, signing_key: SecretKey, accept: bool) {
|
||||||
|
let pcd_hash = AnkPcdHash::from_value(&state.encrypted_pcd);
|
||||||
|
if accept {
|
||||||
|
let validation_hash = AnkValidationYesHash::from_commitment(pcd_hash);
|
||||||
|
let proof = Proof::new(
|
||||||
|
crate::signature::AnkHash::ValidationYes(validation_hash),
|
||||||
|
signing_key,
|
||||||
|
);
|
||||||
|
state.validation_tokens.push(proof);
|
||||||
|
} else {
|
||||||
|
let validation_hash = AnkValidationNoHash::from_commitment(pcd_hash);
|
||||||
|
let proof = Proof::new(
|
||||||
|
crate::signature::AnkHash::ValidationNo(validation_hash),
|
||||||
|
signing_key,
|
||||||
|
);
|
||||||
|
state.validation_tokens.push(proof);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_error_no_proofs() {
|
||||||
|
let state = dummy_process_state();
|
||||||
|
let result = state.is_valid(None);
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().to_string(),
|
||||||
|
"Can't validate a state with no proofs attached"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_error_identical_previous_state() {
|
||||||
|
let mut state = dummy_process_state();
|
||||||
|
// We sign with a random key
|
||||||
|
let signing_key =
|
||||||
|
SecretKey::from_str("39b2a765dc93e02da04a0e9300224b4f99fa7b83cfae49036dff58613fd3277c")
|
||||||
|
.unwrap();
|
||||||
|
add_validation_token(&mut state, signing_key, true);
|
||||||
|
let result = state.is_valid(Some(&state));
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().to_string(),
|
||||||
|
"State is identical to the previous state"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
/// We provide a proof signed with a key that is not the spending key for either alice or bob
|
||||||
|
fn test_error_invalid_proof() {
|
||||||
|
let mut state = dummy_process_state();
|
||||||
|
// We sign with a random key
|
||||||
|
let signing_key =
|
||||||
|
SecretKey::from_str("39b2a765dc93e02da04a0e9300224b4f99fa7b83cfae49036dff58613fd3277c")
|
||||||
|
.unwrap();
|
||||||
|
add_validation_token(&mut state, signing_key, true);
|
||||||
|
let result = state.is_valid(None);
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(result.unwrap_err().to_string(), "Not enough valid proofs");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
/// Carol signs alone for an init state
|
||||||
|
fn test_error_not_enough_signatures() {
|
||||||
|
let mut state = dummy_process_state();
|
||||||
|
// We sign with Carol key
|
||||||
|
let carol_key: SecretKey = create_carol_wallet()
|
||||||
|
.get_client()
|
||||||
|
.get_spend_key()
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
add_validation_token(&mut state, carol_key, true);
|
||||||
|
let result = state.is_valid(None);
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(result.unwrap_err().to_string(), "Not enough valid proofs");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
/// Alice signs alone for her fields in an init state
|
||||||
|
fn test_valid_just_enough_signatures() {
|
||||||
|
let mut state = dummy_process_state();
|
||||||
|
// We sign with Alice and Carol keys
|
||||||
|
let alice_key: SecretKey = create_alice_wallet()
|
||||||
|
.get_client()
|
||||||
|
.get_spend_key()
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
let carol_key: SecretKey = create_carol_wallet()
|
||||||
|
.get_client()
|
||||||
|
.get_spend_key()
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
add_validation_token(&mut state, alice_key, true);
|
||||||
|
add_validation_token(&mut state, carol_key, true);
|
||||||
|
let result = state.is_valid(None);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
/// everyone signs for everything
|
||||||
|
fn test_valid_all_signatures() {
|
||||||
|
let mut state = dummy_process_state();
|
||||||
|
let alice_key: SecretKey = create_alice_wallet()
|
||||||
|
.get_client()
|
||||||
|
.get_spend_key()
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
let bob_key: SecretKey = create_bob_wallet()
|
||||||
|
.get_client()
|
||||||
|
.get_spend_key()
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
let carol_key: SecretKey = create_carol_wallet()
|
||||||
|
.get_client()
|
||||||
|
.get_spend_key()
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
add_validation_token(&mut state, alice_key, true);
|
||||||
|
add_validation_token(&mut state, bob_key, true);
|
||||||
|
add_validation_token(&mut state, carol_key, true);
|
||||||
|
let result = state.is_valid(None);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
/// Carol refuses change for her part
|
||||||
|
fn test_error_carol_votes_no() {
|
||||||
|
let mut state = dummy_process_state();
|
||||||
|
let alice_key: SecretKey = create_alice_wallet()
|
||||||
|
.get_client()
|
||||||
|
.get_spend_key()
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
let bob_key: SecretKey = create_bob_wallet()
|
||||||
|
.get_client()
|
||||||
|
.get_spend_key()
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
let carol_key: SecretKey = create_carol_wallet()
|
||||||
|
.get_client()
|
||||||
|
.get_spend_key()
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
add_validation_token(&mut state, alice_key, true);
|
||||||
|
add_validation_token(&mut state, bob_key, true);
|
||||||
|
add_validation_token(&mut state, carol_key, false);
|
||||||
|
let result = state.is_valid(None);
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(result.unwrap_err().to_string(), "Not enough valid proofs");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
/// Bob refuses change for his part, but Alice is enough to reach quorum
|
||||||
|
fn test_valid_bob_votes_no() {
|
||||||
|
let mut state = dummy_process_state();
|
||||||
|
let alice_key: SecretKey = create_alice_wallet()
|
||||||
|
.get_client()
|
||||||
|
.get_spend_key()
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
let bob_key: SecretKey = create_bob_wallet()
|
||||||
|
.get_client()
|
||||||
|
.get_spend_key()
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
let carol_key: SecretKey = create_carol_wallet()
|
||||||
|
.get_client()
|
||||||
|
.get_spend_key()
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
add_validation_token(&mut state, alice_key, true);
|
||||||
|
add_validation_token(&mut state, bob_key, false);
|
||||||
|
add_validation_token(&mut state, carol_key, true);
|
||||||
|
let result = state.is_valid(None);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
/// Everyone signs, and we have a previous state
|
||||||
|
fn test_valid_everyone_signs_with_prev_state() {
|
||||||
|
let state = dummy_process_state();
|
||||||
|
let mut new_state = state.clone();
|
||||||
|
if let Value::Object(ref mut map) = new_state.encrypted_pcd {
|
||||||
|
// Modify the field
|
||||||
|
map.insert("field1".to_string(), Value::String("new_value1".to_owned()));
|
||||||
|
} else {
|
||||||
|
// Handle the case where encrypted_pcd is not an object
|
||||||
|
panic!("encrypted_pcd is not a JSON object.");
|
||||||
|
}
|
||||||
|
let alice_key: SecretKey = create_alice_wallet()
|
||||||
|
.get_client()
|
||||||
|
.get_spend_key()
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
let bob_key: SecretKey = create_bob_wallet()
|
||||||
|
.get_client()
|
||||||
|
.get_spend_key()
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
let carol_key: SecretKey = create_carol_wallet()
|
||||||
|
.get_client()
|
||||||
|
.get_spend_key()
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
add_validation_token(&mut new_state, alice_key, true);
|
||||||
|
add_validation_token(&mut new_state, bob_key, true);
|
||||||
|
add_validation_token(&mut new_state, carol_key, true);
|
||||||
|
let result = new_state.is_valid(Some(&state));
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
/// Only Carol signs, but she doesn't have modification rights on modified field
|
||||||
|
fn test_error_not_right_signatures_with_prev_state() {
|
||||||
|
let state = dummy_process_state();
|
||||||
|
let mut new_state = state.clone();
|
||||||
|
if let Value::Object(ref mut map) = new_state.encrypted_pcd {
|
||||||
|
// Modify the field
|
||||||
|
map.insert("field1".to_string(), Value::String("new_value1".to_owned()));
|
||||||
|
} else {
|
||||||
|
// Handle the case where encrypted_pcd is not an object
|
||||||
|
panic!("encrypted_pcd is not a JSON object.");
|
||||||
|
}
|
||||||
|
let carol_key: SecretKey = create_carol_wallet()
|
||||||
|
.get_client()
|
||||||
|
.get_spend_key()
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
add_validation_token(&mut new_state, carol_key, true);
|
||||||
|
let result = new_state.is_valid(Some(&state));
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(result.unwrap_err().to_string(), "Not enough valid proofs");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user