diff --git a/Cargo.lock b/Cargo.lock index 005e65d7b7..421c0adc3b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -115,6 +115,17 @@ dependencies = [ "version_check", ] +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if 1.0.0", + "once_cell", + "version_check", +] + [[package]] name = "aho-corasick" version = "1.0.1" @@ -248,6 +259,7 @@ dependencies = [ "lazy_static", "libvdrtools", "log", + "lru 0.10.0", "rand 0.7.3", "serde", "serde_json", @@ -2022,7 +2034,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" dependencies = [ - "ahash", + "ahash 0.7.6", ] [[package]] @@ -2031,7 +2043,16 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash", + "ahash 0.7.6", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash 0.8.3", ] [[package]] @@ -2439,7 +2460,7 @@ dependencies = [ "indy-utils 0.1.0", "libc", "log", - "lru", + "lru 0.7.8", "rmp-serde 0.13.7", "serde", "serde_derive", @@ -2770,6 +2791,15 @@ dependencies = [ "hashbrown 0.12.3", ] +[[package]] +name = "lru" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03f1160296536f10c833a82dca22267d5486734230d47bf00bf435885814ba1e" +dependencies = [ + "hashbrown 0.13.2", +] + [[package]] name = "memchr" version = "2.5.0" @@ -4211,7 +4241,7 @@ name = "sqlx-core" version = "0.5.8" source = "git+https://github.com/jovfer/sqlx?branch=feature/json_no_preserve_order_v5#7b9b4b371071e7d29d3b10da5a205460b3fc2de4" dependencies = [ - "ahash", + "ahash 0.7.6", "atoi", "base64 0.13.1", "bitflags 1.3.2", @@ -4265,7 +4295,7 @@ version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "195183bf6ff8328bb82c0511a83faf60aacf75840103388851db61d7a9854ae3" dependencies = [ - "ahash", + "ahash 0.7.6", "atoi", "bitflags 1.3.2", "byteorder", diff --git a/aries_vcx/src/core/profile/modular_libs_profile.rs b/aries_vcx/src/core/profile/modular_libs_profile.rs index 74179f7810..bb91f74be1 100644 --- a/aries_vcx/src/core/profile/modular_libs_profile.rs +++ b/aries_vcx/src/core/profile/modular_libs_profile.rs @@ -1,4 +1,6 @@ +use std::num::NonZeroUsize; use std::sync::Arc; +use std::time::Duration; use aries_vcx_core::anoncreds::base_anoncreds::BaseAnonCreds; use aries_vcx_core::anoncreds::credx_anoncreds::IndyCredxAnonCreds; @@ -6,10 +8,11 @@ use aries_vcx_core::ledger::base_ledger::BaseLedger; use aries_vcx_core::ledger::indy_vdr_ledger::{IndyVdrLedger, IndyVdrLedgerConfig}; use aries_vcx_core::ledger::request_signer::base_wallet::BaseWalletRequestSigner; use aries_vcx_core::ledger::request_submitter::vdr_ledger::{IndyVdrLedgerPool, IndyVdrSubmitter, LedgerPoolConfig}; +use aries_vcx_core::ledger::response_cacher::in_memory::{InMemoryResponseCacher, InMemoryResponseCacherConfig}; use aries_vcx_core::wallet::base_wallet::BaseWallet; use aries_vcx_core::ResponseParser; -use crate::errors::error::VcxResult; +use crate::errors::error::{AriesVcxError, AriesVcxErrorKind, VcxResult}; use super::profile::Profile; @@ -28,10 +31,19 @@ impl ModularLibsProfile { let request_signer = Arc::new(BaseWalletRequestSigner::new(wallet.clone())); let request_submitter = Arc::new(IndyVdrSubmitter::new(ledger_pool)); let response_parser = Arc::new(ResponseParser::new()); + let cacher_config = InMemoryResponseCacherConfig { + ttl: Duration::from_secs(60), + capacity: NonZeroUsize::new(1000).ok_or(AriesVcxError::from_msg( + AriesVcxErrorKind::InvalidConfiguration, + "Failed to parse cache capacity into NonZeroUsize", + ))?, + }; + let response_cacher = Arc::new(InMemoryResponseCacher::new(cacher_config)); let config = IndyVdrLedgerConfig { request_signer, request_submitter, response_parser, + response_cacher, }; let ledger = Arc::new(IndyVdrLedger::new(config)); Ok(ModularLibsProfile { diff --git a/aries_vcx/src/core/profile/vdr_proxy_profile.rs b/aries_vcx/src/core/profile/vdr_proxy_profile.rs index 7f00815797..d46060c23a 100644 --- a/aries_vcx/src/core/profile/vdr_proxy_profile.rs +++ b/aries_vcx/src/core/profile/vdr_proxy_profile.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{num::NonZeroUsize, sync::Arc, time::Duration}; use aries_vcx_core::{ anoncreds::{base_anoncreds::BaseAnonCreds, indy_anoncreds::IndySdkAnonCreds}, @@ -7,11 +7,14 @@ use aries_vcx_core::{ indy_vdr_ledger::{IndyVdrLedger, IndyVdrLedgerConfig}, request_signer::base_wallet::BaseWalletRequestSigner, request_submitter::vdr_proxy::VdrProxySubmitter, + response_cacher::in_memory::{InMemoryResponseCacher, InMemoryResponseCacherConfig}, }, wallet::{base_wallet::BaseWallet, indy_wallet::IndySdkWallet}, ResponseParser, VdrProxyClient, WalletHandle, }; +use crate::errors::error::{AriesVcxError, AriesVcxErrorKind, VcxResult}; + use super::profile::Profile; #[derive(Debug)] @@ -22,23 +25,32 @@ pub struct VdrProxyProfile { } impl VdrProxyProfile { - pub fn new(wallet_handle: WalletHandle, client: VdrProxyClient) -> Self { + pub fn new(wallet_handle: WalletHandle, client: VdrProxyClient) -> VcxResult { let wallet = Arc::new(IndySdkWallet::new(wallet_handle)); let anoncreds = Arc::new(IndySdkAnonCreds::new(wallet_handle)); let request_signer = Arc::new(BaseWalletRequestSigner::new(wallet.clone())); let request_submitter = Arc::new(VdrProxySubmitter::new(Arc::new(client))); let response_parser = Arc::new(ResponseParser::new()); + let cacher_config = InMemoryResponseCacherConfig { + ttl: Duration::from_secs(60), + capacity: NonZeroUsize::new(1000).ok_or(AriesVcxError::from_msg( + AriesVcxErrorKind::InvalidConfiguration, + "Failed to parse cache capacity into NonZeroUsize", + ))?, + }; + let response_cacher = Arc::new(InMemoryResponseCacher::new(cacher_config)); let config = IndyVdrLedgerConfig { request_signer, request_submitter, response_parser, + response_cacher, }; let ledger = Arc::new(IndyVdrLedger::new(config)); - VdrProxyProfile { + Ok(VdrProxyProfile { wallet, ledger, anoncreds, - } + }) } } diff --git a/aries_vcx/src/utils/devsetup.rs b/aries_vcx/src/utils/devsetup.rs index e7109445ec..632c5375bd 100644 --- a/aries_vcx/src/utils/devsetup.rs +++ b/aries_vcx/src/utils/devsetup.rs @@ -464,7 +464,7 @@ impl SetupProfile { let client_url = env::var("VDR_PROXY_CLIENT_URL").unwrap_or_else(|_| "http://127.0.0.1:3030".to_string()); let client = VdrProxyClient::new(&client_url).unwrap(); - let profile: Arc = Arc::new(VdrProxyProfile::new(wallet_handle, client)); + let profile: Arc = Arc::new(VdrProxyProfile::new(wallet_handle, client).unwrap()); async fn vdr_proxy_teardown() { // nothing to do diff --git a/aries_vcx_core/Cargo.toml b/aries_vcx_core/Cargo.toml index b37fe3e148..460c1f0996 100644 --- a/aries_vcx_core/Cargo.toml +++ b/aries_vcx_core/Cargo.toml @@ -8,8 +8,8 @@ edition = "2021" # Feature flag to include the libvdrtools dependency vdrtools = ["dep:libvdrtools"] # Feature flag to include the 'modular library' dependencies (vdrtools alternatives; indy-vdr, indy-credx) -modular_libs = ["dep:indy-vdr", "dep:indy-credx", "dep:indy-ledger-response-parser"] -vdr_proxy_ledger = ["dep:indy-vdr", "dep:indy-credx", "dep:indy-vdr-proxy-client", "dep:indy-ledger-response-parser"] +modular_libs = ["dep:indy-vdr", "dep:indy-credx", "dep:indy-ledger-response-parser", "dep:lru"] +vdr_proxy_ledger = ["dep:indy-vdr", "dep:indy-credx", "dep:indy-vdr-proxy-client", "dep:indy-ledger-response-parser", "dep:lru"] [dependencies] agency_client = { path = "../agency_client" } @@ -43,6 +43,7 @@ tokio = { version = "1.20" } # TODO: Point to the official repo if / when vdr-proxy-client PR is merged: https://github.com/hyperledger/indy-vdr/pull/184 indy-vdr-proxy-client = { git = "https://github.com/mirgee/indy-vdr.git", rev = "fab0535", optional = true } indy-ledger-response-parser = { path = "../indy_ledger_response_parser", optional = true } +lru = { version = "0.10.0", optional = true } [dev-dependencies] tokio = { version = "1.20", features = ["rt", "macros", "rt-multi-thread"] } diff --git a/aries_vcx_core/src/ledger/indy_vdr_ledger.rs b/aries_vcx_core/src/ledger/indy_vdr_ledger.rs index dcf9a21a05..eb47a183c5 100644 --- a/aries_vcx_core/src/ledger/indy_vdr_ledger.rs +++ b/aries_vcx_core/src/ledger/indy_vdr_ledger.rs @@ -25,37 +25,44 @@ use crate::global::settings; use super::base_ledger::BaseLedger; use super::request_signer::RequestSigner; use super::request_submitter::RequestSubmitter; +use super::response_cacher::ResponseCacher; -pub struct IndyVdrLedgerConfig +pub struct IndyVdrLedgerConfig where T: RequestSubmitter + Send + Sync, U: RequestSigner + Send + Sync, + V: ResponseCacher + Send + Sync, { pub request_signer: Arc, pub request_submitter: Arc, pub response_parser: Arc, + pub response_cacher: Arc, } -pub struct IndyVdrLedger +pub struct IndyVdrLedger where T: RequestSubmitter + Send + Sync, U: RequestSigner + Send + Sync, + V: ResponseCacher + Send + Sync, { request_signer: Arc, request_submitter: Arc, response_parser: Arc, + response_cacher: Arc, } -impl IndyVdrLedger +impl IndyVdrLedger where T: RequestSubmitter + Send + Sync, U: RequestSigner + Send + Sync, + V: ResponseCacher + Send + Sync, { - pub fn new(config: IndyVdrLedgerConfig) -> Self { + pub fn new(config: IndyVdrLedgerConfig) -> Self { Self { request_signer: config.request_signer, request_submitter: config.request_submitter, response_parser: config.response_parser, + response_cacher: config.response_cacher, } } @@ -66,6 +73,16 @@ where Ok(RequestBuilder::new(version)) } + async fn _submit_request_cached(&self, id: &str, request: PreparedRequest) -> VcxCoreResult { + if let Some(response) = self.response_cacher.get(id).await? { + Ok(response) + } else { + let response = self.request_submitter.submit(request).await?; + self.response_cacher.put(id, json!(response).to_string()).await?; + Ok(response) + } + } + async fn _submit_request(&self, request: PreparedRequest) -> VcxCoreResult { self.request_submitter.submit(request).await } @@ -193,10 +210,11 @@ where } } -impl Debug for IndyVdrLedger +impl Debug for IndyVdrLedger where T: RequestSubmitter + Send + Sync, U: RequestSigner + Send + Sync, + V: ResponseCacher + Send + Sync, { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "IndyVdrLedger instance") @@ -204,10 +222,11 @@ where } #[async_trait] -impl BaseLedger for IndyVdrLedger +impl BaseLedger for IndyVdrLedger where T: RequestSubmitter + Send + Sync, U: RequestSigner + Send + Sync, + V: ResponseCacher + Send + Sync, { async fn sign_and_submit_request(&self, submitter_did: &str, request_json: &str) -> VcxCoreResult { let request = PreparedRequest::from_request_json(request_json)?; @@ -303,7 +322,7 @@ where async fn get_rev_reg_def_json(&self, rev_reg_id: &str) -> VcxCoreResult { let id = RevocationRegistryId::from_str(rev_reg_id)?; let request = self.request_builder()?.build_get_revoc_reg_def_request(None, &id)?; - let res = self._submit_request(request).await?; + let res = self._submit_request_cached(rev_reg_id, request).await?; let rev_reg_def = self.response_parser.parse_get_revoc_reg_def_response(&res)?; @@ -325,7 +344,7 @@ where let request = self .request_builder()? .build_get_revoc_reg_delta_request(None, &revoc_reg_def_id, from, to)?; - let res = self._submit_request(request).await?; + let res = self._submit_request_cached(rev_reg_id, request).await?; let RevocationRegistryDeltaInfo { revoc_reg_def_id, @@ -348,7 +367,7 @@ where &revoc_reg_def_id, timestamp.try_into().unwrap(), )?; - let res = self._submit_request(request).await?; + let res = self._submit_request_cached(rev_reg_id, request).await?; let RevocationRegistryInfo { revoc_reg_def_id, diff --git a/aries_vcx_core/src/ledger/mod.rs b/aries_vcx_core/src/ledger/mod.rs index 7bea8c26cc..a31a11ce76 100644 --- a/aries_vcx_core/src/ledger/mod.rs +++ b/aries_vcx_core/src/ledger/mod.rs @@ -7,3 +7,5 @@ pub mod indy_vdr_ledger; pub mod request_signer; #[cfg(any(feature = "modular_libs", feature = "vdr_proxy_ledger"))] pub mod request_submitter; +#[cfg(any(feature = "modular_libs", feature = "vdr_proxy_ledger"))] +pub mod response_cacher; diff --git a/aries_vcx_core/src/ledger/response_cacher/in_memory.rs b/aries_vcx_core/src/ledger/response_cacher/in_memory.rs new file mode 100644 index 0000000000..2fae5a0ea4 --- /dev/null +++ b/aries_vcx_core/src/ledger/response_cacher/in_memory.rs @@ -0,0 +1,73 @@ +use std::collections::HashMap; +use std::num::NonZeroUsize; +use std::sync::Arc; +use std::time::{Duration, Instant}; + +use async_trait::async_trait; +use lru::LruCache; +use serde::{Deserialize, Serialize}; +use tokio::sync::RwLock; + +use crate::errors::error::VcxCoreResult; + +use super::ResponseCacher; + +// TODO: Configs should be optional with defaults +// TODO: Perhaps should use a builder +pub struct InMemoryResponseCacherConfig { + pub ttl: Duration, + pub capacity: NonZeroUsize, +} + +pub struct InMemoryResponseCacher { + cache: Arc>>, + config: InMemoryResponseCacherConfig, +} + +impl InMemoryResponseCacher { + pub fn new(config: InMemoryResponseCacherConfig) -> Self { + Self { + cache: Arc::new(RwLock::new(LruCache::new(config.capacity))), + config, + } + } +} + +#[async_trait] +impl ResponseCacher for InMemoryResponseCacher { + async fn put(&self, id: S, obj: T) -> VcxCoreResult<()> + where + S: ToString + Send, + T: Serialize + for<'de> Deserialize<'de> + Send, + { + let id = id.to_string(); + let obj = serde_json::to_string(&obj)?; + + let mut cache = self.cache.write().await; + cache.put(id, (obj, Instant::now())); + Ok(()) + } + + // TODO: Perhaps should be possible to override TTL + async fn get(&self, id: S) -> VcxCoreResult> + where + S: ToString + Send, + T: Serialize + for<'de> Deserialize<'de> + Send, + { + let id = id.to_string(); + + let mut cache = self.cache.write().await; + match cache.get(&id) { + Some((obj, timestamp)) => { + if timestamp.elapsed() > self.config.ttl { + cache.pop(&id); + Ok(None) + } else { + let obj: T = serde_json::from_str(obj)?; + Ok(Some(obj)) + } + } + None => Ok(None), + } + } +} diff --git a/aries_vcx_core/src/ledger/response_cacher/mod.rs b/aries_vcx_core/src/ledger/response_cacher/mod.rs new file mode 100644 index 0000000000..8a85ca5082 --- /dev/null +++ b/aries_vcx_core/src/ledger/response_cacher/mod.rs @@ -0,0 +1,19 @@ +pub mod in_memory; + +use async_trait::async_trait; +use serde::{Deserialize, Serialize}; + +use crate::errors::error::VcxCoreResult; + +#[async_trait] +pub trait ResponseCacher: Send + Sync { + async fn put(&self, id: S, obj: T) -> VcxCoreResult<()> + where + S: ToString + Send, + T: Serialize + for<'de> Deserialize<'de> + Send; + + async fn get(&self, id: S) -> VcxCoreResult> + where + S: ToString + Send, + T: Serialize + for<'de> Deserialize<'de> + Send; +} diff --git a/did_resolver_sov/src/error/mod.rs b/did_resolver_sov/src/error/mod.rs index 2218afe714..fc08f94bb9 100644 --- a/did_resolver_sov/src/error/mod.rs +++ b/did_resolver_sov/src/error/mod.rs @@ -29,6 +29,8 @@ pub enum DidSovError { DidDocumentBuilderError(#[from] DidDocumentBuilderError), #[error("Parsing error: {0}")] ParsingError(#[from] ParsingErrorSource), + #[error("Invalid configuration: {0}")] + InvalidConfiguration(String), #[error(transparent)] Other(#[from] Box), } diff --git a/did_resolver_sov/src/reader/vdr_reader.rs b/did_resolver_sov/src/reader/vdr_reader.rs index 9a1213f3ee..6b8e76fd72 100644 --- a/did_resolver_sov/src/reader/vdr_reader.rs +++ b/did_resolver_sov/src/reader/vdr_reader.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{num::NonZeroUsize, sync::Arc, time::Duration}; use crate::error::DidSovError; use aries_vcx_core::{ @@ -6,6 +6,7 @@ use aries_vcx_core::{ indy_vdr_ledger::{IndyVdrLedger, IndyVdrLedgerConfig}, request_signer::base_wallet::BaseWalletRequestSigner, request_submitter::vdr_ledger::{IndyVdrLedgerPool, IndyVdrSubmitter, LedgerPoolConfig}, + response_cacher::in_memory::{InMemoryResponseCacher, InMemoryResponseCacherConfig}, }, wallet::{base_wallet::BaseWallet, indy_wallet::IndySdkWallet}, ResponseParser, INVALID_WALLET_HANDLE, @@ -22,10 +23,18 @@ impl TryFrom for ConcreteAttrReader { let request_submitter = Arc::new(IndyVdrSubmitter::new(ledger_pool)); let request_signer = Arc::new(BaseWalletRequestSigner::new(wallet.clone())); let response_parser = Arc::new(ResponseParser::new()); + let cacher_config = InMemoryResponseCacherConfig { + ttl: Duration::from_secs(60), + capacity: NonZeroUsize::new(1000).ok_or(DidSovError::InvalidConfiguration( + "Failed to parse cache capacity into NonZeroUsize".to_string(), + ))?, + }; + let response_cacher = Arc::new(InMemoryResponseCacher::new(cacher_config)); let config = IndyVdrLedgerConfig { request_signer, request_submitter, response_parser, + response_cacher, }; let ledger = Arc::new(IndyVdrLedger::new(config)); Ok(Self { ledger })