mirror of
https://github.com/aljazceru/cdk.git
synced 2025-12-22 07:04:56 +01:00
Introduce pluggable backend cache for the HTTP layer. (#495)
--------- Co-authored-by: thesimplekid <tsk@thesimplekid.com>
This commit is contained in:
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@@ -56,6 +56,10 @@ jobs:
|
|||||||
-p cdk --no-default-features --features "mint swagger",
|
-p cdk --no-default-features --features "mint swagger",
|
||||||
-p cdk-redb,
|
-p cdk-redb,
|
||||||
-p cdk-sqlite,
|
-p cdk-sqlite,
|
||||||
|
-p cdk-axum --no-default-features,
|
||||||
|
-p cdk-axum --no-default-features --features swagger,
|
||||||
|
-p cdk-axum --no-default-features --features redis,
|
||||||
|
-p cdk-axum --no-default-features --features "redis swagger",
|
||||||
-p cdk-axum,
|
-p cdk-axum,
|
||||||
-p cdk-cln,
|
-p cdk-cln,
|
||||||
-p cdk-lnd,
|
-p cdk-lnd,
|
||||||
@@ -65,6 +69,9 @@ jobs:
|
|||||||
-p cdk-fake-wallet,
|
-p cdk-fake-wallet,
|
||||||
--bin cdk-cli,
|
--bin cdk-cli,
|
||||||
--bin cdk-mintd,
|
--bin cdk-mintd,
|
||||||
|
--bin cdk-mintd --no-default-features --features swagger,
|
||||||
|
--bin cdk-mintd --no-default-features --features redis,
|
||||||
|
--bin cdk-mintd --no-default-features --features "redis swagger",
|
||||||
]
|
]
|
||||||
steps:
|
steps:
|
||||||
- name: checkout
|
- name: checkout
|
||||||
@@ -152,6 +159,7 @@ jobs:
|
|||||||
-p cdk --no-default-features --features wallet,
|
-p cdk --no-default-features --features wallet,
|
||||||
-p cdk --no-default-features --features mint,
|
-p cdk --no-default-features --features mint,
|
||||||
-p cdk-axum,
|
-p cdk-axum,
|
||||||
|
-p cdk-axum --no-default-features --features redis,
|
||||||
-p cdk-strike,
|
-p cdk-strike,
|
||||||
-p cdk-lnbits,
|
-p cdk-lnbits,
|
||||||
-p cdk-phoenixd,
|
-p cdk-phoenixd,
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
[language-server.rust-analyzer.config]
|
[language-server.rust-analyzer.config]
|
||||||
cargo = { features = ["wallet", "mint", "swagger"] }
|
cargo = { features = ["wallet", "mint", "swagger", "redis"] }
|
||||||
|
|||||||
887
Cargo.lock
generated
887
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -10,7 +10,7 @@ COPY Cargo.toml ./Cargo.toml
|
|||||||
COPY crates ./crates
|
COPY crates ./crates
|
||||||
|
|
||||||
# Start the Nix daemon and develop the environment
|
# Start the Nix daemon and develop the environment
|
||||||
RUN nix develop --extra-experimental-features nix-command --extra-experimental-features flakes --command cargo build --release --bin cdk-mintd
|
RUN nix develop --extra-experimental-features nix-command --extra-experimental-features flakes --command cargo build --release --bin cdk-mintd --features redis
|
||||||
|
|
||||||
# Create a runtime stage
|
# Create a runtime stage
|
||||||
FROM debian:bookworm-slim
|
FROM debian:bookworm-slim
|
||||||
|
|||||||
@@ -30,6 +30,11 @@ serde_json = "1"
|
|||||||
paste = "1.0.15"
|
paste = "1.0.15"
|
||||||
serde = { version = "1.0.210", features = ["derive"] }
|
serde = { version = "1.0.210", features = ["derive"] }
|
||||||
uuid = { version = "1", features = ["v4", "serde"] }
|
uuid = { version = "1", features = ["v4", "serde"] }
|
||||||
|
sha2 = "0.10.8"
|
||||||
|
redis = { version = "0.23.3", features = [
|
||||||
|
"tokio-rustls-comp",
|
||||||
|
], optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
redis = ["dep:redis"]
|
||||||
swagger = ["cdk/swagger", "dep:utoipa"]
|
swagger = ["cdk/swagger", "dep:utoipa"]
|
||||||
|
|||||||
45
crates/cdk-axum/src/cache/backend/memory.rs
vendored
Normal file
45
crates/cdk-axum/src/cache/backend/memory.rs
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use moka::future::Cache;
|
||||||
|
|
||||||
|
use crate::cache::{HttpCacheKey, HttpCacheStorage, DEFAULT_TTI_SECS, DEFAULT_TTL_SECS};
|
||||||
|
|
||||||
|
/// In memory cache storage for the HTTP cache.
|
||||||
|
///
|
||||||
|
/// This is the default cache storage backend, which is used if no other storage
|
||||||
|
/// backend is provided, or if the provided storage backend is `None`.
|
||||||
|
///
|
||||||
|
/// The cache is limited to 10,000 entries and it is not shared between
|
||||||
|
/// instances nor persisted.
|
||||||
|
pub struct InMemoryHttpCache(pub Cache<HttpCacheKey, Vec<u8>>);
|
||||||
|
|
||||||
|
impl Default for InMemoryHttpCache {
|
||||||
|
fn default() -> Self {
|
||||||
|
InMemoryHttpCache(
|
||||||
|
Cache::builder()
|
||||||
|
.max_capacity(10_000)
|
||||||
|
.time_to_live(Duration::from_secs(DEFAULT_TTL_SECS))
|
||||||
|
.time_to_idle(Duration::from_secs(DEFAULT_TTI_SECS))
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl HttpCacheStorage for InMemoryHttpCache {
|
||||||
|
fn set_expiration_times(&mut self, cache_ttl: Duration, cache_tti: Duration) {
|
||||||
|
self.0 = Cache::builder()
|
||||||
|
.max_capacity(10_000)
|
||||||
|
.time_to_live(cache_ttl)
|
||||||
|
.time_to_idle(cache_tti)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get(&self, key: &HttpCacheKey) -> Option<Vec<u8>> {
|
||||||
|
self.0.get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set(&self, key: HttpCacheKey, value: Vec<u8>) {
|
||||||
|
self.0.insert(key, value).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
7
crates/cdk-axum/src/cache/backend/mod.rs
vendored
Normal file
7
crates/cdk-axum/src/cache/backend/mod.rs
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
mod memory;
|
||||||
|
#[cfg(feature = "redis")]
|
||||||
|
mod redis;
|
||||||
|
|
||||||
|
pub use self::memory::InMemoryHttpCache;
|
||||||
|
#[cfg(feature = "redis")]
|
||||||
|
pub use self::redis::{Config as RedisConfig, HttpCacheRedis};
|
||||||
96
crates/cdk-axum/src/cache/backend/redis.rs
vendored
Normal file
96
crates/cdk-axum/src/cache/backend/redis.rs
vendored
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use redis::AsyncCommands;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::cache::{HttpCacheKey, HttpCacheStorage};
|
||||||
|
|
||||||
|
/// Redis cache storage for the HTTP cache.
|
||||||
|
///
|
||||||
|
/// This cache storage backend uses Redis to store the cache.
|
||||||
|
pub struct HttpCacheRedis {
|
||||||
|
cache_ttl: Duration,
|
||||||
|
prefix: Option<Vec<u8>>,
|
||||||
|
client: redis::Client,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configuration for the Redis cache storage.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
pub struct Config {
|
||||||
|
/// Commong key prefix
|
||||||
|
pub key_prefix: Option<String>,
|
||||||
|
|
||||||
|
/// Connection string to the Redis server.
|
||||||
|
pub connection_string: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HttpCacheRedis {
|
||||||
|
/// Create a new Redis cache.
|
||||||
|
pub fn new(client: redis::Client) -> Self {
|
||||||
|
Self {
|
||||||
|
client,
|
||||||
|
prefix: None,
|
||||||
|
cache_ttl: Duration::from_secs(60),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a prefix for the cache keys.
|
||||||
|
///
|
||||||
|
/// This is useful to have all the HTTP cache keys under a common prefix,
|
||||||
|
/// some sort of namespace, to make management of the database easier.
|
||||||
|
pub fn set_prefix(mut self, prefix: Vec<u8>) -> Self {
|
||||||
|
self.prefix = Some(prefix);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl HttpCacheStorage for HttpCacheRedis {
|
||||||
|
fn set_expiration_times(&mut self, cache_ttl: Duration, _cache_tti: Duration) {
|
||||||
|
self.cache_ttl = cache_ttl;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get(&self, key: &HttpCacheKey) -> Option<Vec<u8>> {
|
||||||
|
let mut conn = self
|
||||||
|
.client
|
||||||
|
.get_multiplexed_tokio_connection()
|
||||||
|
.await
|
||||||
|
.map_err(|err| {
|
||||||
|
tracing::error!("Failed to get redis connection: {:?}", err);
|
||||||
|
err
|
||||||
|
})
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
|
let mut db_key = self.prefix.clone().unwrap_or_default();
|
||||||
|
db_key.extend(&**key);
|
||||||
|
|
||||||
|
conn.get(db_key)
|
||||||
|
.await
|
||||||
|
.map_err(|err| {
|
||||||
|
tracing::error!("Failed to get value from redis: {:?}", err);
|
||||||
|
err
|
||||||
|
})
|
||||||
|
.ok()?
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set(&self, key: HttpCacheKey, value: Vec<u8>) {
|
||||||
|
let mut db_key = self.prefix.clone().unwrap_or_default();
|
||||||
|
db_key.extend(&*key);
|
||||||
|
|
||||||
|
let mut conn = match self.client.get_multiplexed_tokio_connection().await {
|
||||||
|
Ok(conn) => conn,
|
||||||
|
Err(err) => {
|
||||||
|
tracing::error!("Failed to get redis connection: {:?}", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let _: Result<(), _> = conn
|
||||||
|
.set_ex(db_key, value, self.cache_ttl.as_secs() as usize)
|
||||||
|
.await
|
||||||
|
.map_err(|err| {
|
||||||
|
tracing::error!("Failed to set value in redis: {:?}", err);
|
||||||
|
err
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
102
crates/cdk-axum/src/cache/config.rs
vendored
Normal file
102
crates/cdk-axum/src/cache/config.rs
vendored
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
pub const ENV_CDK_MINTD_CACHE_BACKEND: &str = "CDK_MINTD_CACHE_BACKEND";
|
||||||
|
|
||||||
|
#[cfg(feature = "redis")]
|
||||||
|
pub const ENV_CDK_MINTD_CACHE_REDIS_URL: &str = "CDK_MINTD_CACHE_REDIS_URL";
|
||||||
|
#[cfg(feature = "redis")]
|
||||||
|
pub const ENV_CDK_MINTD_CACHE_REDIS_KEY_PREFIX: &str = "CDK_MINTD_CACHE_REDIS_KEY_PREFIX";
|
||||||
|
|
||||||
|
pub const ENV_CDK_MINTD_CACHE_TTI: &str = "CDK_MINTD_CACHE_TTI";
|
||||||
|
pub const ENV_CDK_MINTD_CACHE_TTL: &str = "CDK_MINTD_CACHE_TTL";
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
#[serde(tag = "backend")]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum Backend {
|
||||||
|
#[default]
|
||||||
|
Memory,
|
||||||
|
#[cfg(feature = "redis")]
|
||||||
|
Redis(super::backend::RedisConfig),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Backend {
|
||||||
|
pub fn from_env_str(backend_str: &str) -> Option<Self> {
|
||||||
|
match backend_str.to_lowercase().as_str() {
|
||||||
|
"memory" => Some(Self::Memory),
|
||||||
|
#[cfg(feature = "redis")]
|
||||||
|
"redis" => {
|
||||||
|
// Get Redis configuration from environment
|
||||||
|
let connection_string = std::env::var(ENV_CDK_MINTD_CACHE_REDIS_URL)
|
||||||
|
.unwrap_or_else(|_| "redis://127.0.0.1:6379".to_string());
|
||||||
|
|
||||||
|
let key_prefix = std::env::var(ENV_CDK_MINTD_CACHE_REDIS_KEY_PREFIX).ok();
|
||||||
|
|
||||||
|
Some(Self::Redis(super::backend::RedisConfig {
|
||||||
|
connection_string,
|
||||||
|
key_prefix,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cache configuration.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
pub struct Config {
|
||||||
|
/// Cache backend.
|
||||||
|
#[serde(default)]
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub backend: Backend,
|
||||||
|
|
||||||
|
/// Time to live for the cache entries.
|
||||||
|
pub ttl: Option<u64>,
|
||||||
|
|
||||||
|
/// Time for the cache entries to be idle.
|
||||||
|
pub tti: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
/// Config from env
|
||||||
|
pub fn from_env(mut self) -> Self {
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
// Parse backend
|
||||||
|
if let Ok(backend_str) = env::var(ENV_CDK_MINTD_CACHE_BACKEND) {
|
||||||
|
if let Some(backend) = Backend::from_env_str(&backend_str) {
|
||||||
|
self.backend = backend;
|
||||||
|
|
||||||
|
// If Redis backend is selected, parse Redis configuration
|
||||||
|
#[cfg(feature = "redis")]
|
||||||
|
if matches!(self.backend, Backend::Redis(_)) {
|
||||||
|
let connection_string = env::var(ENV_CDK_MINTD_CACHE_REDIS_URL)
|
||||||
|
.unwrap_or_else(|_| "redis://127.0.0.1:6379".to_string());
|
||||||
|
|
||||||
|
let key_prefix = env::var(ENV_CDK_MINTD_CACHE_REDIS_KEY_PREFIX).ok();
|
||||||
|
|
||||||
|
self.backend = Backend::Redis(super::backend::RedisConfig {
|
||||||
|
connection_string,
|
||||||
|
key_prefix,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse TTL
|
||||||
|
if let Ok(ttl_str) = env::var(ENV_CDK_MINTD_CACHE_TTL) {
|
||||||
|
if let Ok(ttl) = ttl_str.parse() {
|
||||||
|
self.ttl = Some(ttl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse TTI
|
||||||
|
if let Ok(tti_str) = env::var(ENV_CDK_MINTD_CACHE_TTI) {
|
||||||
|
if let Ok(tti) = tti_str.parse() {
|
||||||
|
self.tti = Some(tti);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
188
crates/cdk-axum/src/cache/mod.rs
vendored
Normal file
188
crates/cdk-axum/src/cache/mod.rs
vendored
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
//! HTTP cache.
|
||||||
|
//!
|
||||||
|
//! This is mod defines a common trait to define custom backends for the HTTP cache.
|
||||||
|
//!
|
||||||
|
//! The HTTP cache is a layer to cache responses from HTTP requests, to avoid hitting
|
||||||
|
//! the same endpoint multiple times, which can be expensive and slow, or to provide
|
||||||
|
//! idempotent operations.
|
||||||
|
//!
|
||||||
|
//! This mod also provides common backend implementations as well, such as In
|
||||||
|
//! Memory (default) and Redis.
|
||||||
|
use std::ops::Deref;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
use serde::Serialize;
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
|
mod backend;
|
||||||
|
mod config;
|
||||||
|
|
||||||
|
pub use self::backend::*;
|
||||||
|
pub use self::config::Config;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
/// Cache storage for the HTTP cache.
|
||||||
|
pub trait HttpCacheStorage {
|
||||||
|
/// Sets the expiration times for the cache.
|
||||||
|
fn set_expiration_times(&mut self, cache_ttl: Duration, cache_tti: Duration);
|
||||||
|
|
||||||
|
/// Get a value from the cache.
|
||||||
|
async fn get(&self, key: &HttpCacheKey) -> Option<Vec<u8>>;
|
||||||
|
|
||||||
|
/// Set a value in the cache.
|
||||||
|
async fn set(&self, key: HttpCacheKey, value: Vec<u8>);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Http cache with a pluggable storage backend.
|
||||||
|
pub struct HttpCache {
|
||||||
|
/// Time to live for the cache.
|
||||||
|
pub ttl: Duration,
|
||||||
|
/// Time to idle for the cache.
|
||||||
|
pub tti: Duration,
|
||||||
|
/// Storage backend for the cache.
|
||||||
|
storage: Arc<Box<dyn HttpCacheStorage + Send + Sync>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for HttpCache {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new(
|
||||||
|
Duration::from_secs(DEFAULT_TTL_SECS),
|
||||||
|
Duration::from_secs(DEFAULT_TTI_SECS),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Max payload size for the cache key.
|
||||||
|
///
|
||||||
|
/// This is a trade-off between security and performance. A large payload can be used to
|
||||||
|
/// perform a CPU attack.
|
||||||
|
const MAX_PAYLOAD_SIZE: usize = 10 * 1024 * 1024;
|
||||||
|
|
||||||
|
/// Default TTL for the cache.
|
||||||
|
const DEFAULT_TTL_SECS: u64 = 60;
|
||||||
|
|
||||||
|
/// Default TTI for the cache.
|
||||||
|
const DEFAULT_TTI_SECS: u64 = 60;
|
||||||
|
|
||||||
|
/// Http cache key.
|
||||||
|
///
|
||||||
|
/// This type ensures no Vec<u8> is used as a key, which is error-prone.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct HttpCacheKey([u8; 32]);
|
||||||
|
|
||||||
|
impl Deref for HttpCacheKey {
|
||||||
|
type Target = [u8; 32];
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<config::Config> for HttpCache {
|
||||||
|
fn from(config: config::Config) -> Self {
|
||||||
|
match config.backend {
|
||||||
|
config::Backend::Memory => Self::new(
|
||||||
|
Duration::from_secs(config.ttl.unwrap_or(DEFAULT_TTL_SECS)),
|
||||||
|
Duration::from_secs(config.tti.unwrap_or(DEFAULT_TTI_SECS)),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
#[cfg(feature = "redis")]
|
||||||
|
config::Backend::Redis(redis_config) => {
|
||||||
|
let client = redis::Client::open(redis_config.connection_string)
|
||||||
|
.expect("Failed to create Redis client");
|
||||||
|
let storage = HttpCacheRedis::new(client).set_prefix(
|
||||||
|
redis_config
|
||||||
|
.key_prefix
|
||||||
|
.unwrap_or_default()
|
||||||
|
.as_bytes()
|
||||||
|
.to_vec(),
|
||||||
|
);
|
||||||
|
Self::new(
|
||||||
|
Duration::from_secs(config.ttl.unwrap_or(DEFAULT_TTL_SECS)),
|
||||||
|
Duration::from_secs(config.tti.unwrap_or(DEFAULT_TTI_SECS)),
|
||||||
|
Some(Box::new(storage)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HttpCache {
|
||||||
|
/// Create a new HTTP cache.
|
||||||
|
pub fn new(
|
||||||
|
ttl: Duration,
|
||||||
|
tti: Duration,
|
||||||
|
storage: Option<Box<dyn HttpCacheStorage + Send + Sync + 'static>>,
|
||||||
|
) -> Self {
|
||||||
|
let mut storage = storage.unwrap_or_else(|| Box::new(InMemoryHttpCache::default()));
|
||||||
|
storage.set_expiration_times(ttl, tti);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
ttl,
|
||||||
|
tti,
|
||||||
|
storage: Arc::new(storage),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate a cache key from a serializable value.
|
||||||
|
///
|
||||||
|
/// Usually the input is the request body or query parameters.
|
||||||
|
///
|
||||||
|
/// The result is an optional cache key. If the key cannot be calculated, it
|
||||||
|
/// will be None, meaning the value cannot be cached, therefore the entire
|
||||||
|
/// caching mechanism should be skipped.
|
||||||
|
///
|
||||||
|
/// Instead of using the entire serialized input as the key, the key is a
|
||||||
|
/// double hash to have a predictable key size, although it may open the
|
||||||
|
/// window for CPU attacks with large payloads, but it is a trade-off.
|
||||||
|
/// Perhaps upper layer have a protection against large payloads.
|
||||||
|
pub fn calculate_key<K>(&self, key: &K) -> Option<HttpCacheKey>
|
||||||
|
where
|
||||||
|
K: Serialize,
|
||||||
|
{
|
||||||
|
let json_value = match serde_json::to_vec(key) {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(err) => {
|
||||||
|
tracing::warn!("Failed to serialize key: {:?}", err);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if json_value.len() > MAX_PAYLOAD_SIZE {
|
||||||
|
tracing::warn!("Key size is too large: {}", json_value.len());
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let first_hash = Sha256::digest(json_value);
|
||||||
|
let second_hash = Sha256::digest(first_hash);
|
||||||
|
Some(HttpCacheKey(second_hash.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a value from the cache.
|
||||||
|
pub async fn get<V>(self: &Arc<Self>, key: &HttpCacheKey) -> Option<V>
|
||||||
|
where
|
||||||
|
V: DeserializeOwned,
|
||||||
|
{
|
||||||
|
self.storage.get(key).await.and_then(|value| {
|
||||||
|
serde_json::from_slice(&value)
|
||||||
|
.map_err(|e| {
|
||||||
|
tracing::warn!("Failed to deserialize value: {:?}", e);
|
||||||
|
e
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a value in the cache.
|
||||||
|
pub async fn set<V: Serialize>(self: &Arc<Self>, key: HttpCacheKey, value: &V) {
|
||||||
|
if let Ok(bytes) = serde_json::to_vec(value).map_err(|e| {
|
||||||
|
tracing::warn!("Failed to serialize value: {:?}", e);
|
||||||
|
e
|
||||||
|
}) {
|
||||||
|
self.storage.set(key, bytes).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,15 +4,15 @@
|
|||||||
#![warn(rustdoc::bare_urls)]
|
#![warn(rustdoc::bare_urls)]
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use axum::routing::{get, post};
|
use axum::routing::{get, post};
|
||||||
use axum::Router;
|
use axum::Router;
|
||||||
|
use cache::HttpCache;
|
||||||
use cdk::mint::Mint;
|
use cdk::mint::Mint;
|
||||||
use moka::future::Cache;
|
|
||||||
use router_handlers::*;
|
use router_handlers::*;
|
||||||
|
|
||||||
|
pub mod cache;
|
||||||
mod router_handlers;
|
mod router_handlers;
|
||||||
mod ws;
|
mod ws;
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ use uuid::Uuid;
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct MintState {
|
pub struct MintState {
|
||||||
mint: Arc<Mint>,
|
mint: Arc<Mint>,
|
||||||
cache: Cache<String, String>,
|
cache: Arc<cache::HttpCache>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "swagger")]
|
#[cfg(feature = "swagger")]
|
||||||
@@ -131,15 +131,20 @@ pub struct MintState {
|
|||||||
/// OpenAPI spec for the mint's v1 APIs
|
/// OpenAPI spec for the mint's v1 APIs
|
||||||
pub struct ApiDocV1;
|
pub struct ApiDocV1;
|
||||||
|
|
||||||
/// Create mint [`Router`] with required endpoints for cashu mint
|
/// Create mint [`Router`] with required endpoints for cashu mint with the default cache
|
||||||
pub async fn create_mint_router(mint: Arc<Mint>, cache_ttl: u64, cache_tti: u64) -> Result<Router> {
|
pub async fn create_mint_router(mint: Arc<Mint>) -> Result<Router> {
|
||||||
|
create_mint_router_with_custom_cache(mint, Default::default()).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create mint [`Router`] with required endpoints for cashu mint with a custom
|
||||||
|
/// backend for cache
|
||||||
|
pub async fn create_mint_router_with_custom_cache(
|
||||||
|
mint: Arc<Mint>,
|
||||||
|
cache: HttpCache,
|
||||||
|
) -> Result<Router> {
|
||||||
let state = MintState {
|
let state = MintState {
|
||||||
mint,
|
mint,
|
||||||
cache: Cache::builder()
|
cache: Arc::new(cache),
|
||||||
.max_capacity(10_000)
|
|
||||||
.time_to_live(Duration::from_secs(cache_ttl))
|
|
||||||
.time_to_idle(Duration::from_secs(cache_tti))
|
|
||||||
.build(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let v1_router = Router::new()
|
let v1_router = Router::new()
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ use cdk::nuts::{
|
|||||||
SwapRequest, SwapResponse,
|
SwapRequest, SwapResponse,
|
||||||
};
|
};
|
||||||
use cdk::util::unix_time;
|
use cdk::util::unix_time;
|
||||||
use cdk::Error;
|
|
||||||
use paste::paste;
|
use paste::paste;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
@@ -31,19 +30,20 @@ macro_rules! post_cache_wrapper {
|
|||||||
|
|
||||||
let json_extracted_payload = payload.deref();
|
let json_extracted_payload = payload.deref();
|
||||||
let State(mint_state) = state.clone();
|
let State(mint_state) = state.clone();
|
||||||
let cache_key = serde_json::to_string(&json_extracted_payload).map_err(|err| {
|
let cache_key = match mint_state.cache.calculate_key(&json_extracted_payload) {
|
||||||
into_response(Error::from(err))
|
Some(key) => key,
|
||||||
})?;
|
None => {
|
||||||
|
// Could not calculate key, just return the handler result
|
||||||
|
return $handler(state, payload).await;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(cached_response) = mint_state.cache.get(&cache_key) {
|
if let Some(cached_response) = mint_state.cache.get::<$response_type>(&cache_key).await {
|
||||||
return Ok(Json(serde_json::from_str(&cached_response)
|
return Ok(Json(cached_response));
|
||||||
.expect("Shouldn't panic: response is json-deserializable.")));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let response = $handler(state, payload).await?;
|
let response = $handler(state, payload).await?;
|
||||||
mint_state.cache.insert(cache_key, serde_json::to_string(response.deref())
|
mint_state.cache.set(cache_key, &response.deref()).await;
|
||||||
.expect("Shouldn't panic: response is json-serializable.")
|
|
||||||
).await;
|
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,11 +50,9 @@ where
|
|||||||
);
|
);
|
||||||
|
|
||||||
let mint = create_mint(database, ln_backends.clone()).await?;
|
let mint = create_mint(database, ln_backends.clone()).await?;
|
||||||
let cache_ttl = 3600;
|
|
||||||
let cache_tti = 3600;
|
|
||||||
let mint_arc = Arc::new(mint);
|
let mint_arc = Arc::new(mint);
|
||||||
|
|
||||||
let v1_service = cdk_axum::create_mint_router(Arc::clone(&mint_arc), cache_ttl, cache_tti)
|
let v1_service = cdk_axum::create_mint_router(Arc::clone(&mint_arc))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -221,17 +221,11 @@ where
|
|||||||
);
|
);
|
||||||
|
|
||||||
let mint = create_mint(database, ln_backends.clone()).await?;
|
let mint = create_mint(database, ln_backends.clone()).await?;
|
||||||
let cache_time_to_live = 3600;
|
|
||||||
let cache_time_to_idle = 3600;
|
|
||||||
let mint_arc = Arc::new(mint);
|
let mint_arc = Arc::new(mint);
|
||||||
|
|
||||||
let v1_service = cdk_axum::create_mint_router(
|
let v1_service = cdk_axum::create_mint_router(Arc::clone(&mint_arc))
|
||||||
Arc::clone(&mint_arc),
|
.await
|
||||||
cache_time_to_live,
|
.unwrap();
|
||||||
cache_time_to_idle,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mint_service = Router::new()
|
let mint_service = Router::new()
|
||||||
.merge(v1_service)
|
.merge(v1_service)
|
||||||
|
|||||||
@@ -86,17 +86,10 @@ pub async fn start_mint(
|
|||||||
HashMap::new(),
|
HashMap::new(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let cache_time_to_live = 3600;
|
|
||||||
let cache_time_to_idle = 3600;
|
|
||||||
|
|
||||||
let mint_arc = Arc::new(mint);
|
let mint_arc = Arc::new(mint);
|
||||||
|
|
||||||
let v1_service = cdk_axum::create_mint_router(
|
let v1_service = cdk_axum::create_mint_router(Arc::clone(&mint_arc)).await?;
|
||||||
Arc::clone(&mint_arc),
|
|
||||||
cache_time_to_live,
|
|
||||||
cache_time_to_idle,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mint_service = Router::new()
|
let mint_service = Router::new()
|
||||||
.merge(v1_service)
|
.merge(v1_service)
|
||||||
|
|||||||
@@ -6,15 +6,21 @@ authors = ["CDK Developers"]
|
|||||||
license = "MIT"
|
license = "MIT"
|
||||||
homepage = "https://github.com/cashubtc/cdk"
|
homepage = "https://github.com/cashubtc/cdk"
|
||||||
repository = "https://github.com/cashubtc/cdk.git"
|
repository = "https://github.com/cashubtc/cdk.git"
|
||||||
rust-version = "1.63.0" # MSRV
|
rust-version = "1.63.0" # MSRV
|
||||||
description = "CDK mint binary"
|
description = "CDK mint binary"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
axum = "0.6.20"
|
axum = "0.6.20"
|
||||||
cdk = { path = "../cdk", version = "0.5.0", default-features = false, features = ["mint"] }
|
cdk = { path = "../cdk", version = "0.5.0", default-features = false, features = [
|
||||||
cdk-redb = { path = "../cdk-redb", version = "0.5.0", default-features = false, features = ["mint"] }
|
"mint",
|
||||||
cdk-sqlite = { path = "../cdk-sqlite", version = "0.5.0", default-features = false, features = ["mint"] }
|
] }
|
||||||
|
cdk-redb = { path = "../cdk-redb", version = "0.5.0", default-features = false, features = [
|
||||||
|
"mint",
|
||||||
|
] }
|
||||||
|
cdk-sqlite = { path = "../cdk-sqlite", version = "0.5.0", default-features = false, features = [
|
||||||
|
"mint",
|
||||||
|
] }
|
||||||
cdk-cln = { path = "../cdk-cln", version = "0.5.0", default-features = false }
|
cdk-cln = { path = "../cdk-cln", version = "0.5.0", default-features = false }
|
||||||
cdk-lnbits = { path = "../cdk-lnbits", version = "0.5.0", default-features = false }
|
cdk-lnbits = { path = "../cdk-lnbits", version = "0.5.0", default-features = false }
|
||||||
cdk-phoenixd = { path = "../cdk-phoenixd", version = "0.5.0", default-features = false }
|
cdk-phoenixd = { path = "../cdk-phoenixd", version = "0.5.0", default-features = false }
|
||||||
@@ -25,7 +31,10 @@ cdk-axum = { path = "../cdk-axum", version = "0.5.0", default-features = false }
|
|||||||
config = { version = "0.13.3", features = ["toml"] }
|
config = { version = "0.13.3", features = ["toml"] }
|
||||||
clap = { version = "4.4.8", features = ["derive", "env", "default"] }
|
clap = { version = "4.4.8", features = ["derive", "env", "default"] }
|
||||||
tokio = { version = "1", default-features = false }
|
tokio = { version = "1", default-features = false }
|
||||||
tracing = { version = "0.1", default-features = false, features = ["attributes", "log"] }
|
tracing = { version = "0.1", default-features = false, features = [
|
||||||
|
"attributes",
|
||||||
|
"log",
|
||||||
|
] }
|
||||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||||
futures = { version = "0.3.28", default-features = false }
|
futures = { version = "0.3.28", default-features = false }
|
||||||
serde = { version = "1", default-features = false, features = ["derive"] }
|
serde = { version = "1", default-features = false, features = ["derive"] }
|
||||||
@@ -40,3 +49,4 @@ rand = "0.8.5"
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
swagger = ["cdk-axum/swagger", "dep:utoipa", "dep:utoipa-swagger-ui"]
|
swagger = ["cdk-axum/swagger", "dep:utoipa", "dep:utoipa-swagger-ui"]
|
||||||
|
redis = ["cdk-axum/redis"]
|
||||||
|
|||||||
@@ -6,6 +6,14 @@ mnemonic = ""
|
|||||||
# input_fee_ppk = 0
|
# input_fee_ppk = 0
|
||||||
# enable_swagger_ui = false
|
# enable_swagger_ui = false
|
||||||
|
|
||||||
|
[info.http_cache]
|
||||||
|
# memory or redis
|
||||||
|
backend = "memory"
|
||||||
|
ttl = 60
|
||||||
|
tti = 60
|
||||||
|
# `key_prefix` and `connection_string` required for redis
|
||||||
|
# key_prefix = "mintd"
|
||||||
|
# connection_string = "redis://localhost"
|
||||||
|
|
||||||
|
|
||||||
[mint_info]
|
[mint_info]
|
||||||
@@ -32,9 +40,12 @@ ln_backend = "cln"
|
|||||||
# fee_percent=0.04
|
# fee_percent=0.04
|
||||||
# reserve_fee_min=4
|
# reserve_fee_min=4
|
||||||
|
|
||||||
# [cln]
|
[cln]
|
||||||
# Required if using cln backend path to rpc
|
#Required if using cln backend path to rpc
|
||||||
# cln_path = ""
|
cln_path = ""
|
||||||
|
rpc_path = ""
|
||||||
|
fee_percent = 0.02
|
||||||
|
reserve_fee_min = 1
|
||||||
|
|
||||||
# [strike]
|
# [strike]
|
||||||
# For the Webhook subscription, the url under [info] must be a valid, absolute, non-local, https url
|
# For the Webhook subscription, the url under [info] must be a valid, absolute, non-local, https url
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use std::path::PathBuf;
|
|||||||
|
|
||||||
use cdk::nuts::{CurrencyUnit, PublicKey};
|
use cdk::nuts::{CurrencyUnit, PublicKey};
|
||||||
use cdk::Amount;
|
use cdk::Amount;
|
||||||
|
use cdk_axum::cache;
|
||||||
use config::{Config, ConfigError, File};
|
use config::{Config, ConfigError, File};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@@ -11,11 +12,10 @@ pub struct Info {
|
|||||||
pub listen_host: String,
|
pub listen_host: String,
|
||||||
pub listen_port: u16,
|
pub listen_port: u16,
|
||||||
pub mnemonic: String,
|
pub mnemonic: String,
|
||||||
pub seconds_quote_is_valid_for: Option<u64>,
|
|
||||||
pub seconds_to_cache_requests_for: Option<u64>,
|
|
||||||
pub seconds_to_extend_cache_by: Option<u64>,
|
|
||||||
pub input_fee_ppk: Option<u64>,
|
pub input_fee_ppk: Option<u64>,
|
||||||
|
|
||||||
|
pub http_cache: cache::Config,
|
||||||
|
|
||||||
/// When this is set to true, the mint exposes a Swagger UI for it's API at
|
/// When this is set to true, the mint exposes a Swagger UI for it's API at
|
||||||
/// `[listen_host]:[listen_port]/swagger-ui`
|
/// `[listen_host]:[listen_port]/swagger-ui`
|
||||||
///
|
///
|
||||||
@@ -93,6 +93,7 @@ pub struct LNbits {
|
|||||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
pub struct Cln {
|
pub struct Cln {
|
||||||
pub rpc_path: PathBuf,
|
pub rpc_path: PathBuf,
|
||||||
|
#[serde(default)]
|
||||||
pub bolt12: bool,
|
pub bolt12: bool,
|
||||||
pub fee_percent: f32,
|
pub fee_percent: f32,
|
||||||
pub reserve_fee_min: Amount,
|
pub reserve_fee_min: Amount,
|
||||||
@@ -210,7 +211,10 @@ pub struct MintInfo {
|
|||||||
|
|
||||||
impl Settings {
|
impl Settings {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(config_file_name: &Option<PathBuf>) -> Self {
|
pub fn new<P>(config_file_name: Option<P>) -> Self
|
||||||
|
where
|
||||||
|
P: Into<PathBuf>,
|
||||||
|
{
|
||||||
let default_settings = Self::default();
|
let default_settings = Self::default();
|
||||||
// attempt to construct settings with file
|
// attempt to construct settings with file
|
||||||
let from_file = Self::new_from_default(&default_settings, config_file_name);
|
let from_file = Self::new_from_default(&default_settings, config_file_name);
|
||||||
@@ -223,17 +227,20 @@ impl Settings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_from_default(
|
fn new_from_default<P>(
|
||||||
default: &Settings,
|
default: &Settings,
|
||||||
config_file_name: &Option<PathBuf>,
|
config_file_name: Option<P>,
|
||||||
) -> Result<Self, ConfigError> {
|
) -> Result<Self, ConfigError>
|
||||||
|
where
|
||||||
|
P: Into<PathBuf>,
|
||||||
|
{
|
||||||
let mut default_config_file_name = home::home_dir()
|
let mut default_config_file_name = home::home_dir()
|
||||||
.ok_or(ConfigError::NotFound("Config Path".to_string()))?
|
.ok_or(ConfigError::NotFound("Config Path".to_string()))?
|
||||||
.join("cashu-rs-mint");
|
.join("cashu-rs-mint");
|
||||||
|
|
||||||
default_config_file_name.push("config.toml");
|
default_config_file_name.push("config.toml");
|
||||||
let config: String = match config_file_name {
|
let config: String = match config_file_name {
|
||||||
Some(value) => value.clone().to_string_lossy().to_string(),
|
Some(value) => value.into().to_string_lossy().to_string(),
|
||||||
None => default_config_file_name.to_string_lossy().to_string(),
|
None => default_config_file_name.to_string_lossy().to_string(),
|
||||||
};
|
};
|
||||||
let builder = Config::builder();
|
let builder = Config::builder();
|
||||||
|
|||||||
@@ -127,22 +127,15 @@ impl Info {
|
|||||||
self.mnemonic = mnemonic;
|
self.mnemonic = mnemonic;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optional fields
|
|
||||||
if let Ok(seconds_str) = env::var(ENV_SECONDS_QUOTE_VALID) {
|
|
||||||
if let Ok(seconds) = seconds_str.parse() {
|
|
||||||
self.seconds_quote_is_valid_for = Some(seconds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(cache_seconds_str) = env::var(ENV_CACHE_SECONDS) {
|
if let Ok(cache_seconds_str) = env::var(ENV_CACHE_SECONDS) {
|
||||||
if let Ok(seconds) = cache_seconds_str.parse() {
|
if let Ok(seconds) = cache_seconds_str.parse() {
|
||||||
self.seconds_to_cache_requests_for = Some(seconds);
|
self.http_cache.ttl = Some(seconds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(extend_cache_str) = env::var(ENV_EXTEND_CACHE_SECONDS) {
|
if let Ok(extend_cache_str) = env::var(ENV_EXTEND_CACHE_SECONDS) {
|
||||||
if let Ok(seconds) = extend_cache_str.parse() {
|
if let Ok(seconds) = extend_cache_str.parse() {
|
||||||
self.seconds_to_extend_cache_by = Some(seconds);
|
self.http_cache.tti = Some(seconds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,6 +151,8 @@ impl Info {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.http_cache = self.http_cache.from_env();
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,3 +21,22 @@ fn expand_path(path: &str) -> Option<PathBuf> {
|
|||||||
Some(PathBuf::from(path))
|
Some(PathBuf::from(path))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use std::env::current_dir;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn example_is_parsed() {
|
||||||
|
let config = config::Settings::new(Some(format!(
|
||||||
|
"{}/example.config.toml",
|
||||||
|
current_dir().expect("cwd").to_string_lossy()
|
||||||
|
)));
|
||||||
|
let cache = config.info.http_cache;
|
||||||
|
|
||||||
|
assert_eq!(cache.ttl, Some(60));
|
||||||
|
assert_eq!(cache.tti, Some(60));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ use cdk::nuts::nut17::SupportedMethods;
|
|||||||
use cdk::nuts::nut19::{CachedEndpoint, Method as NUT19Method, Path as NUT19Path};
|
use cdk::nuts::nut19::{CachedEndpoint, Method as NUT19Method, Path as NUT19Path};
|
||||||
use cdk::nuts::{ContactInfo, CurrencyUnit, MintVersion, PaymentMethod};
|
use cdk::nuts::{ContactInfo, CurrencyUnit, MintVersion, PaymentMethod};
|
||||||
use cdk::types::LnKey;
|
use cdk::types::LnKey;
|
||||||
|
use cdk_axum::cache::HttpCache;
|
||||||
use cdk_mintd::cli::CLIArgs;
|
use cdk_mintd::cli::CLIArgs;
|
||||||
use cdk_mintd::config::{self, DatabaseEngine, LnBackend};
|
use cdk_mintd::config::{self, DatabaseEngine, LnBackend};
|
||||||
use cdk_mintd::setup::LnBackendSetup;
|
use cdk_mintd::setup::LnBackendSetup;
|
||||||
@@ -36,9 +37,6 @@ use tracing_subscriber::EnvFilter;
|
|||||||
use utoipa::OpenApi;
|
use utoipa::OpenApi;
|
||||||
|
|
||||||
const CARGO_PKG_VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION");
|
const CARGO_PKG_VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION");
|
||||||
const DEFAULT_QUOTE_TTL_SECS: u64 = 1800;
|
|
||||||
const DEFAULT_CACHE_TTL_SECS: u64 = 1800;
|
|
||||||
const DEFAULT_CACHE_TTI_SECS: u64 = 1800;
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
@@ -72,7 +70,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
let mut mint_builder = MintBuilder::new();
|
let mut mint_builder = MintBuilder::new();
|
||||||
|
|
||||||
let mut settings = if config_file_arg.exists() {
|
let mut settings = if config_file_arg.exists() {
|
||||||
config::Settings::new(&Some(config_file_arg))
|
config::Settings::new(Some(config_file_arg))
|
||||||
} else {
|
} else {
|
||||||
tracing::info!("Config file does not exist. Attempting to read env vars");
|
tracing::info!("Config file does not exist. Attempting to read env vars");
|
||||||
config::Settings::default()
|
config::Settings::default()
|
||||||
@@ -302,18 +300,15 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
.with_quote_ttl(10000, 10000)
|
.with_quote_ttl(10000, 10000)
|
||||||
.with_seed(mnemonic.to_seed_normalized("").to_vec());
|
.with_seed(mnemonic.to_seed_normalized("").to_vec());
|
||||||
|
|
||||||
let cache_ttl = settings
|
|
||||||
.info
|
|
||||||
.seconds_to_cache_requests_for
|
|
||||||
.unwrap_or(DEFAULT_CACHE_TTL_SECS);
|
|
||||||
|
|
||||||
let cached_endpoints = vec![
|
let cached_endpoints = vec![
|
||||||
CachedEndpoint::new(NUT19Method::Post, NUT19Path::MintBolt11),
|
CachedEndpoint::new(NUT19Method::Post, NUT19Path::MintBolt11),
|
||||||
CachedEndpoint::new(NUT19Method::Post, NUT19Path::MeltBolt11),
|
CachedEndpoint::new(NUT19Method::Post, NUT19Path::MeltBolt11),
|
||||||
CachedEndpoint::new(NUT19Method::Post, NUT19Path::Swap),
|
CachedEndpoint::new(NUT19Method::Post, NUT19Path::Swap),
|
||||||
];
|
];
|
||||||
|
|
||||||
mint_builder = mint_builder.add_cache(Some(cache_ttl), cached_endpoints);
|
let cache: HttpCache = settings.info.http_cache.into();
|
||||||
|
|
||||||
|
mint_builder = mint_builder.add_cache(Some(cache.ttl.as_secs()), cached_endpoints);
|
||||||
|
|
||||||
let mint = mint_builder.build().await?;
|
let mint = mint_builder.build().await?;
|
||||||
|
|
||||||
@@ -332,16 +327,9 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
let listen_addr = settings.info.listen_host;
|
let listen_addr = settings.info.listen_host;
|
||||||
let listen_port = settings.info.listen_port;
|
let listen_port = settings.info.listen_port;
|
||||||
let _quote_ttl = settings
|
|
||||||
.info
|
|
||||||
.seconds_quote_is_valid_for
|
|
||||||
.unwrap_or(DEFAULT_QUOTE_TTL_SECS);
|
|
||||||
let cache_tti = settings
|
|
||||||
.info
|
|
||||||
.seconds_to_extend_cache_by
|
|
||||||
.unwrap_or(DEFAULT_CACHE_TTI_SECS);
|
|
||||||
|
|
||||||
let v1_service = cdk_axum::create_mint_router(Arc::clone(&mint), cache_ttl, cache_tti).await?;
|
let v1_service =
|
||||||
|
cdk_axum::create_mint_router_with_custom_cache(Arc::clone(&mint), cache).await?;
|
||||||
|
|
||||||
let mut mint_service = Router::new()
|
let mut mint_service = Router::new()
|
||||||
.merge(v1_service)
|
.merge(v1_service)
|
||||||
|
|||||||
@@ -13,4 +13,21 @@ services:
|
|||||||
- CDK_MINTD_LISTEN_PORT=8085
|
- CDK_MINTD_LISTEN_PORT=8085
|
||||||
- CDK_MINTD_MNEMONIC=
|
- CDK_MINTD_MNEMONIC=
|
||||||
- CDK_MINTD_DATABASE=redb
|
- CDK_MINTD_DATABASE=redb
|
||||||
|
- CDK_MINTD_CACHE_BACKEND=memory
|
||||||
|
# - CDK_MINTD_CACHE_REDIS_URL=redis://redis:6379
|
||||||
|
# - CDK_MINTD_CACHE_REDIS_KEY_PREFIX=cdk-mintd
|
||||||
command: ["cdk-mintd"]
|
command: ["cdk-mintd"]
|
||||||
|
# depends_on:
|
||||||
|
# - redis
|
||||||
|
|
||||||
|
# redis:
|
||||||
|
# image: redis:7-alpine
|
||||||
|
# container_name: mint_redis
|
||||||
|
# ports:
|
||||||
|
# - "6379:6379"
|
||||||
|
# volumes:
|
||||||
|
# - redis_data:/data
|
||||||
|
# command: redis-server --save 60 1 --loglevel warning
|
||||||
|
|
||||||
|
# volumes:
|
||||||
|
# redis_data:
|
||||||
|
|||||||
Reference in New Issue
Block a user