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:
lollerfirst
2024-10-05 11:18:23 +02:00
committed by GitHub
parent c0967d8079
commit c25bf79e8c
12 changed files with 149 additions and 24 deletions

View File

@@ -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

View File

@@ -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"

View File

@@ -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>,
}

View File

@@ -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);

View File

@@ -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 = [

View File

@@ -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();

View File

@@ -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)

View File

@@ -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)

View File

@@ -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(&quote.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()?,
&quote.id,
premint_secrets.clone(),
)
.await?;
let response1 = http_client
.post_mint(get_mint_url().as_str().parse()?, &quote.id, premint_secrets)
.await?;
assert!(response == response1);
Ok(())
}

View File

@@ -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>,
}

View File

@@ -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)

View File

@@ -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;