mirror of
https://github.com/aljazceru/cdk.git
synced 2025-12-24 08:05:02 +01:00
Cache SwapResponse, MeltBolt11Response and MintBolt11Response (#361)
* added cache to mint state and post request wrapper macro. --------- Co-authored-by: thesimplekid <tsk@thesimplekid.com>
This commit is contained in:
@@ -21,8 +21,6 @@ repository = "https://github.com/cashubtc/cdk"
|
||||
license-file = "LICENSE"
|
||||
keywords = ["bitcoin", "e-cash", "cashu"]
|
||||
|
||||
[profile]
|
||||
|
||||
[profile.ci]
|
||||
inherits = "dev"
|
||||
incremental = false
|
||||
|
||||
@@ -16,3 +16,6 @@ cdk = { path = "../cdk", version = "0.4.0", default-features = false, features =
|
||||
tokio = { version = "1", default-features = false }
|
||||
tracing = { version = "0.1", default-features = false, features = ["attributes", "log"] }
|
||||
futures = { version = "0.3.28", default-features = false }
|
||||
moka = { version = "0.11.1", features = ["future"] }
|
||||
serde_json = "1"
|
||||
paste = "1.0.15"
|
||||
|
||||
@@ -9,31 +9,47 @@ use anyhow::Result;
|
||||
use axum::routing::{get, post};
|
||||
use axum::Router;
|
||||
use cdk::mint::Mint;
|
||||
use moka::future::Cache;
|
||||
use router_handlers::*;
|
||||
use std::time::Duration;
|
||||
|
||||
mod router_handlers;
|
||||
|
||||
/// CDK Mint State
|
||||
#[derive(Clone)]
|
||||
pub struct MintState {
|
||||
mint: Arc<Mint>,
|
||||
cache: Cache<String, String>,
|
||||
}
|
||||
|
||||
/// Create mint [`Router`] with required endpoints for cashu mint
|
||||
pub async fn create_mint_router(mint: Arc<Mint>) -> Result<Router> {
|
||||
let state = MintState { mint };
|
||||
pub async fn create_mint_router(mint: Arc<Mint>, cache_ttl: u64, cache_tti: u64) -> Result<Router> {
|
||||
let state = MintState {
|
||||
mint,
|
||||
cache: Cache::builder()
|
||||
.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()
|
||||
.route("/keys", get(get_keys))
|
||||
.route("/keysets", get(get_keysets))
|
||||
.route("/keys/:keyset_id", get(get_keyset_pubkeys))
|
||||
.route("/swap", post(post_swap))
|
||||
.route("/swap", post(cache_post_swap))
|
||||
.route("/mint/quote/bolt11", post(get_mint_bolt11_quote))
|
||||
.route(
|
||||
"/mint/quote/bolt11/:quote_id",
|
||||
get(get_check_mint_bolt11_quote),
|
||||
)
|
||||
.route("/mint/bolt11", post(post_mint_bolt11))
|
||||
.route("/mint/bolt11", post(cache_post_mint_bolt11))
|
||||
.route("/melt/quote/bolt11", post(get_melt_bolt11_quote))
|
||||
.route(
|
||||
"/melt/quote/bolt11/:quote_id",
|
||||
get(get_check_melt_bolt11_quote),
|
||||
)
|
||||
.route("/melt/bolt11", post(post_melt_bolt11))
|
||||
.route("/melt/bolt11", post(cache_post_melt_bolt11))
|
||||
.route("/checkstate", post(post_check))
|
||||
.route("/info", get(get_mint_info))
|
||||
.route("/restore", post(post_restore));
|
||||
@@ -42,9 +58,3 @@ pub async fn create_mint_router(mint: Arc<Mint>) -> Result<Router> {
|
||||
|
||||
Ok(mint_router)
|
||||
}
|
||||
|
||||
/// CDK Mint State
|
||||
#[derive(Clone)]
|
||||
pub struct MintState {
|
||||
mint: Arc<Mint>,
|
||||
}
|
||||
|
||||
@@ -10,9 +10,45 @@ use cdk::nuts::{
|
||||
SwapRequest, SwapResponse,
|
||||
};
|
||||
use cdk::util::unix_time;
|
||||
use cdk::Error;
|
||||
use paste::paste;
|
||||
|
||||
use crate::MintState;
|
||||
|
||||
macro_rules! post_cache_wrapper {
|
||||
($handler:ident, $request_type:ty, $response_type:ty) => {
|
||||
paste! {
|
||||
/// Cache wrapper function for $handler:
|
||||
/// Wrap $handler into a function that caches responses using the request as key
|
||||
pub async fn [<cache_ $handler>](
|
||||
state: State<MintState>,
|
||||
payload: Json<$request_type>
|
||||
) -> Result<Json<$response_type>, Response> {
|
||||
let Json(json_extracted_payload) = payload.clone();
|
||||
let State(mint_state) = state.clone();
|
||||
let cache_key = serde_json::to_string(&json_extracted_payload).map_err(|err| {
|
||||
into_response(Error::from(err))
|
||||
})?;
|
||||
|
||||
if let Some(cached_response) = mint_state.cache.get(&cache_key) {
|
||||
return Ok(Json(serde_json::from_str(&cached_response)
|
||||
.expect("Shouldn't panic: response is json-deserializable.")));
|
||||
}
|
||||
|
||||
let Json(response) = $handler(state, payload).await?;
|
||||
mint_state.cache.insert(cache_key, serde_json::to_string(&response)
|
||||
.expect("Shouldn't panic: response is json-serializable.")
|
||||
).await;
|
||||
Ok(Json(response))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
post_cache_wrapper!(post_swap, SwapRequest, SwapResponse);
|
||||
post_cache_wrapper!(post_mint_bolt11, MintBolt11Request, MintBolt11Response);
|
||||
post_cache_wrapper!(post_melt_bolt11, MeltBolt11Request, MeltBolt11Response);
|
||||
|
||||
pub async fn get_keys(State(state): State<MintState>) -> Result<Json<KeysResponse>, Response> {
|
||||
let pubkeys = state.mint.pubkeys().await.map_err(|err| {
|
||||
tracing::error!("Could not get keys: {}", err);
|
||||
|
||||
@@ -35,6 +35,7 @@ ln-regtest-rs = { git = "https://github.com/thesimplekid/ln-regtest-rs", rev = "
|
||||
lightning-invoice = { version = "0.32.0", features = ["serde", "std"] }
|
||||
tracing = { version = "0.1", default-features = false, features = ["attributes", "log"] }
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||
tower-service = "0.3.3"
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
tokio = { version = "1", features = [
|
||||
|
||||
@@ -61,10 +61,11 @@ where
|
||||
);
|
||||
|
||||
let mint = create_mint(database, ln_backends.clone()).await?;
|
||||
|
||||
let cache_ttl = 3600;
|
||||
let cache_tti = 3600;
|
||||
let mint_arc = Arc::new(mint);
|
||||
|
||||
let v1_service = cdk_axum::create_mint_router(Arc::clone(&mint_arc))
|
||||
let v1_service = cdk_axum::create_mint_router(Arc::clone(&mint_arc), cache_ttl, cache_tti)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -214,12 +214,17 @@ where
|
||||
);
|
||||
|
||||
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 v1_service = cdk_axum::create_mint_router(Arc::clone(&mint_arc))
|
||||
.await
|
||||
.unwrap();
|
||||
let v1_service = cdk_axum::create_mint_router(
|
||||
Arc::clone(&mint_arc),
|
||||
cache_time_to_live,
|
||||
cache_time_to_idle,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mint_service = Router::new()
|
||||
.merge(v1_service)
|
||||
|
||||
@@ -84,10 +84,17 @@ pub async fn start_mint(
|
||||
supported_units,
|
||||
)
|
||||
.await?;
|
||||
let cache_time_to_live = 3600;
|
||||
let cache_time_to_idle = 3600;
|
||||
|
||||
let mint_arc = Arc::new(mint);
|
||||
|
||||
let v1_service = cdk_axum::create_mint_router(Arc::clone(&mint_arc)).await?;
|
||||
let v1_service = cdk_axum::create_mint_router(
|
||||
Arc::clone(&mint_arc),
|
||||
cache_time_to_live,
|
||||
cache_time_to_idle,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mint_service = Router::new()
|
||||
.merge(v1_service)
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
use std::{str::FromStr, sync::Arc};
|
||||
use std::{str::FromStr, sync::Arc, time::Duration};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use bip39::Mnemonic;
|
||||
use cdk::{
|
||||
amount::{Amount, SplitTarget},
|
||||
cdk_database::WalletMemoryDatabase,
|
||||
nuts::{CurrencyUnit, MeltQuoteState, State},
|
||||
wallet::Wallet,
|
||||
nuts::{CurrencyUnit, MeltQuoteState, MintQuoteState, PreMintSecrets, State},
|
||||
wallet::{client::HttpClient, Wallet},
|
||||
};
|
||||
use cdk_integration_tests::init_regtest::{get_mint_url, init_cln_client, init_lnd_client};
|
||||
use lightning_invoice::Bolt11Invoice;
|
||||
use ln_regtest_rs::InvoiceStatus;
|
||||
use tokio::time::sleep;
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
||||
async fn test_regtest_mint_melt_round_trip() -> Result<()> {
|
||||
@@ -253,3 +254,52 @@ async fn test_internal_payment() -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
||||
async fn test_cached_mint() -> Result<()> {
|
||||
let lnd_client = init_lnd_client().await.unwrap();
|
||||
|
||||
let wallet = Wallet::new(
|
||||
&get_mint_url(),
|
||||
CurrencyUnit::Sat,
|
||||
Arc::new(WalletMemoryDatabase::default()),
|
||||
&Mnemonic::generate(12)?.to_seed_normalized(""),
|
||||
None,
|
||||
)?;
|
||||
|
||||
let mint_amount = Amount::from(100);
|
||||
|
||||
let quote = wallet.mint_quote(mint_amount, None).await?;
|
||||
lnd_client.pay_invoice(quote.request).await?;
|
||||
|
||||
loop {
|
||||
let status = wallet.mint_quote_state("e.id).await.unwrap();
|
||||
|
||||
println!("Quote status: {}", status.state);
|
||||
|
||||
if status.state == MintQuoteState::Paid {
|
||||
break;
|
||||
}
|
||||
|
||||
sleep(Duration::from_secs(5)).await;
|
||||
}
|
||||
|
||||
let active_keyset_id = wallet.get_active_mint_keyset().await?.id;
|
||||
let http_client = HttpClient::new();
|
||||
let premint_secrets =
|
||||
PreMintSecrets::random(active_keyset_id, 31.into(), &SplitTarget::default()).unwrap();
|
||||
|
||||
let response = http_client
|
||||
.post_mint(
|
||||
get_mint_url().as_str().parse()?,
|
||||
"e.id,
|
||||
premint_secrets.clone(),
|
||||
)
|
||||
.await?;
|
||||
let response1 = http_client
|
||||
.post_mint(get_mint_url().as_str().parse()?, "e.id, premint_secrets)
|
||||
.await?;
|
||||
|
||||
assert!(response == response1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ pub struct Info {
|
||||
pub listen_port: u16,
|
||||
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>,
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,8 @@ mod config;
|
||||
|
||||
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]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
@@ -458,8 +460,16 @@ async fn main() -> anyhow::Result<()> {
|
||||
.info
|
||||
.seconds_quote_is_valid_for
|
||||
.unwrap_or(DEFAULT_QUOTE_TTL_SECS);
|
||||
let cache_ttl = settings
|
||||
.info
|
||||
.seconds_to_cache_requests_for
|
||||
.unwrap_or(DEFAULT_CACHE_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)).await?;
|
||||
let v1_service = cdk_axum::create_mint_router(Arc::clone(&mint), cache_ttl, cache_tti).await?;
|
||||
|
||||
let mut mint_service = Router::new()
|
||||
.merge(v1_service)
|
||||
|
||||
@@ -137,6 +137,8 @@
|
||||
cargo update -p backtrace --precise 0.3.58
|
||||
# For wasm32-unknown-unknown target
|
||||
cargo update -p bumpalo --precise 3.12.0
|
||||
cargo update -p moka --precise 0.11.1
|
||||
cargo update -p triomphe --precise 0.1.11
|
||||
";
|
||||
buildInputs = buildInputs ++ WASMInputs ++ [ msrv_toolchain ];
|
||||
inherit nativeBuildInputs;
|
||||
|
||||
Reference in New Issue
Block a user