From abfa5b12e8df51d7783187a51313ef2a690269c2 Mon Sep 17 00:00:00 2001 From: Evelyn Hobert Date: Mon, 20 Jan 2025 17:26:32 +0000 Subject: [PATCH] adding the ability to create dht records with a specified owner keypair --- veilid-core/src/storage_manager/mod.rs | 18 ++++- .../storage_manager/storage_manager_inner.rs | 43 +++++++----- veilid-core/src/tests/common/test_dht.rs | 66 +++++++++++++++++-- veilid-core/src/veilid_api/debug.rs | 2 +- .../src/veilid_api/json_api/process.rs | 2 +- veilid-core/src/veilid_api/routing_context.rs | 44 ++++++++++--- veilid-flutter/rust/src/dart_ffi.rs | 2 +- veilid-wasm/src/lib.rs | 2 +- veilid-wasm/src/veilid_routing_context_js.rs | 2 +- 9 files changed, 145 insertions(+), 36 deletions(-) diff --git a/veilid-core/src/storage_manager/mod.rs b/veilid-core/src/storage_manager/mod.rs index eb1db71e..8ac58f4b 100644 --- a/veilid-core/src/storage_manager/mod.rs +++ b/veilid-core/src/storage_manager/mod.rs @@ -229,12 +229,26 @@ impl StorageManager { .collect() } - /// Create a local record from scratch with a new owner key, open it, and return the opened descriptor + /// Builds the record key for a given schema and owner #[instrument(level = "trace", target = "stor", skip_all)] + pub async fn get_record_key( + &self, + kind: CryptoKind, + schema: DHTSchema, + owner_key: &PublicKey, + ) -> VeilidAPIResult { + let inner = self.lock().await?; + schema.validate()?; + + inner.get_record_key(kind, owner_key, schema).await + } + + /// Create a local record from scratch with a new owner key, open it, and return the opened descriptor pub async fn create_record( &self, kind: CryptoKind, schema: DHTSchema, + owner: Option, safety_selection: SafetySelection, ) -> VeilidAPIResult { let mut inner = self.lock().await?; @@ -242,7 +256,7 @@ impl StorageManager { // Create a new owned local record from scratch let (key, owner) = inner - .create_new_owned_local_record(kind, schema, safety_selection) + .create_new_owned_local_record(kind, schema, owner, safety_selection) .await?; // Now that the record is made we should always succeed to open the existing record diff --git a/veilid-core/src/storage_manager/storage_manager_inner.rs b/veilid-core/src/storage_manager/storage_manager_inner.rs index aac4da59..2907d977 100644 --- a/veilid-core/src/storage_manager/storage_manager_inner.rs +++ b/veilid-core/src/storage_manager/storage_manager_inner.rs @@ -219,6 +219,7 @@ impl StorageManagerInner { &mut self, kind: CryptoKind, schema: DHTSchema, + owner: Option, safety_selection: SafetySelection, ) -> VeilidAPIResult<(TypedKey, KeyPair)> { // Get cryptosystem @@ -248,8 +249,11 @@ impl StorageManagerInner { // Compile the dht schema let schema_data = schema.compile(); - // New values require a new owner key - let owner = vcrypto.generate_keypair(); + // New values require a new owner key if not given + let owner = owner.unwrap_or_else(|| vcrypto.generate_keypair()); + + // Calculate dht key + let dht_key = Self::get_key(vcrypto.clone(), &owner.key, &schema_data); // Make a signed value descriptor for this dht value let signed_value_descriptor = Arc::new(SignedValueDescriptor::make_signature( @@ -258,14 +262,12 @@ impl StorageManagerInner { vcrypto.clone(), owner.secret, )?); - // Add new local value record let cur_ts = Timestamp::now(); let local_record_detail = LocalRecordDetail::new(safety_selection); let record = Record::::new(cur_ts, signed_value_descriptor, local_record_detail)?; - let dht_key = Self::get_key(vcrypto.clone(), &record); local_record_store.new_record(dht_key, record).await?; Ok((dht_key, owner)) @@ -702,18 +704,29 @@ impl StorageManagerInner { }) } - /// # DHT Key = Hash(ownerKeyKind) of: [ ownerKeyValue, schema ] - #[instrument(level = "trace", target = "stor", skip_all)] - fn get_key(vcrypto: CryptoSystemVersion, record: &Record) -> TypedKey - where - D: fmt::Debug + Clone + Serialize, - { - let descriptor = record.descriptor(); - let compiled = descriptor.schema_data(); - let mut hash_data = Vec::::with_capacity(PUBLIC_KEY_LENGTH + 4 + compiled.len()); + pub async fn get_record_key( + &self, + kind: CryptoKind, + owner_key: &PublicKey, + schema: DHTSchema, + ) -> VeilidAPIResult { + // Get cryptosystem + let Some(vcrypto) = self.unlocked_inner.crypto.get(kind) else { + apibail_generic!("unsupported cryptosystem"); + }; + + Ok(Self::get_key(vcrypto, owner_key, &schema.compile())) + } + + fn get_key( + vcrypto: CryptoSystemVersion, + owner_key: &PublicKey, + schema_data: &[u8], + ) -> TypedKey { + let mut hash_data = Vec::::with_capacity(PUBLIC_KEY_LENGTH + 4 + schema_data.len()); hash_data.extend_from_slice(&vcrypto.kind().0); - hash_data.extend_from_slice(&record.owner().bytes); - hash_data.extend_from_slice(compiled); + hash_data.extend_from_slice(&owner_key.bytes); + hash_data.extend_from_slice(schema_data); let hash = vcrypto.generate_hash(&hash_data); TypedKey::new(vcrypto.kind(), hash) } diff --git a/veilid-core/src/tests/common/test_dht.rs b/veilid-core/src/tests/common/test_dht.rs index 3073c0fb..f2aeb20b 100644 --- a/veilid-core/src/tests/common/test_dht.rs +++ b/veilid-core/src/tests/common/test_dht.rs @@ -62,7 +62,7 @@ pub async fn test_create_delete_dht_record_simple(api: VeilidAPI) { .unwrap(); let rec = rc - .create_dht_record(DHTSchema::dflt(1).unwrap(), Some(CRYPTO_KIND_VLD0)) + .create_dht_record(DHTSchema::dflt(1).unwrap(), None, Some(CRYPTO_KIND_VLD0)) .await .unwrap(); @@ -71,6 +71,63 @@ pub async fn test_create_delete_dht_record_simple(api: VeilidAPI) { rc.delete_dht_record(dht_key).await.unwrap(); } +pub async fn test_create_dht_record_with_owner(api: VeilidAPI) { + let rc = api + .routing_context() + .unwrap() + .with_safety(SafetySelection::Unsafe(Sequencing::EnsureOrdered)) + .unwrap(); + + let cs = api.crypto().unwrap().get(CRYPTO_KIND_VLD0).unwrap(); + let owner_keypair = cs.generate_keypair(); + + let rec = rc + .create_dht_record( + DHTSchema::dflt(1).unwrap(), + Some(owner_keypair), + Some(CRYPTO_KIND_VLD0), + ) + .await + .unwrap(); + + assert_eq!(rec.owner(), &owner_keypair.key); + + 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_record_key(api: VeilidAPI) { + let rc = api + .routing_context() + .unwrap() + .with_safety(SafetySelection::Unsafe(Sequencing::EnsureOrdered)) + .unwrap(); + + let cs = api.crypto().unwrap().get(CRYPTO_KIND_VLD0).unwrap(); + let owner_keypair = cs.generate_keypair(); + let schema = DHTSchema::dflt(1).unwrap(); + + // create the record normally + let rec = rc + .create_dht_record(schema.clone(), Some(owner_keypair), Some(CRYPTO_KIND_VLD0)) + .await + .unwrap(); + + // recreate the record key from the metadata alone + let key = rc + .get_dht_record_key(schema.clone(), &owner_keypair.key, Some(CRYPTO_KIND_VLD0)) + .await + .unwrap(); + + // keys should be the same + assert_eq!(key, *rec.key()); + + 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() @@ -79,7 +136,7 @@ pub async fn test_get_dht_value_nonexistent(api: VeilidAPI) { .unwrap(); let rec = rc - .create_dht_record(DHTSchema::dflt(1).unwrap(), Some(CRYPTO_KIND_VLD0)) + .create_dht_record(DHTSchema::dflt(1).unwrap(), None, Some(CRYPTO_KIND_VLD0)) .await .unwrap(); let dht_key = *rec.key(); @@ -98,7 +155,7 @@ pub async fn test_set_get_dht_value(api: VeilidAPI) { .unwrap(); let rec = rc - .create_dht_record(DHTSchema::dflt(2).unwrap(), Some(CRYPTO_KIND_VLD0)) + .create_dht_record(DHTSchema::dflt(2).unwrap(), None, Some(CRYPTO_KIND_VLD0)) .await .unwrap(); let dht_key = *rec.key(); @@ -150,7 +207,7 @@ pub async fn test_open_writer_dht_value(api: VeilidAPI) { .unwrap(); let rec = rc - .create_dht_record(DHTSchema::dflt(2).unwrap(), Some(CRYPTO_KIND_VLD0)) + .create_dht_record(DHTSchema::dflt(2).unwrap(), None, Some(CRYPTO_KIND_VLD0)) .await .unwrap(); let key = *rec.key(); @@ -340,6 +397,7 @@ pub async fn test_all() { 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_create_dht_record_with_owner(api.clone()).await; test_set_get_dht_value(api.clone()).await; test_open_writer_dht_value(api.clone()).await; diff --git a/veilid-core/src/veilid_api/debug.rs b/veilid-core/src/veilid_api/debug.rs index f13841cd..66e93a38 100644 --- a/veilid-core/src/veilid_api/debug.rs +++ b/veilid-core/src/veilid_api/debug.rs @@ -1478,7 +1478,7 @@ impl VeilidAPI { }; // Do a record create - let record = match rc.create_dht_record(schema, Some(csv.kind())).await { + let record = match rc.create_dht_record(schema, None, Some(csv.kind())).await { Err(e) => return Ok(format!("Can't open DHT record: {}", e)), Ok(v) => v, }; diff --git a/veilid-core/src/veilid_api/json_api/process.rs b/veilid-core/src/veilid_api/json_api/process.rs index 44d88640..b35c8c4b 100644 --- a/veilid-core/src/veilid_api/json_api/process.rs +++ b/veilid-core/src/veilid_api/json_api/process.rs @@ -296,7 +296,7 @@ impl JsonRequestProcessor { RoutingContextResponseOp::CreateDhtRecord { result: to_json_api_result( routing_context - .create_dht_record(schema, kind) + .create_dht_record(schema, None, kind) .await .map(Box::new), ), diff --git a/veilid-core/src/veilid_api/routing_context.rs b/veilid-core/src/veilid_api/routing_context.rs index 7eee4269..ec00593f 100644 --- a/veilid-core/src/veilid_api/routing_context.rs +++ b/veilid-core/src/veilid_api/routing_context.rs @@ -225,27 +225,51 @@ impl RoutingContext { /////////////////////////////////// /// DHT Records - /// Creates a new DHT record - /// - /// The record is considered 'open' after the create operation succeeds. - /// * 'schema' - the schema to use when creating the DHT record - /// * 'kind' - specify a cryptosystem kind to use. Normally you will leave this as None to choose the 'best' cryptosystem available. - /// Returns the newly allocated DHT record's key if successful. + /// Builds the record key for a given schema and owner public key #[instrument(target = "veilid_api", level = "debug", ret, err)] - pub async fn create_dht_record( + pub async fn get_dht_record_key( &self, schema: DHTSchema, + owner_key: &PublicKey, kind: Option, - ) -> VeilidAPIResult { + ) -> VeilidAPIResult { event!(target: "veilid_api", Level::DEBUG, - "RoutingContext::create_dht_record(self: {:?}, schema: {:?}, kind: {:?})", self, schema, kind); + "RoutingContext::get_dht_record_key(self: {:?}, schema: {:?}, owner_key: {:?}, kind: {:?})", self, schema, owner_key, kind); schema.validate()?; let kind = kind.unwrap_or(best_crypto_kind()); Crypto::validate_crypto_kind(kind)?; let storage_manager = self.api.storage_manager()?; storage_manager - .create_record(kind, schema, self.unlocked_inner.safety_selection) + .get_record_key(kind, schema, owner_key) + .await + } + + /// Creates a new DHT record + /// + /// The record is considered 'open' after the create operation succeeds. + /// * 'schema' - the schema to use when creating the DHT record + /// * 'owner' - optionally specify an owner keypair to use. If you leave this as None then a random one will be generated + /// * 'kind' - specify a cryptosystem kind to use. Normally you will leave this as None to choose the 'best' cryptosystem available. + /// Returns the newly allocated DHT record's key if successful. + /// + /// Note: if you pass in an owner keypair this call is a deterministic! This means that if you try to create a new record for a given owner and schema that already exists it *will* fail. + #[instrument(target = "veilid_api", level = "debug", ret, err)] + pub async fn create_dht_record( + &self, + schema: DHTSchema, + owner: Option, + kind: Option, + ) -> VeilidAPIResult { + event!(target: "veilid_api", Level::DEBUG, + "RoutingContext::create_dht_record(self: {:?}, schema: {:?}, owner: {:?}, kind: {:?})", self, schema, owner, kind); + schema.validate()?; + + let kind = kind.unwrap_or(best_crypto_kind()); + Crypto::validate_crypto_kind(kind)?; + let storage_manager = self.api.storage_manager()?; + storage_manager + .create_record(kind, schema, owner, self.unlocked_inner.safety_selection) .await } diff --git a/veilid-flutter/rust/src/dart_ffi.rs b/veilid-flutter/rust/src/dart_ffi.rs index 80501daf..80793294 100644 --- a/veilid-flutter/rust/src/dart_ffi.rs +++ b/veilid-flutter/rust/src/dart_ffi.rs @@ -671,7 +671,7 @@ pub extern "C" fn routing_context_create_dht_record(port: i64, id: u32, schema: let routing_context = get_routing_context(id, "routing_context_create_dht_record")?; let dht_record_descriptor = routing_context - .create_dht_record(schema, crypto_kind) + .create_dht_record(schema, None, crypto_kind) .await?; APIResult::Ok(dht_record_descriptor) } diff --git a/veilid-wasm/src/lib.rs b/veilid-wasm/src/lib.rs index 85d2ffb9..5983f580 100644 --- a/veilid-wasm/src/lib.rs +++ b/veilid-wasm/src/lib.rs @@ -494,7 +494,7 @@ pub fn routing_context_create_dht_record(id: u32, schema: String, kind: u32) -> let routing_context = get_routing_context(id, "routing_context_create_dht_record")?; let dht_record_descriptor = routing_context - .create_dht_record(schema, crypto_kind) + .create_dht_record(schema, None, crypto_kind) .await?; APIResult::Ok(dht_record_descriptor) }) diff --git a/veilid-wasm/src/veilid_routing_context_js.rs b/veilid-wasm/src/veilid_routing_context_js.rs index 8cbfa5f4..a972cd93 100644 --- a/veilid-wasm/src/veilid_routing_context_js.rs +++ b/veilid-wasm/src/veilid_routing_context_js.rs @@ -207,7 +207,7 @@ impl VeilidRoutingContext { let routing_context = self.getRoutingContext()?; let dht_record_descriptor = routing_context - .create_dht_record(schema, crypto_kind) + .create_dht_record(schema, None, crypto_kind) .await?; APIResult::Ok(dht_record_descriptor) }