diff --git a/libindy_vdr/Cargo.toml b/libindy_vdr/Cargo.toml index ae5d95f0..a377218f 100644 --- a/libindy_vdr/Cargo.toml +++ b/libindy_vdr/Cargo.toml @@ -60,7 +60,7 @@ thiserror = "1.0" time = { version = "=0.3.20", features = ["parsing"] } url = "2.2.2" zmq = "0.9" -async-trait = "0.1.77" +async-trait = "0.1.80" async-lock = "3.3.0" sled = "0.34.7" diff --git a/libindy_vdr/src/pool/cache/mod.rs b/libindy_vdr/src/pool/cache/mod.rs index 7950eb20..068bdbad 100644 --- a/libindy_vdr/src/pool/cache/mod.rs +++ b/libindy_vdr/src/pool/cache/mod.rs @@ -1,17 +1,17 @@ -use async_lock::RwLock; -use async_trait::async_trait; -use std::{fmt::Display, sync::Arc}; +use std::{ + fmt::Display, + sync::{Arc, RwLock}, +}; pub mod storage; pub mod strategy; -#[async_trait] pub trait CacheStrategy: Send + Sync + 'static { - async fn get(&self, key: &K) -> Option; + fn get(&self, key: &K) -> Option; - async fn remove(&mut self, key: &K) -> Option; + fn remove(&self, key: &K) -> Option; - async fn insert(&mut self, key: K, value: V, custom_exp_offset: Option) -> Option; + fn insert(&self, key: K, value: V, custom_exp_offset: Option) -> Option; } pub struct Cache { @@ -34,23 +34,28 @@ impl Cache { } } - pub async fn get(&self, key: &K) -> Option { + pub fn get(&self, key: &K) -> Option { let full_key = self.full_key(key); - self.storage.read().await.get(&full_key).await + if let Some(storage) = self.storage.read().ok() { + return storage.get(&full_key); + } + None } - pub async fn remove(&self, key: &K) -> Option { + pub fn remove(&self, key: &K) -> Option { let full_key = self.full_key(key); - self.storage.write().await.remove(&full_key).await + if let Some(storage) = self.storage.write().ok() { + return storage.remove(&full_key); + } + None } - pub async fn insert(&self, key: K, value: V, custom_exp_offset: Option) -> Option { + pub fn insert(&self, key: K, value: V, custom_exp_offset: Option) -> Option { let full_key = self.full_key(&key); - self.storage - .write() - .await - .insert(full_key, value, custom_exp_offset) - .await + if let Some(storage) = self.storage.write().ok() { + return storage.insert(full_key, value, custom_exp_offset); + } + None } } diff --git a/libindy_vdr/src/pool/cache/strategy.rs b/libindy_vdr/src/pool/cache/strategy.rs index 1f6a2fca..0cf4b2fe 100644 --- a/libindy_vdr/src/pool/cache/strategy.rs +++ b/libindy_vdr/src/pool/cache/strategy.rs @@ -1,8 +1,13 @@ use super::storage::OrderedHashMap; use super::CacheStrategy; -use async_lock::Mutex; -use async_trait::async_trait; -use std::{collections::BTreeMap, fmt::Debug, hash::Hash, sync::Arc, time::SystemTime}; +use std::{ + collections::BTreeMap, + fmt::Debug, + hash::Hash, + ops::Deref, + sync::{Arc, Mutex}, + time::SystemTime, +}; /// A simple struct to hold a value and the expiry offset /// needed because items can be inserted with custom ttl values @@ -50,89 +55,94 @@ impl CacheStrategy for Arc> { - async fn get(&self, key: &K) -> Option { - self.get(key).await + fn get(&self, key: &K) -> Option { + self.deref().get(key) } - async fn remove(&mut self, key: &K) -> Option { - self.remove(key).await + fn remove(&self, key: &K) -> Option { + self.deref().remove(key) } - async fn insert(&mut self, key: K, value: V, custom_exp_offset: Option) -> Option { - self.insert(key, value, custom_exp_offset).await + fn insert(&self, key: K, value: V, custom_exp_offset: Option) -> Option { + self.deref().insert(key, value, custom_exp_offset) } } -#[async_trait] impl CacheStrategy for CacheStrategyTTL { - async fn get(&self, key: &K) -> Option { - let mut store_lock = self.store.lock().await; - let current_time = SystemTime::now() - .duration_since(self.create_time) - .unwrap() - .as_millis(); - let get_res = match store_lock.get(key) { - Some((ts, v)) => { - if current_time < *ts { - Some((*ts, v.clone())) - } else { - store_lock.remove(key); - None + fn get(&self, key: &K) -> Option { + if let Some(mut store_lock) = self.store.lock().ok() { + let current_time = SystemTime::now() + .duration_since(self.create_time) + .unwrap() + .as_millis(); + let get_res = match store_lock.get(key) { + Some((ts, v)) => { + if current_time < *ts { + Some((*ts, v.clone())) + } else { + store_lock.remove(key); + None + } } + None => None, + }; + // update the timestamp if the entry is still valid + if let Some((_, ref v)) = get_res { + store_lock.re_order(key, current_time + v.expire_offset); } - None => None, - }; - // update the timestamp if the entry is still valid - if let Some((_, ref v)) = get_res { - store_lock.re_order(key, current_time + v.expire_offset); + return get_res.map(|(_, v)| v.value); } - get_res.map(|(_, v)| v.value) + None } - async fn remove(&mut self, key: &K) -> Option { - self.store.lock().await.remove(key).map(|(_, v)| v.value) + fn remove(&self, key: &K) -> Option { + if let Some(mut store) = self.store.lock().ok() { + return store.remove(key).map(|(_, v)| v.value); + } + None } - async fn insert(&mut self, key: K, value: V, custom_exp_offset: Option) -> Option { - let mut store_lock = self.store.lock().await; - let current_ts = SystemTime::now() - .duration_since(self.create_time) - .unwrap() - .as_millis(); - - // remove expired entries - while store_lock.len() > 0 - && store_lock - .get_first_key_value() - .map(|(_, ts, _)| ts.clone() < current_ts) - .unwrap_or(false) - { - store_lock.remove_first(); - } + fn insert(&self, key: K, value: V, custom_exp_offset: Option) -> Option { + if let Some(mut store_lock) = self.store.lock().ok() { + let current_ts = SystemTime::now() + .duration_since(self.create_time) + .unwrap() + .as_millis(); - // remove the oldest item if the cache is still full - if store_lock.len() >= self.capacity && store_lock.get(&key).is_none() { - // remove the oldest item - let removal_key = store_lock.get_first_key_value().map(|(k, _, _)| k.clone()); - if let Some(removal_key) = removal_key { - store_lock.remove(&removal_key); + // remove expired entries + while store_lock.len() > 0 + && store_lock + .get_first_key_value() + .map(|(_, ts, _)| ts.clone() < current_ts) + .unwrap_or(false) + { + store_lock.remove_first(); } - }; - let exp_offset = custom_exp_offset.unwrap_or(self.expire_after); - store_lock - .insert( - key, - TTLCacheItem { - value: value, - expire_offset: exp_offset, - }, - current_ts + exp_offset, - ) - .map(|v| v.value) + // remove the oldest item if the cache is still full + if store_lock.len() >= self.capacity && store_lock.get(&key).is_none() { + // remove the oldest item + let removal_key = store_lock.get_first_key_value().map(|(k, _, _)| k.clone()); + if let Some(removal_key) = removal_key { + store_lock.remove(&removal_key); + } + }; + + let exp_offset = custom_exp_offset.unwrap_or(self.expire_after); + return store_lock + .insert( + key, + TTLCacheItem { + value: value, + expire_offset: exp_offset, + }, + current_ts + exp_offset, + ) + .map(|v| v.value); + } + None } } @@ -158,53 +168,31 @@ mod tests { let caches = vec![cache, fs_cache]; block_on(async { for cache in caches { - cache - .insert("key".to_string(), "value".to_string(), None) - .await; - assert_eq!( - cache.get(&"key".to_string()).await, - Some("value".to_string()) - ); - cache - .insert("key1".to_string(), "value1".to_string(), None) - .await; - cache - .insert("key2".to_string(), "value2".to_string(), None) - .await; - assert_eq!(cache.get(&"key".to_string()).await, None); - cache - .insert("key3".to_string(), "value3".to_string(), None) - .await; - cache.get(&"key2".to_string()).await; - cache - .insert("key4".to_string(), "value4".to_string(), None) - .await; + cache.insert("key".to_string(), "value".to_string(), None); + assert_eq!(cache.get(&"key".to_string()), Some("value".to_string())); + cache.insert("key1".to_string(), "value1".to_string(), None); + cache.insert("key2".to_string(), "value2".to_string(), None); + assert_eq!(cache.get(&"key".to_string()), None); + cache.insert("key3".to_string(), "value3".to_string(), None); + cache.get(&"key2".to_string()); + cache.insert("key4".to_string(), "value4".to_string(), None); // key2 should not be evicted because of LRU assert_eq!( - cache.remove(&"key2".to_string()).await, + cache.remove(&"key2".to_string()), Some("value2".to_string()) ); // key3 should be evicted because it was bumped to back after key2 was accessed - assert_eq!(cache.get(&"key3".to_string()).await, None); - cache - .insert("key5".to_string(), "value5".to_string(), None) - .await; + assert_eq!(cache.get(&"key3".to_string()), None); + cache.insert("key5".to_string(), "value5".to_string(), None); thread::sleep(std::time::Duration::from_millis(6)); - assert_eq!(cache.get(&"key5".to_string()).await, None); + assert_eq!(cache.get(&"key5".to_string()), None); // test ttl config - cache - .insert("key6".to_string(), "value6".to_string(), Some(1)) - .await; - cache - .insert("key7".to_string(), "value7".to_string(), None) - .await; + cache.insert("key6".to_string(), "value6".to_string(), Some(1)); + cache.insert("key7".to_string(), "value7".to_string(), None); // wait until value6 expires thread::sleep(std::time::Duration::from_millis(1)); - assert_eq!(cache.get(&"key6".to_string()).await, None); - assert_eq!( - cache.get(&"key7".to_string()).await, - Some("value7".to_string()) - ); + assert_eq!(cache.get(&"key6".to_string()), None); + assert_eq!(cache.get(&"key7".to_string()), Some("value7".to_string())); } std::fs::remove_dir_all(cache_location).unwrap(); }); diff --git a/libindy_vdr/src/pool/helpers.rs b/libindy_vdr/src/pool/helpers.rs index e5f1aea0..ac2955b8 100644 --- a/libindy_vdr/src/pool/helpers.rs +++ b/libindy_vdr/src/pool/helpers.rs @@ -223,7 +223,7 @@ pub async fn perform_ledger_request( if is_read_req { if let Some(cache) = cache_opt.clone() { - if let Some((response, meta)) = cache.get(&cache_key).await { + if let Some((response, meta)) = cache.get(&cache_key) { return Ok((RequestResult::Reply(response), meta)); } } @@ -240,9 +240,7 @@ pub async fn perform_ledger_request( } } if let Some(cache) = cache_opt { - cache - .insert(cache_key, (response.to_string(), meta.clone()), None) - .await; + cache.insert(cache_key, (response.to_string(), meta.clone()), None); } } } diff --git a/wrappers/javascript/.gitignore b/wrappers/javascript/.gitignore index 852b83eb..724961eb 100644 --- a/wrappers/javascript/.gitignore +++ b/wrappers/javascript/.gitignore @@ -49,3 +49,5 @@ react-native/android/gradle* **/build **/native/mobile genesis.txn + +libindy_vdr.dylib diff --git a/wrappers/javascript/indy-vdr-nodejs/README.md b/wrappers/javascript/indy-vdr-nodejs/README.md index b82c3024..0442a6ee 100644 --- a/wrappers/javascript/indy-vdr-nodejs/README.md +++ b/wrappers/javascript/indy-vdr-nodejs/README.md @@ -51,3 +51,13 @@ yarn test:local-build ``` > **Note**: If you want to use this library in a cross-platform environment you need to import methods from the `@hyperledger/indy-vdr-shared` package instead. This is a platform independent package that allows to register the native bindings. The `@hyperledger/indy-vdr-nodejs` package uses this package under the hood. See the [Indy VDR Shared README](https://github.com/hyperledger/indy-vdr/tree/main/wrappers/javascript/indy-vdr-shared/README.md) for documentation on how to use this package. + +## Version Compatibility + +The JavaScript wrapper is versioned independently from the native bindings. The following table shows the compatibility between the different versions: + +| Indy VDR | JavaScript Wrapper | +| ------------- | ------------------ | +| v0.4.0-dev.16 | v0.1.0 | +| v0.4.1 | v0.2.x | +| v0.4.2 | v0.3.0 | \ No newline at end of file diff --git a/wrappers/javascript/indy-vdr-nodejs/package.json b/wrappers/javascript/indy-vdr-nodejs/package.json index 2658c3ce..6708211e 100644 --- a/wrappers/javascript/indy-vdr-nodejs/package.json +++ b/wrappers/javascript/indy-vdr-nodejs/package.json @@ -1,6 +1,6 @@ { "name": "@hyperledger/indy-vdr-nodejs", - "version": "0.2.0-dev.6", + "version": "0.2.3", "license": "Apache-2.0", "description": "Nodejs wrapper for Indy Vdr", "source": "src/index", @@ -41,7 +41,7 @@ "dependencies": { "@2060.io/ffi-napi": "4.0.9", "@2060.io/ref-napi": "3.0.6", - "@hyperledger/indy-vdr-shared": "0.2.0-dev.6", + "@hyperledger/indy-vdr-shared": "0.2.3", "@mapbox/node-pre-gyp": "^1.0.10", "@types/ref-array-di": "^1.2.8", "ref-array-di": "^1.2.2", @@ -50,7 +50,7 @@ "binary": { "module_name": "indy_vdr", "module_path": "native", - "remote_path": "v0.4.0", + "remote_path": "v0.4.2", "host": "https://github.com/hyperledger/indy-vdr/releases/download/", "package_name": "library-{platform}-{arch}.tar.gz" }, diff --git a/wrappers/javascript/indy-vdr-nodejs/src/library/bindings.ts b/wrappers/javascript/indy-vdr-nodejs/src/library/bindings.ts index 1f844264..3119e2cf 100644 --- a/wrappers/javascript/indy-vdr-nodejs/src/library/bindings.ts +++ b/wrappers/javascript/indy-vdr-nodejs/src/library/bindings.ts @@ -16,6 +16,7 @@ import { export const nativeBindings = { // first element is method return type, second element is list of method argument types + indy_vdr_set_ledger_txn_cache: [FFI_ERROR_CODE, [FFI_INT32, FFI_INT64, FFI_STRING]], indy_vdr_set_config: [FFI_ERROR_CODE, [FFI_STRING]], indy_vdr_set_default_logger: [FFI_ERROR_CODE, []], indy_vdr_set_protocol_version: [FFI_ERROR_CODE, [FFI_INT64]], diff --git a/wrappers/javascript/indy-vdr-nodejs/tests/IndyVdrTxnCache.test.ts b/wrappers/javascript/indy-vdr-nodejs/tests/IndyVdrTxnCache.test.ts new file mode 100644 index 00000000..c3f3b295 --- /dev/null +++ b/wrappers/javascript/indy-vdr-nodejs/tests/IndyVdrTxnCache.test.ts @@ -0,0 +1,39 @@ +import type { GetTransactionResponse, IndyVdrPool } from '@hyperledger/indy-vdr-nodejs' + +import { genesisTxnPath } from './utils' + +import { PoolCreate } from '@hyperledger/indy-vdr-nodejs' +import { GetTransactionRequest, indyVdr } from '@hyperledger/indy-vdr-shared' +import { accessSync, rmSync } from 'fs'; + +describe('IndyVdrTxnCache', () => { + let pool: IndyVdrPool + + beforeAll(() => { + indyVdr.setLedgerTxnCache({ capacity: 100, expiry_offset_ms: 60 * 60 * 1000, path: "txn-cache" }) + pool = new PoolCreate({ parameters: { transactions_path: genesisTxnPath } }) + }) + + afterAll(() => { + rmSync('txn-cache', { force: true, recursive: true }) + }) + + test('Get pool handle', () => { + let accessed = false + try { + accessSync('txn-cache') + accessed = true + } catch { } + expect(accessed).toBe(true) + }) + + test('Submit GetTxn request', async () => { + const request = new GetTransactionRequest({ ledgerType: 1, seqNo: 1 }) + const response: GetTransactionResponse = await pool.submitRequest(request) + + const requestCached = new GetTransactionRequest({ ledgerType: 1, seqNo: 1 }) + const responseCached: GetTransactionResponse = await pool.submitRequest(requestCached) + expect(response).toMatchObject(responseCached) + }) + +}) diff --git a/wrappers/javascript/indy-vdr-react-native/cpp/indyVdr.cpp b/wrappers/javascript/indy-vdr-react-native/cpp/indyVdr.cpp index 0b59d156..706134f8 100644 --- a/wrappers/javascript/indy-vdr-react-native/cpp/indyVdr.cpp +++ b/wrappers/javascript/indy-vdr-react-native/cpp/indyVdr.cpp @@ -33,8 +33,8 @@ jsi::Value setCacheDirectory(jsi::Runtime &rt, jsi::Object options) { }; jsi::Value setLedgerTxnCache(jsi::Runtime &rt, jsi::Object options) { - auto capacity = jsiToValue(rt, options, "capacity"); - auto expiry_offset_ms = jsiToValue(rt, options, "expiry_offset_ms"); + auto capacity = jsiToValue(rt, options, "capacity"); + auto expiry_offset_ms = jsiToValue(rt, options, "expiry_offset_ms"); auto path = jsiToValue(rt, options, "path", true); ErrorCode code = indy_vdr_set_ledger_txn_cache(capacity, expiry_offset_ms, path.length() > 0 ? path.c_str() : nullptr); @@ -250,11 +250,13 @@ jsi::Value buildGetNymRequest(jsi::Runtime &rt, jsi::Object options) { auto seqNo = jsiToValue(rt, options, "seqNo", true); auto timestamp = jsiToValue(rt, options, "timestamp", true); + auto convertedSeqNo = seqNo == 0 ? -1 : seqNo; + RequestHandle out; ErrorCode code = indy_vdr_build_get_nym_request( submitterDid.length() > 0 ? submitterDid.c_str() : nullptr, dest.c_str(), - seqNo, timestamp, &out); + convertedSeqNo, timestamp, &out); return createReturnValue(rt, code, &out); }; diff --git a/wrappers/javascript/indy-vdr-react-native/cpp/turboModuleUtility.cpp b/wrappers/javascript/indy-vdr-react-native/cpp/turboModuleUtility.cpp index 3ff7d9a4..5a029ea3 100644 --- a/wrappers/javascript/indy-vdr-react-native/cpp/turboModuleUtility.cpp +++ b/wrappers/javascript/indy-vdr-react-native/cpp/turboModuleUtility.cpp @@ -143,7 +143,7 @@ int64_t jsiToValue(jsi::Runtime &rt, jsi::Object &options, const char *name, bool optional) { jsi::Value value = options.getProperty(rt, name); if ((value.isNull() || value.isUndefined()) && optional) - return 0; + return -1; if (value.isNumber()) return value.asNumber(); @@ -169,7 +169,7 @@ int32_t jsiToValue(jsi::Runtime &rt, jsi::Object &options, const char *name, bool optional) { jsi::Value value = options.getProperty(rt, name); if ((value.isNull() || value.isUndefined()) && optional) - return 0; + return -1; if (value.isNumber()) return value.asNumber(); diff --git a/wrappers/javascript/indy-vdr-react-native/package.json b/wrappers/javascript/indy-vdr-react-native/package.json index 23b3695e..9f0161d7 100644 --- a/wrappers/javascript/indy-vdr-react-native/package.json +++ b/wrappers/javascript/indy-vdr-react-native/package.json @@ -1,6 +1,6 @@ { "name": "@hyperledger/indy-vdr-react-native", - "version": "0.2.0-dev.6", + "version": "0.2.3", "license": "Apache-2.0", "description": "React Native wrapper for Indy Vdr", "source": "src/index", @@ -40,7 +40,7 @@ "install": "node-pre-gyp install" }, "dependencies": { - "@hyperledger/indy-vdr-shared": "0.2.0-dev.6", + "@hyperledger/indy-vdr-shared": "0.2.3", "@mapbox/node-pre-gyp": "^1.0.10" }, "devDependencies": { @@ -58,7 +58,7 @@ "binary": { "module_name": "indy_vdr", "module_path": "native", - "remote_path": "v0.4.0", + "remote_path": "v0.4.2", "host": "https://github.com/hyperledger/indy-vdr/releases/download/", "package_name": "library-ios-android.tar.gz" } diff --git a/wrappers/javascript/indy-vdr-react-native/src/NativeBindings.ts b/wrappers/javascript/indy-vdr-react-native/src/NativeBindings.ts index 5d602819..991f38b6 100644 --- a/wrappers/javascript/indy-vdr-react-native/src/NativeBindings.ts +++ b/wrappers/javascript/indy-vdr-react-native/src/NativeBindings.ts @@ -91,6 +91,7 @@ export interface NativeBindings { dest: string didDocContent?: string version?: number + seqNo?: number }): ReturnObject buildGetSchemaRequest(options: { submitterDid?: string; schemaId: string }): ReturnObject diff --git a/wrappers/javascript/indy-vdr-shared/README.md b/wrappers/javascript/indy-vdr-shared/README.md index cc0c6fc5..cb5174dc 100644 --- a/wrappers/javascript/indy-vdr-shared/README.md +++ b/wrappers/javascript/indy-vdr-shared/README.md @@ -57,3 +57,13 @@ try { ``` How you approach it is up to you, as long as the native binding are called before any actions are performed on the Indy VDR library. + +## Version Compatibility + +The JavaScript wrapper is versioned independently from the native bindings. The following table shows the compatibility between the different versions: + +| Indy VDR | JavaScript Wrapper | +| ------------- | ------------------ | +| v0.4.0-dev.16 | v0.1.0 | +| v0.4.1 | v0.2.x | +| v0.4.2 | v0.3.0 | \ No newline at end of file diff --git a/wrappers/javascript/indy-vdr-shared/package.json b/wrappers/javascript/indy-vdr-shared/package.json index 9ab59847..aa530937 100644 --- a/wrappers/javascript/indy-vdr-shared/package.json +++ b/wrappers/javascript/indy-vdr-shared/package.json @@ -1,6 +1,6 @@ { "name": "@hyperledger/indy-vdr-shared", - "version": "0.2.0-dev.6", + "version": "0.2.3", "license": "Apache-2.0", "description": "Shared library for using Indy VDR with NodeJS and React Native", "main": "build/index", diff --git a/wrappers/javascript/lerna.json b/wrappers/javascript/lerna.json index d0a1a4a4..3887f74f 100644 --- a/wrappers/javascript/lerna.json +++ b/wrappers/javascript/lerna.json @@ -1,7 +1,7 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", "packages": ["indy-vdr-*"], - "version": "0.2.0-dev.6", + "version": "0.2.3", "npmClient": "yarn", "command": { "version": {