From 44918f25879544637a2319b6d2881a23abcccc0f Mon Sep 17 00:00:00 2001 From: Brandon Vandegrift <798832-bmv437@users.noreply.gitlab.com> Date: Mon, 17 Feb 2025 15:22:25 +0000 Subject: [PATCH] fix(wasm)!: Update createDhtRecord with optional owner/kind to match veilid-core --- veilid-core/src/veilid_api/routing_context.rs | 2 +- veilid-wasm/src/veilid_routing_context_js.rs | 74 ++++++++++++++----- .../tests/src/VeilidRoutingContext.test.ts | 35 ++++++++- 3 files changed, 89 insertions(+), 22 deletions(-) diff --git a/veilid-core/src/veilid_api/routing_context.rs b/veilid-core/src/veilid_api/routing_context.rs index f366cfd0..704d6b93 100644 --- a/veilid-core/src/veilid_api/routing_context.rs +++ b/veilid-core/src/veilid_api/routing_context.rs @@ -225,7 +225,7 @@ impl RoutingContext { /////////////////////////////////// /// DHT Records - /// Builds the record key for a given schema and owner public key + /// Deterministicly builds the record key for a given schema and owner public key #[instrument(target = "veilid_api", level = "debug", ret, err)] pub fn get_dht_record_key( &self, diff --git a/veilid-wasm/src/veilid_routing_context_js.rs b/veilid-wasm/src/veilid_routing_context_js.rs index a972cd93..e2433b4d 100644 --- a/veilid-wasm/src/veilid_routing_context_js.rs +++ b/veilid-wasm/src/veilid_routing_context_js.rs @@ -189,49 +189,81 @@ impl VeilidRoutingContext { APIResult::Ok(answer) } - /// DHT Records Creates a new DHT record a specified crypto kind and schema + /////////////////////////////////// + /// DHT Records + + /// Deterministicly builds the record key for a given schema and owner public key + pub async fn getDhtRecordKey( + &self, + schema: DHTSchema, + owner: String, + kind: Option, + ) -> APIResult { + let owner = PublicKey::from_str(&owner)?; + let crypto_kind = kind + .map(|kind| veilid_core::FourCC::from_str(&kind)) + .map_or(APIResult::Ok(None), |r| r.map(Some))?; + let routing_context = self.getRoutingContext()?; + + let key = routing_context.get_dht_record_key(schema, &owner, crypto_kind)?; + APIResult::Ok(key.to_string()) + } + + /// 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. /// - /// @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. pub async fn createDhtRecord( &self, schema: DHTSchema, - kind: String, + owner: Option, + kind: Option, ) -> APIResult { - let crypto_kind = if kind.is_empty() { - None - } else { - Some(veilid_core::FourCC::from_str(&kind)?) - }; + let crypto_kind = kind + .map(|kind| veilid_core::FourCC::from_str(&kind)) + .map_or(APIResult::Ok(None), |r| r.map(Some))?; + let owner_keypair = owner + .map(|owner| KeyPair::from_str(&owner)) + .map_or(APIResult::Ok(None), |r| r.map(Some))?; let routing_context = self.getRoutingContext()?; let dht_record_descriptor = routing_context - .create_dht_record(schema, None, crypto_kind) + .create_dht_record(schema, owner_keypair, crypto_kind) .await?; APIResult::Ok(dht_record_descriptor) } /// Opens a DHT record at a specific key. /// - /// Associates a secret if one is provided to provide writer capability. Records may only be opened or created. To re-open with a different routing context, first close the value. + /// Associates a 'default_writer' secret if one is provided to provide writer capability. The + /// writer can be overridden if specified here via the set_dht_value writer. + /// + /// Records may only be opened or created. If a record is re-opened it will use the new writer and routing context + /// ignoring the settings of the last time it was opened. This allows one to open a record a second time + /// without first closing it, which will keep the active 'watches' on the record but change the default writer or + /// safety selection. /// /// @returns the DHT record descriptor for the opened record if successful. - /// @param {string} writer - Stringified key pair, in the form of `key:secret` where `key` and `secret` are base64Url encoded. /// @param {string} key - key of the DHT record. + /// @param {string} default_writer - Stringified key pair, in the form of `key:secret` where `key` and `secret` are base64Url encoded. #[wasm_bindgen(skip_jsdoc)] pub async fn openDhtRecord( &self, key: String, - writer: Option, + default_writer: Option, ) -> APIResult { let key = TypedKey::from_str(&key)?; - let writer = writer - .map(|writer| KeyPair::from_str(&writer)) + let default_writer = default_writer + .map(|default_writer| KeyPair::from_str(&default_writer)) .map_or(APIResult::Ok(None), |r| r.map(Some))?; let routing_context = self.getRoutingContext()?; - let dht_record_descriptor = routing_context.open_dht_record(key, writer).await?; + let dht_record_descriptor = routing_context.open_dht_record(key, default_writer).await?; APIResult::Ok(dht_record_descriptor) } @@ -245,11 +277,11 @@ impl VeilidRoutingContext { APIRESULT_UNDEFINED } - /// Deletes a DHT record at a specific key + /// Deletes a DHT record at a specific key. /// /// If the record is opened, it must be closed before it is deleted. - /// Deleting a record does not delete it from the network, but will remove the storage of the record locally, - /// and will prevent its value from being refreshed on the network by this node. + /// Deleting a record does not delete it from the network, but will remove the storage of the record + /// locally, and will prevent its value from being refreshed on the network by this node. pub async fn deleteDhtRecord(&self, key: String) -> APIResult<()> { let key = TypedKey::from_str(&key)?; let routing_context = self.getRoutingContext()?; @@ -277,7 +309,10 @@ impl VeilidRoutingContext { APIResult::Ok(res) } - /// Pushes a changed subkey value to the network + /// Pushes a changed subkey value to the network. + /// The DHT record must first by opened via open_dht_record or create_dht_record. + /// + /// The writer, if specified, will override the 'default_writer' specified when the record is opened. /// /// Returns `undefined` if the value was successfully put. /// Returns a Uint8Array of `data` if the value put was older than the one available on the network. @@ -352,6 +387,7 @@ impl VeilidRoutingContext { /// are subtracted from the watched subkey range. If no range is specified, this is equivalent to cancelling the entire range of subkeys. /// Only the subkey range is changed, the expiration and count remain the same. /// If no subkeys remain, the watch is entirely cancelled and will receive no more updates. + /// /// Returns true if there is any remaining watch for this record /// Returns false if the entire watch has been cancelled pub async fn cancelDhtWatch( diff --git a/veilid-wasm/tests/src/VeilidRoutingContext.test.ts b/veilid-wasm/tests/src/VeilidRoutingContext.test.ts index 4bdacb36..d4989c3d 100644 --- a/veilid-wasm/tests/src/VeilidRoutingContext.test.ts +++ b/veilid-wasm/tests/src/VeilidRoutingContext.test.ts @@ -76,18 +76,49 @@ describe('VeilidRoutingContext', () => { }); }); + describe('createDhtRecord', () => { + it('should create dht record with default schema', async () => { + const dhtRecord = await routingContext.createDhtRecord({ kind: 'DFLT', o_cnt: 1 }); + expect(dhtRecord.key).toBeDefined(); + expect(dhtRecord.owner).toBeDefined(); + expect(dhtRecord.owner_secret).toBeDefined(); + expect(dhtRecord.schema).toEqual({ kind: 'DFLT', o_cnt: 1 }); + }); + + it('should create dht record with default schema, no owner', async () => { + const dhtRecord = await routingContext.createDhtRecord({ kind: 'DFLT', o_cnt: 1 }, undefined, veilidCrypto.bestCryptoKind()); + expect(dhtRecord.key).toBeDefined(); + expect(dhtRecord.owner).toBeDefined(); + expect(dhtRecord.owner_secret).toBeDefined(); + expect(dhtRecord.schema).toEqual({ kind: 'DFLT', o_cnt: 1 }); + }); + + it('should create dht record with default schema, with owner, and a deterministic key', async () => { + const bestCryptoKind = veilidCrypto.bestCryptoKind(); + const ownerKeyPair = veilidCrypto.generateKeyPair(bestCryptoKind); + const [owner, secret] = ownerKeyPair.split(':'); + const dhtRecordKey = await routingContext.getDhtRecordKey({ kind: 'DFLT', o_cnt: 1 }, owner, bestCryptoKind); + const dhtRecord = await routingContext.createDhtRecord({ kind: 'DFLT', o_cnt: 1 }, ownerKeyPair, bestCryptoKind); + expect(dhtRecord.key).toBeDefined(); + expect(dhtRecord.key).toEqual(dhtRecordKey); + expect(dhtRecord.owner).toBeDefined(); + expect(dhtRecord.owner).toEqual(owner); + expect(dhtRecord.owner_secret).toBeDefined(); + expect(dhtRecord.owner_secret).toEqual(secret); + expect(dhtRecord.schema).toEqual({ kind: 'DFLT', o_cnt: 1 }); + }); + }); + describe('DHT kitchen sink', () => { let dhtRecord: DHTRecordDescriptor; const data = '🚀 This example DHT data with unicode a Ā 𐀀 文 🚀'; beforeEach('create dht record', async () => { - const bestKind = veilidCrypto.bestCryptoKind(); dhtRecord = await routingContext.createDhtRecord( { kind: 'DFLT', o_cnt: 1, }, - bestKind ); expect(dhtRecord.key).toBeDefined();