From 992cc7a1fdc9c825045baaef3fce52dbea80c1e7 Mon Sep 17 00:00:00 2001 From: Sergei Surovsev Date: Sat, 21 Oct 2023 23:29:09 +0000 Subject: [PATCH] Rust DHT test suite --- veilid-core/src/tests/common/mod.rs | 1 + veilid-core/src/tests/common/test_dht.rs | 323 ++++++++++++++++++ .../src/tests/common/test_veilid_config.rs | 10 +- veilid-core/src/tests/native/mod.rs | 4 + 4 files changed, 335 insertions(+), 3 deletions(-) create mode 100644 veilid-core/src/tests/common/test_dht.rs diff --git a/veilid-core/src/tests/common/mod.rs b/veilid-core/src/tests/common/mod.rs index 2c507282..24824df0 100644 --- a/veilid-core/src/tests/common/mod.rs +++ b/veilid-core/src/tests/common/mod.rs @@ -1,3 +1,4 @@ +pub mod test_dht; pub mod test_protected_store; pub mod test_veilid_config; pub mod test_veilid_core; diff --git a/veilid-core/src/tests/common/test_dht.rs b/veilid-core/src/tests/common/test_dht.rs new file mode 100644 index 00000000..20a0afd7 --- /dev/null +++ b/veilid-core/src/tests/common/test_dht.rs @@ -0,0 +1,323 @@ +use super::test_veilid_config::*; +use crate::*; + +use lazy_static::*; + +lazy_static! { + static ref BOGUS_KEY: TypedKey = TypedKey::from(CryptoTyped::new( + CRYPTO_KIND_VLD0, + CryptoKey::new([0u8; 32]) + )); +} + +pub async fn test_get_dht_value_unopened(api: VeilidAPI) { + let rc = api.routing_context(); + + let result = rc.get_dht_value(*BOGUS_KEY, 0, false).await; + assert_err!(result); +} + +pub async fn test_open_dht_record_nonexistent_no_writer(api: VeilidAPI) { + let rc = api.routing_context(); + + let result = rc.get_dht_value(*BOGUS_KEY, 0, false).await; + assert_err!(result); +} + +pub async fn test_close_dht_record_nonexistent(api: VeilidAPI) { + let rc = api.routing_context(); + + let result = rc.close_dht_record(*BOGUS_KEY).await; + assert_err!(result); +} + +pub async fn test_delete_dht_record_nonexistent(api: VeilidAPI) { + let rc = api.routing_context(); + + let result = rc.delete_dht_record(*BOGUS_KEY).await; + assert_err!(result); +} + +pub async fn test_create_delete_dht_record_simple(api: VeilidAPI) { + let rc = api.routing_context(); + + let rec = rc + .create_dht_record( + DHTSchema::DFLT(DHTSchemaDFLT { o_cnt: 1 }), + Some(CRYPTO_KIND_VLD0), + ) + .await + .unwrap(); + + let dht_key = *rec.key(); + rc.close_dht_record(dht_key).await.unwrap(); + rc.delete_dht_record(dht_key).await.unwrap(); +} + +pub async fn test_get_dht_value_nonexistent(api: VeilidAPI) { + let rc = api.routing_context(); + + let rec = rc + .create_dht_record( + DHTSchema::DFLT(DHTSchemaDFLT { o_cnt: 1 }), + Some(CRYPTO_KIND_VLD0), + ) + .await + .unwrap(); + let dht_key = *rec.key(); + let result = rc.get_dht_value(dht_key, 0, false).await; + assert_eq!(result.expect("should not be error"), None); + + rc.close_dht_record(dht_key).await.unwrap(); + rc.delete_dht_record(dht_key).await.unwrap(); +} + +pub async fn test_set_get_dht_value(api: VeilidAPI) { + let rc = api.routing_context(); + + let rec = rc + .create_dht_record( + DHTSchema::DFLT(DHTSchemaDFLT { o_cnt: 2 }), + Some(CRYPTO_KIND_VLD0), + ) + .await + .unwrap(); + let dht_key = *rec.key(); + + let test_value = String::from("BLAH BLAH BLAH").as_bytes().to_vec(); + // convert string to byte array + let set_dht_value_result = rc.set_dht_value(dht_key, 0, test_value.clone()).await; + assert_eq!(set_dht_value_result.expect("should be Ok(None)"), None); + + let get_dht_value_result_0_non_force = rc.get_dht_value(dht_key, 0, false).await; + assert_eq!( + get_dht_value_result_0_non_force + .expect("should not be error") + .expect("should hold a value") + .data(), + test_value.clone() + ); + + // works in python, fails in rust + let get_dht_value_result_0_force = rc.get_dht_value(dht_key, 0, true).await; + assert_eq!( + get_dht_value_result_0_force + .expect("should not be error") + .expect("should hold a value") + .data(), + test_value.clone() + ); + + let get_dht_value_result_1_non_force = rc.get_dht_value(dht_key, 1, false).await; + assert_eq!( + get_dht_value_result_1_non_force.expect("should not be error"), + None + ); + + // assert_eq!( + // get_dht_value_result_0_non_force.expect("should hold value"), + // get_dht_value_result_1_non_force.expect("should hold value") + // ); + + rc.close_dht_record(dht_key).await.unwrap(); + rc.delete_dht_record(dht_key).await.unwrap(); +} + +pub async fn test_open_writer_dht_value(api: VeilidAPI) { + let rc = api.routing_context(); + + let rec = rc + .create_dht_record( + DHTSchema::DFLT(DHTSchemaDFLT { o_cnt: 2 }), + Some(CRYPTO_KIND_VLD0), + ) + .await + .unwrap(); + let key = *rec.key(); + let owner = rec.owner(); + let secret = rec.owner_secret().unwrap(); + let keypair = KeyPair::new(*owner, *secret); + + let test_value_1 = String::from("Qwertyuiop Asdfghjkl Zxcvbnm") + .as_bytes() + .to_vec(); + let test_data_2 = String::from("1234567890").as_bytes().to_vec(); + let test_data_3 = String::from("!@#$%^&*()").as_bytes().to_vec(); + + // Scenario 1 + // 1. Write test data 1 to subkey 1, + // 2. Read data from subkey 1, without force_refresh, check data, sequence and owner + // 3. Read data from subkey 0, should return an error + // 4. Write test data to subkey 0 + // 5. Read data from subkey 0 with force_refresh, check data + // 6. Read data from subkey 1 with force_refresh, check data + // 7. Overwrite value 1 twice, check that there's no errors + let set_dht_test_value_1_result = rc.set_dht_value(key, 1, test_value_1.clone()).await; + assert!(set_dht_test_value_1_result.is_ok()); + + let get_dht_value_result_1_non_force = rc.get_dht_value(key, 1, false).await; + assert!(get_dht_value_result_1_non_force.is_ok()); + let get_dht_value_result_1_non_force = get_dht_value_result_1_non_force + .unwrap() + .expect("should hold value"); + assert_eq!(get_dht_value_result_1_non_force.data(), test_value_1); + assert_eq!(get_dht_value_result_1_non_force.seq(), 0); + assert_eq!(get_dht_value_result_1_non_force.writer(), owner); + + let get_dht_value_result_0_non_force = rc.get_dht_value(key, 0, false).await; + assert_eq!( + get_dht_value_result_0_non_force.expect("should not be error"), + None + ); + + let set_dht_test_value_0_result = rc.set_dht_value(key, 0, test_data_2.clone()).await; + assert!(set_dht_test_value_0_result.is_ok()); + + let get_dht_value_result_0_force = rc.get_dht_value(key, 0, true).await; + assert_eq!( + get_dht_value_result_0_force + .expect("should be OK(result)") + .expect("should hold value") + .data(), + test_data_2 + ); + + let get_dht_value_result_1_force = rc.get_dht_value(key, 1, true).await; + assert_eq!( + get_dht_value_result_1_force + .expect("should be OK(result)") + .expect("should hold value") + .data(), + test_value_1 + ); + + let overwrite_value_1_result_1 = rc.set_dht_value(key, 1, test_value_1.clone()).await; + assert!(overwrite_value_1_result_1.is_ok()); + + let overwrite_value_1_result_2 = rc.set_dht_value(key, 1, test_data_2.clone()).await; + assert!(overwrite_value_1_result_2.is_ok()); + + // Now that we initialized some subkeys + // and verified they stored correctly + // Delete things locally and reopen and see if we can write + // with the same writer key + + rc.close_dht_record(key).await.unwrap(); + rc.delete_dht_record(key).await.unwrap(); + + // Scenario 2 + // 1. Open DHT record with existing keys + // 2. Check record key, owner, record secret and schema against original values + // 3. Write test data 3 to subkey 1, without updating a value check that it still + // holds test data 2, but sequence has incremented, check owner + // 4. Check that subkey 1 can be overwritten + // 5. Read data from subkey 1 with force_refresh, check data + + let rec = rc.open_dht_record(key, Some(keypair)).await; + assert!(rec.is_ok()); + let rec = rec.unwrap(); + assert_eq!(rec.key().value, key.value); + assert_eq!(rec.key().kind, key.kind); + assert_eq!(rec.owner(), owner); + assert_eq!(rec.owner_secret().unwrap(), secret); + assert!(matches!( + rec.schema().clone(), + DHTSchema::DFLT(DHTSchemaDFLT { o_cnt: 2 }) + )); + + //Verify subkey 1 can be set before it is get but newer is available online + let set_dht_test_value_1_result = rc.set_dht_value(key, 1, test_data_3.clone()).await; + assert!(set_dht_test_value_1_result.is_ok()); + let vdtemp = set_dht_test_value_1_result.unwrap().unwrap(); + assert_eq!(vdtemp.data(), test_data_2); + assert_eq!(vdtemp.seq(), 1); + assert_eq!(vdtemp.writer(), owner); + + // Verify subkey 1 can be set a second time and it updates because seq is newer + let set_dht_test_value_1_result = rc.set_dht_value(key, 1, test_data_3.clone()).await; + assert!(set_dht_test_value_1_result.is_ok()); + + // Verify the network got the subkey update with a refresh check + let get_dht_value_result_1_force = rc.get_dht_value(key, 1, true).await; + assert!(get_dht_value_result_1_force.is_ok()); + let get_dht_value_result_1_force = get_dht_value_result_1_force + .expect("should be OK(result)") + .expect("should hold value"); + assert_eq!(get_dht_value_result_1_force.data(), test_data_3); + assert_eq!(get_dht_value_result_1_force.seq(), 2); + assert_eq!(get_dht_value_result_1_force.writer(), owner); + + // Delete things locally and reopen and see if we can write + // with a different writer key (should fail) + rc.close_dht_record(key).await.unwrap(); + rc.delete_dht_record(key).await.unwrap(); + + // Scenario 3 + // 1. Open DHT record with new keypair + // 2. Check record key, owner, record secret and schema against original values + // 3. Try writing to subkey 1, expect error + // 4. Try writing to subkey 0, expect error + + let cs = api.crypto().unwrap().get(key.kind).unwrap(); + assert!(cs.validate_keypair(owner, secret)); + let other_keypair = cs.generate_keypair(); + + let rec = rc.open_dht_record(key, Some(other_keypair)).await; + assert!(rec.is_ok()); + let rec = rec.unwrap(); + assert_eq!(rec.key().value, key.value); + assert_eq!(rec.key().kind, key.kind); + assert_eq!(rec.owner(), owner); + assert_eq!(rec.owner_secret(), None); + let schema = rec.schema().clone(); + assert!(matches!( + schema, + DHTSchema::DFLT(DHTSchemaDFLT { o_cnt: 2 }) + )); + + // Verify subkey 1 can NOT be set because we have the wrong writer + let set_dht_test_value_0_result = rc.set_dht_value(key, 1, test_value_1.clone()).await; + assert_err!(set_dht_test_value_0_result); + + // Verify subkey 0 can NOT be set because we have the wrong writer + let set_dht_test_value_0_result = rc.set_dht_value(key, 0, test_value_1.clone()).await; + assert_err!(set_dht_test_value_0_result); + + rc.close_dht_record(key).await.unwrap(); + rc.delete_dht_record(key).await.unwrap(); +} + +// Network-related code to make sure veilid node is connetected to other peers + +async fn wait_for_public_internet_ready(api: &VeilidAPI) { + info!("wait_for_public_internet_ready"); + loop { + let state = api.get_state().await.unwrap(); + if state.attachment.public_internet_ready { + break; + } + sleep(5000).await; + } + info!("wait_for_public_internet_ready, done"); +} + +pub async fn test_all() { + let (update_callback, config_callback) = setup_veilid_core(); + let api = api_startup(update_callback, config_callback) + .await + .expect("startup failed"); + + let _ = api.attach().await; + wait_for_public_internet_ready(&api).await; + + test_get_dht_value_unopened(api.clone()).await; + test_open_dht_record_nonexistent_no_writer(api.clone()).await; + test_close_dht_record_nonexistent(api.clone()).await; + test_delete_dht_record_nonexistent(api.clone()).await; + test_get_dht_value_nonexistent(api.clone()).await; + test_create_delete_dht_record_simple(api.clone()).await; + test_set_get_dht_value(api.clone()).await; + test_open_writer_dht_value(api.clone()).await; + + api.shutdown().await; +} diff --git a/veilid-core/src/tests/common/test_veilid_config.rs b/veilid-core/src/tests/common/test_veilid_config.rs index 77484ad7..c38a8db9 100644 --- a/veilid-core/src/tests/common/test_veilid_config.rs +++ b/veilid-core/src/tests/common/test_veilid_config.rs @@ -162,7 +162,7 @@ pub fn setup_veilid_core() -> (UpdateCallback, ConfigCallback) { (Arc::new(update_callback), Arc::new(config_callback)) } -fn config_callback(key: String) -> ConfigCallbackReturn { +pub fn config_callback(key: String) -> ConfigCallbackReturn { match key.as_str() { "program_name" => Ok(Box::new(String::from("VeilidCoreTests"))), "namespace" => Ok(Box::::default()), @@ -191,7 +191,8 @@ fn config_callback(key: String) -> ConfigCallbackReturn { "network.network_key_password" => Ok(Box::new(Option::::None)), "network.routing_table.node_id" => Ok(Box::new(TypedKeyGroup::new())), "network.routing_table.node_id_secret" => Ok(Box::new(TypedSecretGroup::new())), - "network.routing_table.bootstrap" => Ok(Box::>::default()), + // "network.routing_table.bootstrap" => Ok(Box::new(Vec::::new())), + "network.routing_table.bootstrap" => Ok(Box::new(vec!["bootstrap.veilid.net".to_string()])), "network.routing_table.limit_over_attached" => Ok(Box::new(64u32)), "network.routing_table.limit_fully_attached" => Ok(Box::new(32u32)), "network.routing_table.limit_attached_strong" => Ok(Box::new(16u32)), @@ -325,7 +326,10 @@ pub async fn test_config() { assert_eq!(inner.network.rpc.default_route_hop_count, 1u8); assert_eq!(inner.network.routing_table.node_id.len(), 0); assert_eq!(inner.network.routing_table.node_id_secret.len(), 0); - assert_eq!(inner.network.routing_table.bootstrap, Vec::::new()); + assert_eq!( + inner.network.routing_table.bootstrap, + vec!["bootstrap.veilid.net"], + ); assert_eq!(inner.network.routing_table.limit_over_attached, 64u32); assert_eq!(inner.network.routing_table.limit_fully_attached, 32u32); assert_eq!(inner.network.routing_table.limit_attached_strong, 16u32); diff --git a/veilid-core/src/tests/native/mod.rs b/veilid-core/src/tests/native/mod.rs index 558904a5..b598a87d 100644 --- a/veilid-core/src/tests/native/mod.rs +++ b/veilid-core/src/tests/native/mod.rs @@ -17,6 +17,8 @@ pub async fn run_all_tests() { test_types::test_all().await; info!("TEST: test_veilid_core"); test_veilid_core::test_all().await; + info!("TEST: test_dht"); + test_dht::test_all().await; info!("TEST: test_veilid_config"); test_veilid_config::test_all().await; info!("TEST: test_connection_table"); @@ -133,5 +135,7 @@ cfg_if! { run_test!(veilid_api, test_serialize_json); run_test!(routing_table, test_serialize_routing_table); + + run_test!(test_dht); } }