diff --git a/anchor/database/src/lib.rs b/anchor/database/src/lib.rs index cda91fe8..482c47b6 100644 --- a/anchor/database/src/lib.rs +++ b/anchor/database/src/lib.rs @@ -9,7 +9,7 @@ use std::sync::LazyLock; use std::time::Duration; mod cluster_operations; -pub mod error; +mod error; mod operator_operations; mod share_operations; mod state; @@ -24,7 +24,7 @@ const POOL_SIZE: u32 = 1; const CONNECTION_TIMEOUT: Duration = Duration::from_secs(5); #[derive(Debug, Clone, Default)] -pub struct NetworkState { +struct NetworkState { /// The ID of our own operator. This is determined via events when the operator is /// registered with the network. Therefore, this may not be available right away if the client /// is running but has not bee registered with the network contract yet. diff --git a/anchor/database/src/operator_operations.rs b/anchor/database/src/operator_operations.rs index 0d805e51..b56d62e9 100644 --- a/anchor/database/src/operator_operations.rs +++ b/anchor/database/src/operator_operations.rs @@ -51,11 +51,6 @@ impl NetworkDatabase { Ok(()) } - /// Set the id of our own operator - pub fn set_own_id(&mut self, id: OperatorId) { - self.state.id = Some(id); - } - // Helper to encode the RsaPublicKey to PEM fn encode_pubkey(pubkey: &Rsa) -> String { // this should never fail as the key has already been validated upon construction diff --git a/anchor/database/src/state.rs b/anchor/database/src/state.rs index 98ecc129..da7c79f8 100644 --- a/anchor/database/src/state.rs +++ b/anchor/database/src/state.rs @@ -25,7 +25,6 @@ impl NetworkState { let mut cluster_members: HashMap> = HashMap::with_capacity(num_clusters); - println!("{:#?}", clusters); // Populate state stores from cluster data clusters.iter().for_each(|cluster| { let cluster_id = cluster.cluster_id; @@ -125,4 +124,9 @@ impl NetworkDatabase { pub fn member_of_cluster(&self, id: &ClusterId) -> bool { self.state.clusters.contains(id) } + + /// Set the id of our own operator + pub fn set_own_id(&mut self, id: OperatorId) { + self.state.id = Some(id); + } } diff --git a/anchor/database/src/tests/cluster_tests.rs b/anchor/database/src/tests/cluster_tests.rs index 4a25e9d9..81eb2d1b 100644 --- a/anchor/database/src/tests/cluster_tests.rs +++ b/anchor/database/src/tests/cluster_tests.rs @@ -7,101 +7,29 @@ mod cluster_database_tests { #[test] // Test inserting a cluster into the database fn test_insert_retrieve_cluster() { - // Create a temporary database - let dir = tempdir().unwrap(); - let file = dir.path().join("db.sqlite"); - let mut db = NetworkDatabase::new(&file, Some(OperatorId(1))).unwrap(); - - // First insert the operators that will be part of the cluster - for i in 0..4 { - let operator = dummy_operator(i); - assert!(db.insert_operator(&operator).is_ok()); - } - - // Insert a dummy cluster - let cluster = dummy_cluster(4); - assert!(db.insert_cluster(cluster.clone()).is_ok()); - - debug_print_db(&db); - println!("{:#?}", db.state); - - // Verify cluster is in memory - assert!(db.member_of_cluster(&cluster.cluster_id)); - assert_eq!( - db.state.cluster_members[&cluster.cluster_id].len(), - cluster.cluster_members.len() - ); - - // Verify cluster is in the underlying database - let cluster_row = get_cluster_from_db(&db, cluster.cluster_id); - assert!(cluster_row.is_some()); - let (db_cluster_id, db_faulty, db_liquidated) = cluster_row.unwrap(); - assert_eq!(db_cluster_id, *cluster.cluster_id as i64); - assert_eq!(db_faulty, cluster.faulty as i64); - assert_eq!(db_liquidated, cluster.liquidated); - - // Verify cluster members are in the underlying database - for member in &cluster.cluster_members { - let member_row = get_cluster_member_from_db(&db, member.cluster_id, member.operator_id); - assert!(member_row.is_some()); - let (db_cluster_id, db_operator_id) = member_row.unwrap(); - assert_eq!(db_cluster_id, *member.cluster_id as i64); - assert_eq!(db_operator_id, *member.operator_id as i64); - } - - // Verify that the shares are in the database - let all_shares = get_shares_from_db(&db, cluster.cluster_id); - assert!(!all_shares.is_empty()); - - // Verify that the validator is in the database - let validator_pubkey_str = cluster.validator_metadata.validator_pubkey.to_string(); - assert!(get_validator_from_db(&db, &validator_pubkey_str).is_some()); + let fixture = TestFixture::new(Some(1)); + assertions::assert_cluster_exists_fully(&fixture.db, &fixture.cluster); } #[test] - /// Try inserting a cluster that does not already have registers operators in the database + // Try inserting a cluster that does not already have registers operators in the database fn test_insert_cluster_without_operators() { - // Create a temporary database - let dir = tempdir().unwrap(); - let file = dir.path().join("db.sqlite"); - let mut db = NetworkDatabase::new(&file, None).unwrap(); - - // Try to insert a cluster without first inserting its operators - let cluster = dummy_cluster(4); - - // This should fail because the operators don't exist in the database - assert!(db.insert_cluster(cluster).is_err()); + let mut fixture = TestFixture::new_empty(); + let cluster = generators::cluster::random(3); + fixture + .db + .insert_cluster(cluster) + .expect_err("Insertion should fail"); } #[test] + // Test deleting a cluster and make sure that it is properly cleaned up fn test_delete_cluster() { - // Create a temporary database - let dir = tempdir().unwrap(); - let file = dir.path().join("db.sqlite"); - let mut db = NetworkDatabase::new(&file, Some(OperatorId(1))).unwrap(); - - // populate the db with operators and cluster - let cluster = db_with_cluster(&mut db); - - // Delete the cluster and then confirm it is gone from memory and dbb - assert!(db.delete_cluster(cluster.cluster_id).is_ok()); - - let cluster_row = get_cluster_from_db(&db, cluster.cluster_id); - assert!(!db.member_of_cluster(&cluster.cluster_id)); - assert!(cluster_row.is_none()); - - // Make sure all the members are gone - for member in &cluster.cluster_members { - let member_row = get_cluster_member_from_db(&db, member.cluster_id, member.operator_id); - assert!(member_row.is_none()); - } - - // Make sure all the shares are gone - let all_shares = get_shares_from_db(&db, cluster.cluster_id); - assert!(all_shares.is_empty()); - - // Make sure the validator this cluster represented is gone - let validator_pubkey_str = cluster.validator_metadata.validator_pubkey.to_string(); - assert!(get_validator_from_db(&db, &validator_pubkey_str).is_none()); + let mut fixture = TestFixture::new(Some(1)); + fixture + .db + .delete_cluster(fixture.cluster.cluster_id) + .expect("Failed to delete cluster"); + assertions::assert_cluster_exists_not_fully(&fixture.db, &fixture.cluster); } } diff --git a/anchor/database/src/tests/operator_tests.rs b/anchor/database/src/tests/operator_tests.rs index 0db7a62b..04767b19 100644 --- a/anchor/database/src/tests/operator_tests.rs +++ b/anchor/database/src/tests/operator_tests.rs @@ -5,78 +5,88 @@ mod operator_database_tests { use super::*; #[test] - // Test inserting into the database and then confirming that it is both in - // memory and in the underlying database + // Test to make sure we can insert new operators into the database and they are present in the + // state stores fn test_insert_retrieve_operator() { - // Create a temporary database - let dir = tempdir().unwrap(); - let file = dir.path().join("db.sqlite"); - let mut db = NetworkDatabase::new(&file, None).unwrap(); + // Create a new text fixture with empty db + let mut fixture = TestFixture::new_empty(); - // Insert dummy operator data into the database - let operator = dummy_operator(1); - assert!(db.insert_operator(&operator).is_ok()); + // Generate a new operator and insert it + let operator = generators::operator::with_id(1); + fixture + .db + .insert_operator(&operator) + .expect("Failed to insert operator"); - // Fetch operator from in memory store and confirm values - let fetched_operator = db.get_operator(&operator.id); - if let Some(op) = fetched_operator { - assert_eq!(op.id, operator.id); + // Confirm that it exists both in the db and the state store + assertions::assert_operator_exists_fully(&fixture.db, &operator); + } - assert_eq!( - op.rsa_pubkey.public_key_to_pem().unwrap(), - operator.rsa_pubkey.public_key_to_pem().unwrap() - ); - assert_eq!(op.owner, operator.owner); - } else { - panic!("Expected to find operator in memory"); - } + #[test] + // Ensure that we cannot insert a duplicate operator into the database + fn test_duplicate_insert() { + // Create a new test fixture with empty db + let mut fixture = TestFixture::new_empty(); + + // Generate a new operator and insert it + let operator = generators::operator::with_id(1); + fixture + .db + .insert_operator(&operator) + .expect("Failed to insert operator"); - // Check to make sure the operator is also in the underlying db - let db_operator = get_operator_from_db(&db, operator.id); - if let Some(op) = db_operator { - assert_eq!( - op.rsa_pubkey.public_key_to_pem().unwrap(), - operator.rsa_pubkey.public_key_to_pem().unwrap() - ); - assert_eq!(op.id, operator.id); - assert_eq!(op.owner, operator.owner); - } else { - panic!("Expected to find operator in database"); + // Try to insert it again, this should fail + let success = fixture.db.insert_operator(&operator); + if success.is_ok() { + panic!("Expected an error when inserting an operator that is already present"); } } #[test] // Test deleting an operator and confirming it is gone from the db and in memory fn test_insert_delete_operator() { - // Create a temporary database - let dir = tempdir().unwrap(); - let file = dir.path().join("db.sqlite"); - let mut db = NetworkDatabase::new(&file, None).unwrap(); + // Create new test fixture with empty db + let mut fixture = TestFixture::new_empty(); - // Insert dummy operator data into the database - let operator = dummy_operator(1); - let _ = db.insert_operator(&operator); + // Generate a new operator and insert it + let operator = generators::operator::with_id(1); + fixture + .db + .insert_operator(&operator) + .expect("Failed to insert operator"); // Now, delete the operator - assert!(db.delete_operator(operator.id).is_ok()); + fixture + .db + .delete_operator(operator.id) + .expect("Failed to delete operator"); - // Confirm that is it removed from in memory - assert!(db.get_operator(&operator.id).is_none()); - - // Also confirm that it is removed from the database - assert!(get_operator_from_db(&db, operator.id).is_none()); + // Confirm that it is gone + assertions::assert_operator_not_exists_fully(&fixture.db, operator.id); } #[test] - // insert multiple operators + // Test inserting multiple operators fn test_insert_multiple_operators() { - let dir = tempdir().unwrap(); - let file = dir.path().join("db.sqlite"); - let mut db = NetworkDatabase::new(&file, None).unwrap(); + // Create new test fixture with empty db + let mut fixture = TestFixture::new_empty(); + + // Generate and insert operators + let operators: Vec = (0..4).map(generators::operator::with_id).collect(); + for operator in &operators { + fixture + .db + .insert_operator(operator) + .expect("Failed to insert operator"); + } - for id in 0..4 { - let operator = dummy_operator(id); - assert!(db.insert_operator(&operator).is_ok()); + // Delete them all and confirm deletion + for operator in operators { + fixture + .db + .delete_operator(operator.id) + .expect("Failed to delete operator"); + assertions::assert_operator_not_exists_fully(&fixture.db, operator.id); } } } diff --git a/anchor/database/src/tests/state_tests.rs b/anchor/database/src/tests/state_tests.rs index d75ff349..05fd9ff6 100644 --- a/anchor/database/src/tests/state_tests.rs +++ b/anchor/database/src/tests/state_tests.rs @@ -5,26 +5,34 @@ mod state_database_tests { use super::*; #[test] - fn test_state_after_restart() { - // Create a temporary database - let dir = tempdir().unwrap(); - let file = dir.path().join("db.sqlite"); - let mut db = NetworkDatabase::new(&file, Some(OperatorId(1))).unwrap(); + // Make sure all of the previously inserted operators are present after restart + fn test_operator_store() { + // Create new test fixture with populated DB + let mut fixture = TestFixture::new(Some(1)); - // Insert the operators and a cluster we are a part of - for i in 0..4 { - let operator = dummy_operator(i); - assert!(db.insert_operator(&operator).is_ok()); + // drop the database and then recreate it + drop(fixture.db); + fixture.db = NetworkDatabase::new(&fixture.path, Some(OperatorId(1))) + .expect("Failed to create database"); + + // confirm that all of the operators exist were + for operator in fixture.operators { + assertions::assert_operator_exists_fully(&fixture.db, &operator); } - // Insert a dummy cluster - let cluster = dummy_cluster(4); - assert!(db.insert_cluster(cluster.clone()).is_ok()); - println!("{:#?}", db.state); + } + + #[test] + fn test_cluster_after_restart() { + // Create new test fixture with populated DB + let mut fixture = TestFixture::new(Some(1)); + let cluster = fixture.cluster; - // drop db and recreate it, stores should be built since db already exists - drop(db); + // drop the database and then recreate it + drop(fixture.db); + fixture.db = NetworkDatabase::new(&fixture.path, Some(OperatorId(1))) + .expect("Failed to create database"); - let db = NetworkDatabase::new(&file, Some(OperatorId(1))).unwrap(); - println!("{:#?}", db.state); + // Confirm all cluster related data is still correct + assertions::assert_cluster_exists_fully(&fixture.db, &cluster); } } diff --git a/anchor/database/src/tests/utils.rs b/anchor/database/src/tests/utils.rs index f4de0555..bcb90175 100644 --- a/anchor/database/src/tests/utils.rs +++ b/anchor/database/src/tests/utils.rs @@ -1,258 +1,517 @@ -use crate::NetworkDatabase; +use super::test_prelude::*; use openssl::rsa::Rsa; use rand::Rng; use rusqlite::{params, OptionalExtension}; -use ssv_types::{ - Cluster, ClusterId, ClusterMember, Operator, OperatorId, Share, ValidatorIndex, - ValidatorMetadata, -}; +use std::path::PathBuf; +use tempfile::TempDir; use types::test_utils::{SeedableRng, TestRandom, XorShiftRng}; use types::{Address, Graffiti, PublicKey}; -// Generate a random PublicKey -pub fn random_pubkey() -> PublicKey { - let rng = &mut XorShiftRng::from_seed([42; 16]); - PublicKey::random_for_test(rng) -} +const DEFAULT_NUM_OPERATORS: u64 = 4; +const RSA_KEY_SIZE: u32 = 2048; +const DEFAULT_SEED: [u8; 16] = [42; 16]; -// Generate random operator data -pub fn dummy_operator(id: u64) -> Operator { - let op_id = OperatorId(id); - let address = Address::random(); - //let pubkey = Rsa::generate(2048).unwrap().public_key_to_pem(); - let _priv_key = Rsa::generate(2048).unwrap(); - let public_key = _priv_key.public_key_to_pem().unwrap(); - let public_key = Rsa::public_key_from_pem(&public_key).unwrap(); - Operator::new_with_pubkey(public_key, op_id, address) +// Test fixture for common scnearios +#[derive(Debug)] +pub struct TestFixture { + pub db: NetworkDatabase, + pub cluster: Cluster, + pub operators: Vec, + pub path: PathBuf, + _temp_dir: TempDir, } -// Generate a random Cluster -pub fn dummy_cluster(num_operators: u64) -> Cluster { - let cluster_id = ClusterId(rand::thread_rng().gen::().into()); - let mut members = Vec::new(); +impl TestFixture { + pub fn new(id: Option) -> Self { + let temp_dir = TempDir::new().expect("Failed to create temporary directory"); + let db_path = temp_dir.path().join("test.db"); + let mut db = if let Some(id) = id { + NetworkDatabase::new(&db_path, Some(OperatorId(id))) + .expect("Failed to create test database") + } else { + NetworkDatabase::new(&db_path, None).expect("Failed to create test database") + }; + + let operators: Vec = (0..DEFAULT_NUM_OPERATORS) + .map(generators::operator::with_id) + .collect(); + + operators.iter().for_each(|op| { + db.insert_operator(op).expect("Failed to insert operator"); + }); + + let cluster = generators::cluster::with_operators(&operators); + db.insert_cluster(cluster.clone()) + .expect("Failed to insert cluster"); - // Create members for the cluster - for i in 0..num_operators { - let member = dummy_cluster_member(cluster_id, OperatorId(i)); - members.push(member); + Self { + db, + cluster, + operators, + path: db_path, + _temp_dir: temp_dir, + } } - Cluster { - cluster_id, - cluster_members: members, - faulty: 0, - liquidated: false, - validator_metadata: dummy_validator_metadata(), + pub fn new_empty() -> Self { + let temp_dir = TempDir::new().expect("Failed to create temporary directory"); + let db_path = temp_dir.path().join("test.db"); + + let db = NetworkDatabase::new(&db_path, None).expect("Failed to create test database"); + + Self { + db, + cluster: generators::cluster::random(0), // Empty cluster + operators: Vec::new(), + path: db_path, + _temp_dir: temp_dir, + } } } -// Generate a random ClusterMember -pub fn dummy_cluster_member(cluster_id: ClusterId, operator_id: OperatorId) -> ClusterMember { - ClusterMember { - operator_id, - cluster_id, - share: dummy_share(), +// Generator functions for test data +pub mod generators { + use super::*; + + pub mod operator { + use super::*; + + pub fn random() -> Operator { + with_id(rand::thread_rng().gen()) + } + + pub fn with_id(id: u64) -> Operator { + let priv_key = Rsa::generate(RSA_KEY_SIZE).expect("Failed to generate RSA key"); + let public_key = priv_key + .public_key_to_pem() + .and_then(|pem| Rsa::public_key_from_pem(&pem)) + .expect("Failed to process RSA key"); + + Operator::new_with_pubkey(public_key, OperatorId(id), Address::random()) + } } -} -// Generate a random Share -pub fn dummy_share() -> Share { - Share { - share_pubkey: random_pubkey(), - encrypted_private_key: [0u8; 256], + pub mod cluster { + use super::*; + + pub fn random(num_operators: u64) -> Cluster { + let cluster_id = ClusterId(rand::thread_rng().gen::().into()); + let members = (0..num_operators) + .map(|i| member::new(cluster_id, OperatorId(i))) + .collect(); + + Cluster { + cluster_id, + cluster_members: members, + faulty: 0, + liquidated: false, + validator_metadata: validator::random_metadata(), + } + } + + pub fn with_operators(operators: &[Operator]) -> Cluster { + let cluster_id = ClusterId(rand::thread_rng().gen::().into()); + let members = operators + .iter() + .map(|op| member::new(cluster_id, op.id)) + .collect(); + + Cluster { + cluster_id, + cluster_members: members, + faulty: 0, + liquidated: false, + validator_metadata: validator::random_metadata(), + } + } } -} -// Generate random validator metadata -pub fn dummy_validator_metadata() -> ValidatorMetadata { - ValidatorMetadata { - validator_index: ValidatorIndex(rand::thread_rng().gen::()), - validator_pubkey: random_pubkey(), - fee_recipient: Address::random(), - graffiti: Graffiti::default(), - owner: Address::random(), + pub mod member { + use super::*; + + pub fn new(cluster_id: ClusterId, operator_id: OperatorId) -> ClusterMember { + ClusterMember { + operator_id, + cluster_id, + share: share::random(), + } + } } -} -// Construct a mock database with a cluster -pub fn db_with_cluster(db: &mut NetworkDatabase) -> Cluster { - for i in 0..4 { - let operator = dummy_operator(i); - db.insert_operator(&operator).unwrap(); + pub mod share { + use super::*; + + pub fn random() -> Share { + Share { + share_pubkey: pubkey::random(), + encrypted_private_key: [0u8; 256], + } + } } - // Insert a dummy cluster - let cluster = dummy_cluster(4); - db.insert_cluster(cluster.clone()).unwrap(); - cluster -} + pub mod pubkey { + use super::*; -// Get an Operator from the database -pub fn get_operator_from_db(db: &NetworkDatabase, id: OperatorId) -> Option { - let conn = db.connection().unwrap(); - let mut query = conn - .prepare( - "SELECT operator_id, public_key, owner_address FROM operators WHERE operator_id = ?1", - ) - .unwrap(); - let res: Option = query - .query_row(params![*id], |row| Ok(row.try_into().unwrap())) - .ok(); - res -} + pub fn random() -> PublicKey { + let rng = &mut XorShiftRng::from_seed(DEFAULT_SEED); + PublicKey::random_for_test(rng) + } + } -// Get a cluster from the database -pub fn get_cluster_from_db(db: &NetworkDatabase, id: ClusterId) -> Option<(i64, i64, bool)> { - let conn = db.connection().unwrap(); - let mut stmt = conn - .prepare("SELECT cluster_id, faulty, liquidated FROM clusters WHERE cluster_id = ?1") - .unwrap(); - let cluster_row: Option<(i64, i64, bool)> = stmt - .query_row(params![*id], |row| { - Ok((row.get(0)?, row.get(1)?, row.get(2)?)) - }) - .optional() - .unwrap(); - cluster_row -} + pub mod validator { + use super::*; -// Get all of the shares for a cluster -// Get all shares for a cluster -pub fn get_shares_from_db( - db: &NetworkDatabase, - cluster_id: ClusterId, -) -> Vec<(String, i64, i64, Option)> { - let conn = db.connection().unwrap(); - let mut stmt = conn - .prepare("SELECT validator_pubkey, cluster_id, operator_id, share_pubkey FROM shares WHERE cluster_id = ?1") - .unwrap(); - let shares = stmt - .query_map(params![*cluster_id], |row| { - Ok(( - row.get(0).unwrap(), - row.get(1).unwrap(), - row.get(2).unwrap(), - row.get(3).unwrap(), - )) - }) - .unwrap() - .map(|r| r.unwrap()) - .collect(); - shares + pub fn random_metadata() -> ValidatorMetadata { + ValidatorMetadata { + validator_index: ValidatorIndex(rand::thread_rng().gen()), + validator_pubkey: pubkey::random(), + fee_recipient: Address::random(), + graffiti: Graffiti::default(), + owner: Address::random(), + } + } + } } -// Get validator metadata from the database -pub fn get_validator_from_db(db: &NetworkDatabase, pubkey: &str) -> Option<(String, i64)> { - let conn = db.connection().unwrap(); - let mut stmt = conn - .prepare("SELECT validator_pubkey, cluster_id FROM validators WHERE validator_pubkey = ?1") - .unwrap(); - stmt.query_row(params![pubkey], |row| Ok((row.get(0)?, row.get(1)?))) - .optional() - .unwrap() -} +/// Database queries for testing +pub mod queries { + use super::*; + + pub fn get_operator(db: &NetworkDatabase, id: OperatorId) -> Option { + let conn = db.connection().unwrap(); + let operators = conn.prepare("SELECT operator_id, public_key, owner_address FROM operators WHERE operator_id = ?1") + .unwrap() + .query_row(params![*id], |row| Ok(row.try_into().unwrap())) + .ok(); + operators + } + + pub fn get_cluster(db: &NetworkDatabase, id: ClusterId) -> Option<(i64, i64, bool)> { + let conn = db.connection().unwrap(); + let cluster = conn + .prepare("SELECT cluster_id, faulty, liquidated FROM clusters WHERE cluster_id = ?1") + .unwrap() + .query_row(params![*id], |row| { + Ok((row.get(0)?, row.get(1)?, row.get(2)?)) + }) + .optional() + .unwrap(); + cluster + } -// Get a ClusterMember from the database -pub fn get_cluster_member_from_db( - db: &NetworkDatabase, - cluster_id: ClusterId, - operator_id: OperatorId, -) -> Option<(i64, i64)> { - let conn = db.connection().unwrap(); - let mut stmt = conn.prepare("SELECT cluster_id, operator_id FROM cluster_members WHERE cluster_id = ?1 AND operator_id = ?2").unwrap(); - let member_row: Option<(i64, i64)> = stmt - .query_row(params![*cluster_id, *operator_id], |row| { - Ok((row.get(0)?, row.get(1)?)) - }) - .optional() - .unwrap(); - member_row + pub fn get_shares( + db: &NetworkDatabase, + cluster_id: ClusterId, + ) -> Vec<(String, i64, i64, Option)> { + let conn = db.connection().unwrap(); + + let mut stmt = conn + .prepare("SELECT validator_pubkey, cluster_id, operator_id, share_pubkey FROM shares WHERE cluster_id = ?1") + .unwrap(); + let shares = stmt + .query_map(params![*cluster_id], |row| { + Ok(( + row.get(0).unwrap(), + row.get(1).unwrap(), + row.get(2).unwrap(), + row.get(3).unwrap(), + )) + }) + .unwrap() + .map(|r| r.unwrap()) + .collect(); + shares + } + + pub fn get_cluster_member( + db: &NetworkDatabase, + cluster_id: ClusterId, + operator_id: OperatorId, + ) -> Option<(i64, i64)> { + let conn = db.connection().unwrap(); + let member = conn.prepare("SELECT cluster_id, operator_id FROM cluster_members WHERE cluster_id = ?1 AND operator_id = ?2") + .unwrap() + .query_row(params![*cluster_id, *operator_id], |row| Ok((row.get(0)?, row.get(1)?))) + .optional() + .unwrap(); + member + } + + pub fn get_validator( + db: &NetworkDatabase, + validator_pubkey: &str, + ) -> Option<(String, i64, String)> { + let conn = db.connection().unwrap(); + let validator = conn.prepare("SELECT validator_pubkey, cluster_id, owner FROM validators WHERE validator_pubkey = ?1") + .unwrap() + .query_row(params![validator_pubkey], |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?))) + .optional() + .unwrap(); + validator + } } -// Debug print the entire database. For testing purposes -pub fn debug_print_db(db: &NetworkDatabase) { - let conn = db.connection().unwrap(); - - println!("\n=== CLUSTERS ==="); - let mut stmt = conn.prepare("SELECT * FROM clusters").unwrap(); - let clusters = stmt - .query_map([], |row| { - Ok(format!( - "Cluster ID: {}, Faulty: {}, Liquidated: {}", - row.get::<_, i64>(0).unwrap(), - row.get::<_, i64>(1).unwrap(), - row.get::<_, bool>(2).unwrap() - )) - }) - .unwrap(); - for cluster in clusters { - println!("{}", cluster.unwrap()); +/// Database assertions for testing +pub mod assertions { + use super::*; + + pub fn assert_operator_exists_fully(db: &NetworkDatabase, operator: &Operator) { + // Check in-memory state + let fetched = db + .get_operator(&operator.id) + .expect("Operator not found in memory"); + + assert_eq!(fetched.id, operator.id, "Operator ID mismatch in memory"); + assert_eq!( + fetched.rsa_pubkey.public_key_to_pem().unwrap(), + operator.rsa_pubkey.public_key_to_pem().unwrap(), + "Operator public key mismatch in memory" + ); + assert_eq!( + fetched.owner, operator.owner, + "Operator owner mismatch in memory" + ); + + // Check database state + let db_operator = + queries::get_operator(db, operator.id).expect("Operator not found in database"); + + assert_eq!( + db_operator.rsa_pubkey.public_key_to_pem().unwrap(), + operator.rsa_pubkey.public_key_to_pem().unwrap(), + "Operator public key mismatch in database" + ); + assert_eq!( + db_operator.id, operator.id, + "Operator ID mismatch in database" + ); + assert_eq!( + db_operator.owner, operator.owner, + "Operator owner mismatch in database" + ); } - println!("\n=== OPERATORS ==="); - let mut stmt = conn.prepare("SELECT * FROM operators").unwrap(); - let operators = stmt - .query_map([], |row| { - Ok(format!( - "Operator ID: {}, PublicKey: {}, Owner: {}", - row.get::<_, i64>(0).unwrap(), - row.get::<_, String>(1).unwrap(), - row.get::<_, String>(2).unwrap() - )) - }) - .unwrap(); - for operator in operators { - println!("{}", operator.unwrap()); + pub fn assert_operator_not_exists_fully(db: &NetworkDatabase, operator_id: OperatorId) { + // Check memory + assert!( + db.get_operator(&operator_id).is_none(), + "Operator still exists in memory" + ); + + // Check database + assert!( + queries::get_operator(db, operator_id).is_none(), + "Operator still exists in database" + ); } - println!("\n=== CLUSTER MEMBERS ==="); - let mut stmt = conn.prepare("SELECT * FROM cluster_members").unwrap(); - let members = stmt - .query_map([], |row| { - Ok(format!( - "Cluster ID: {}, Operator ID: {}", - row.get::<_, i64>(0).unwrap(), - row.get::<_, i64>(1).unwrap() - )) - }) - .unwrap(); - for member in members { - println!("{}", member.unwrap()); + /// Verifies that a cluster exists and all its data is correctly stored + pub fn assert_cluster_exists_fully(db: &NetworkDatabase, cluster: &Cluster) { + // Check cluster base data + let (id, faulty, liquidated) = + queries::get_cluster(db, cluster.cluster_id).expect("Cluster not found in database"); + + assert_eq!(id as u64, *cluster.cluster_id, "Cluster ID mismatch"); + assert_eq!( + faulty as u64, cluster.faulty, + "Cluster faulty count mismatch" + ); + assert_eq!( + liquidated, cluster.liquidated, + "Cluster liquidated status mismatch" + ); + + // Verify cluster is in memory if we're a member + if let Some(our_id) = db.state.id { + if cluster + .cluster_members + .iter() + .any(|m| m.operator_id == our_id) + { + assert!( + db.state.clusters.contains(&cluster.cluster_id), + "Cluster not found in memory state" + ); + assert_eq!( + db.state.cluster_members[&cluster.cluster_id].len(), + cluster.cluster_members.len(), + "Cluster members count mismatch in memory" + ); + } + } + + // Verify cluster members + for member in &cluster.cluster_members { + let member_exists = + queries::get_cluster_member(db, member.cluster_id, member.operator_id) + .expect("Cluster member not found in database"); + + assert_eq!( + member_exists.0 as u64, *member.cluster_id, + "Cluster member cluster ID mismatch" + ); + assert_eq!( + member_exists.1 as u64, *member.operator_id, + "Cluster member operator ID mismatch" + ); + } + + // Verify shares + let shares = queries::get_shares(db, cluster.cluster_id); + assert!(!shares.is_empty(), "No shares found for cluster"); + + // Verify validator metadata + let validator = + queries::get_validator(db, &cluster.validator_metadata.validator_pubkey.to_string()) + .expect("Validator not found in database"); + + assert_eq!( + validator.0, + cluster.validator_metadata.validator_pubkey.to_string(), + "Validator pubkey mismatch" + ); + assert_eq!( + validator.1 as u64, *cluster.cluster_id, + "Validator cluster ID mismatch" + ); + assert_eq!( + validator.2, + cluster.validator_metadata.owner.to_string(), + "Validator owner mismatch" + ); } - println!("\n=== VALIDATORS ==="); - let mut stmt = conn.prepare("SELECT * FROM validators").unwrap(); - let validators = stmt - .query_map([], |row| { - Ok(format!( - "Pubkey: {}, Cluster ID: {}, Fee Recipient: {:?}, Owner: {:?}, Graffiti: {:?}, Index: {:?}", - row.get::<_, String>(0).unwrap(), - row.get::<_, i64>(1).unwrap(), - row.get::<_, Option>(2).unwrap(), - row.get::<_, Option>(3).unwrap(), - row.get::<_, Vec>(4).unwrap(), - row.get::<_, Option>(5).unwrap() - )) - }) - .unwrap(); - for validator in validators { - println!("{}", validator.unwrap()); + /// Verifies that a cluster does not exist in any form + pub fn assert_cluster_exists_not_fully(db: &NetworkDatabase, cluster: &Cluster) { + // Verify cluster base data is gone + assert!( + queries::get_cluster(db, cluster.cluster_id).is_none(), + "Cluster still exists in database" + ); + + // Verify cluster is not in memory + assert!( + !db.state.clusters.contains(&cluster.cluster_id), + "Cluster still exists in memory state" + ); + assert!( + !db.state.cluster_members.contains_key(&cluster.cluster_id), + "Cluster members still exist in memory state" + ); + + // Verify all cluster members are gone + for member in &cluster.cluster_members { + assert!( + queries::get_cluster_member(db, member.cluster_id, member.operator_id).is_none(), + "Cluster member still exists in database" + ); + } + + // Verify all shares are gone + let shares = queries::get_shares(db, cluster.cluster_id); + assert!(shares.is_empty(), "Shares still exist for cluster"); + + // Verify validator metadata is gone + assert!( + queries::get_validator(db, &cluster.validator_metadata.validator_pubkey.to_string()) + .is_none(), + "Validator still exists in database" + ); + assert!( + !db.state + .validator_metadata + .contains_key(&cluster.cluster_id), + "Validator metadata still exists in memory state" + ); } +} + +pub mod debug { + use super::*; + pub fn debug_print_db(db: &NetworkDatabase) { + let conn = db.connection().unwrap(); + + println!("\n=== CLUSTERS ==="); + let mut stmt = conn.prepare("SELECT * FROM clusters").unwrap(); + let clusters = stmt + .query_map([], |row| { + Ok(format!( + "Cluster ID: {}, Faulty: {}, Liquidated: {}", + row.get::<_, i64>(0).unwrap(), + row.get::<_, i64>(1).unwrap(), + row.get::<_, bool>(2).unwrap() + )) + }) + .unwrap(); + for cluster in clusters { + println!("{}", cluster.unwrap()); + } + + println!("\n=== OPERATORS ==="); + let mut stmt = conn.prepare("SELECT * FROM operators").unwrap(); + let operators = stmt + .query_map([], |row| { + Ok(format!( + "Operator ID: {}, PublicKey: {}, Owner: {}", + row.get::<_, i64>(0).unwrap(), + row.get::<_, String>(1).unwrap(), + row.get::<_, String>(2).unwrap() + )) + }) + .unwrap(); + for operator in operators { + println!("{}", operator.unwrap()); + } + + println!("\n=== CLUSTER MEMBERS ==="); + let mut stmt = conn.prepare("SELECT * FROM cluster_members").unwrap(); + let members = stmt + .query_map([], |row| { + Ok(format!( + "Cluster ID: {}, Operator ID: {}", + row.get::<_, i64>(0).unwrap(), + row.get::<_, i64>(1).unwrap() + )) + }) + .unwrap(); + for member in members { + println!("{}", member.unwrap()); + } + + println!("\n=== VALIDATORS ==="); + let mut stmt = conn.prepare("SELECT * FROM validators").unwrap(); + let validators = stmt + .query_map([], |row| { + Ok(format!( + "Pubkey: {}, Cluster ID: {}, Fee Recipient: {:?}, Owner: {:?}, Graffiti: {:?}, Index: {:?}", + row.get::<_, String>(0).unwrap(), + row.get::<_, i64>(1).unwrap(), + row.get::<_, Option>(2).unwrap(), + row.get::<_, Option>(3).unwrap(), + row.get::<_, Vec>(4).unwrap(), + row.get::<_, Option>(5).unwrap() + )) + }) + .unwrap(); + for validator in validators { + println!("{}", validator.unwrap()); + } - println!("\n=== SHARES ==="); - let mut stmt = conn.prepare("SELECT * FROM shares").unwrap(); - let shares = stmt - .query_map([], |row| { - Ok(format!( - "Validator Pubkey: {}, Cluster ID: {}, Operator ID: {}, Share Pubkey: {:?}", - row.get::<_, String>(0).unwrap(), - row.get::<_, i64>(1).unwrap(), - row.get::<_, i64>(2).unwrap(), - row.get::<_, Option>(3).unwrap() - )) - }) - .unwrap(); - for share in shares { - println!("{}", share.unwrap()); + println!("\n=== SHARES ==="); + let mut stmt = conn.prepare("SELECT * FROM shares").unwrap(); + let shares = stmt + .query_map([], |row| { + Ok(format!( + "Validator Pubkey: {}, Cluster ID: {}, Operator ID: {}, Share Pubkey: {:?}", + row.get::<_, String>(0).unwrap(), + row.get::<_, i64>(1).unwrap(), + row.get::<_, i64>(2).unwrap(), + row.get::<_, Option>(3).unwrap() + )) + }) + .unwrap(); + for share in shares { + println!("{}", share.unwrap()); + } } } diff --git a/anchor/network/Cargo.toml b/anchor/network/Cargo.toml index 73801a6a..3c8bac68 100644 --- a/anchor/network/Cargo.toml +++ b/anchor/network/Cargo.toml @@ -17,4 +17,4 @@ serde = { workspace = true } tracing = { workspace = true } [dev-dependencies] -async-channel = { workspace = true } \ No newline at end of file +async-channel = { workspace = true }