Skip to content

Commit

Permalink
Merge branch 'main' into ok300-mintd-swagger-ui
Browse files Browse the repository at this point in the history
# Conflicts:
#	crates/cdk-axum/src/lib.rs
#	crates/cdk-axum/src/router_handlers.rs
  • Loading branch information
ok300 committed Oct 6, 2024
2 parents fa01c3f + 515991f commit 82f4c57
Show file tree
Hide file tree
Showing 12 changed files with 188 additions and 62 deletions.
2 changes: 0 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions crates/cdk-axum/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ tokio = { version = "1", default-features = false }
tracing = { version = "0.1", default-features = false, features = ["attributes", "log"] }
utoipa = { version = "4.1.0" }
futures = { version = "0.3.28", default-features = false }
moka = { version = "0.11.1", features = ["future"] }
serde_json = "1"
paste = "1.0.15"
108 changes: 59 additions & 49 deletions crates/cdk-axum/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#![warn(rustdoc::bare_urls)]

use std::sync::Arc;
use std::time::Duration;

use anyhow::Result;
use axum::routing::{get, post};
Expand All @@ -21,76 +22,91 @@ use cdk::nuts::nut05::MeltMethodSettings;
use cdk::nuts::nut06::{ContactInfo, MintInfo, MintVersion, Nuts, SupportedSettings};
use cdk::nuts::nut15;
use cdk::nuts::nut15::MppMethodSettings;
use moka::future::Cache;
use router_handlers::*;
use utoipa::OpenApi;

mod router_handlers;

/// CDK Mint State
#[derive(Clone)]
pub struct MintState {
mint: Arc<Mint>,
cache: Cache<String, String>,
}

#[derive(OpenApi)]
#[openapi(
components(
schemas(
Amount,
ContactInfo,
CurrencyUnit,
ErrorCode,
ErrorResponse,
Id,
Keys,
KeysResponse,
KeysetResponse,
KeySet,
KeySetInfo,
KeySetVersion,
MeltMethodSettings,
MintInfo,
MintMethodSettings,
MintVersion,
MppMethodSettings,
Nuts,
PaymentMethod,
PublicKey,
SupportedSettings,
nut04::Settings,
nut05::Settings,
nut15::Settings
)
),
info(
description = "Cashu CDK mint APIs",
title = "cdk-mintd",
),
paths(
get_keys,
get_keyset_pubkeys,
get_keysets,
get_mint_info
)
components(
schemas(
Amount,
ContactInfo,
CurrencyUnit,
ErrorCode,
ErrorResponse,
Id,
Keys,
KeysResponse,
KeysetResponse,
KeySet,
KeySetInfo,
KeySetVersion,
MeltMethodSettings,
MintInfo,
MintMethodSettings,
MintVersion,
MppMethodSettings,
Nuts,
PaymentMethod,
PublicKey,
SupportedSettings,
nut04::Settings,
nut05::Settings,
nut15::Settings
)
),
info(
description = "Cashu CDK mint APIs",
title = "cdk-mintd",
),
paths(
get_keys,
get_keyset_pubkeys,
get_keysets,
get_mint_info
)
)]
/// OpenAPI spec for the mint's v1 APIs
pub struct ApiDocV1;

/// 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));
Expand All @@ -99,9 +115,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>,
}
37 changes: 37 additions & 0 deletions crates/cdk-axum/src/router_handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,46 @@ 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, MeltQuoteBolt11Response);


#[utoipa::path(
get,
context_path = "/v1",
Expand Down
1 change: 1 addition & 0 deletions crates/cdk-integration-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down
5 changes: 3 additions & 2 deletions crates/cdk-integration-tests/src/init_fake_wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
13 changes: 9 additions & 4 deletions crates/cdk-integration-tests/src/init_regtest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
9 changes: 8 additions & 1 deletion crates/cdk-integration-tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
56 changes: 53 additions & 3 deletions crates/cdk-integration-tests/tests/regtest.rs
Original file line number Diff line number Diff line change
@@ -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<()> {
Expand Down Expand Up @@ -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(())
}
2 changes: 2 additions & 0 deletions crates/cdk-mintd/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
}

Expand Down
12 changes: 11 additions & 1 deletion crates/cdk-mintd/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,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<()> {
Expand Down Expand Up @@ -460,8 +462,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(SwaggerUi::new("/swagger-ui").url("/api-docs/openapi.json", cdk_axum::ApiDocV1::openapi()))
Expand Down
2 changes: 2 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit 82f4c57

Please sign in to comment.