Skip to content

Commit

Permalink
Refactor mint tranactions checks (#585)
Browse files Browse the repository at this point in the history
* refactor: consolidate validation checks

* refactor: melt verification checks

* refactor: mint verification

* chore: clippy

* chore: use error codes

* fix: order of verifications

* fix: p2pk test ws updates

We only expect the proof to be set to pending once. As a proof without
a signature failes before the spent check where the state is chaged.

* fix: mint_melt regtest frome wait
  • Loading branch information
thesimplekid authored Feb 8, 2025
1 parent b818054 commit a8ec526
Show file tree
Hide file tree
Showing 12 changed files with 554 additions and 236 deletions.
54 changes: 50 additions & 4 deletions crates/cdk-common/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,17 @@ pub enum Error {
#[error("Inputs: `{0}`, Outputs: `{1}`, Expected Fee: `{2}`")]
TransactionUnbalanced(u64, u64, u64),
/// Duplicate proofs provided
#[error("Duplicate proofs")]
DuplicateProofs,
#[error("Duplicate Inputs")]
DuplicateInputs,
/// Duplicate output
#[error("Duplicate outputs")]
DuplicateOutputs,
/// Multiple units provided
#[error("Cannot have multiple units")]
MultipleUnits,
/// Unit mismatch
#[error("Input unit must match output")]
UnitMismatch,
/// Sig all cannot be used in melt
#[error("Sig all cannot be used in melt")]
SigAllUsedInMelt,
Expand Down Expand Up @@ -393,6 +399,26 @@ impl From<Error> for ErrorResponse {
error: Some(err.to_string()),
detail: None,
},
Error::DuplicateInputs => ErrorResponse {
code: ErrorCode::DuplicateInputs,
error: Some(err.to_string()),
detail: None,
},
Error::DuplicateOutputs => ErrorResponse {
code: ErrorCode::DuplicateOutputs,
error: Some(err.to_string()),
detail: None,
},
Error::MultipleUnits => ErrorResponse {
code: ErrorCode::MultipleUnits,
error: Some(err.to_string()),
detail: None,
},
Error::UnitMismatch => ErrorResponse {
code: ErrorCode::UnitMismatch,
error: Some(err.to_string()),
detail: None,
},
_ => ErrorResponse {
code: ErrorCode::Unknown(9999),
error: Some(err.to_string()),
Expand Down Expand Up @@ -423,6 +449,10 @@ impl From<ErrorResponse> for Error {
}
ErrorCode::TokenPending => Self::TokenPending,
ErrorCode::WitnessMissingOrInvalid => Self::SignatureMissingOrInvalid,
ErrorCode::DuplicateInputs => Self::DuplicateInputs,
ErrorCode::DuplicateOutputs => Self::DuplicateOutputs,
ErrorCode::MultipleUnits => Self::MultipleUnits,
ErrorCode::UnitMismatch => Self::UnitMismatch,
_ => Self::UnknownErrorResponse(err.to_string()),
}
}
Expand Down Expand Up @@ -466,6 +496,14 @@ pub enum ErrorCode {
AmountOutofLimitRange,
/// Witness missing or invalid
WitnessMissingOrInvalid,
/// Duplicate Inputs
DuplicateInputs,
/// Duplicate Outputs
DuplicateOutputs,
/// Multiple Units
MultipleUnits,
/// Input unit does not match output
UnitMismatch,
/// Unknown error code
Unknown(u16),
}
Expand All @@ -480,7 +518,11 @@ impl ErrorCode {
11002 => Self::TransactionUnbalanced,
11005 => Self::UnsupportedUnit,
11006 => Self::AmountOutofLimitRange,
11007 => Self::TokenPending,
11007 => Self::DuplicateInputs,
11008 => Self::DuplicateOutputs,
11009 => Self::MultipleUnits,
11010 => Self::UnitMismatch,
11012 => Self::TokenPending,
12001 => Self::KeysetNotFound,
12002 => Self::KeysetInactive,
20000 => Self::LightningError,
Expand All @@ -504,7 +546,11 @@ impl ErrorCode {
Self::TransactionUnbalanced => 11002,
Self::UnsupportedUnit => 11005,
Self::AmountOutofLimitRange => 11006,
Self::TokenPending => 11007,
Self::DuplicateInputs => 11007,
Self::DuplicateOutputs => 11008,
Self::MultipleUnits => 11009,
Self::UnitMismatch => 11010,
Self::TokenPending => 11012,
Self::KeysetNotFound => 12001,
Self::KeysetInactive => 12002,
Self::LightningError => 20000,
Expand Down
13 changes: 13 additions & 0 deletions crates/cdk-integration-tests/src/init_pure_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use cdk::wallet::Wallet;
use cdk::{Amount, Error, Mint};
use cdk_fake_wallet::FakeWallet;
use tokio::sync::Notify;
use tracing_subscriber::EnvFilter;
use uuid::Uuid;

use crate::wait_for_mint_to_be_paid;
Expand Down Expand Up @@ -143,6 +144,18 @@ impl MintConnector for DirectMintConnection {
}

pub async fn create_and_start_test_mint() -> anyhow::Result<Arc<Mint>> {
let default_filter = "debug";

let sqlx_filter = "sqlx=warn";
let hyper_filter = "hyper=warn";

let env_filter = EnvFilter::new(format!(
"{},{},{}",
default_filter, sqlx_filter, hyper_filter
));

tracing_subscriber::fmt().with_env_filter(env_filter).init();

let mut mint_builder = MintBuilder::new();

let database = MintMemoryDatabase::default();
Expand Down
229 changes: 224 additions & 5 deletions crates/cdk-integration-tests/tests/fake_wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,7 @@ async fn test_fake_mint_multiple_units() -> Result<()> {

match response {
Err(err) => match err {
cdk::Error::UnsupportedUnit => (),
cdk::Error::MultipleUnits => (),
err => {
bail!("Wrong mint error returned: {}", err.to_string());
}
Expand Down Expand Up @@ -652,7 +652,7 @@ async fn test_fake_mint_multiple_unit_swap() -> Result<()> {

match response {
Err(err) => match err {
cdk::Error::UnsupportedUnit => (),
cdk::Error::MultipleUnits => (),
err => {
bail!("Wrong mint error returned: {}", err.to_string());
}
Expand Down Expand Up @@ -689,7 +689,7 @@ async fn test_fake_mint_multiple_unit_swap() -> Result<()> {

match response {
Err(err) => match err {
cdk::Error::UnsupportedUnit => (),
cdk::Error::MultipleUnits => (),
err => {
bail!("Wrong mint error returned: {}", err.to_string());
}
Expand Down Expand Up @@ -763,7 +763,7 @@ async fn test_fake_mint_multiple_unit_melt() -> Result<()> {

match response {
Err(err) => match err {
cdk::Error::UnsupportedUnit => (),
cdk::Error::MultipleUnits => (),
err => {
bail!("Wrong mint error returned: {}", err.to_string());
}
Expand Down Expand Up @@ -807,7 +807,7 @@ async fn test_fake_mint_multiple_unit_melt() -> Result<()> {

match response {
Err(err) => match err {
cdk::Error::UnsupportedUnit => (),
cdk::Error::MultipleUnits => (),
err => {
bail!("Wrong mint error returned: {}", err.to_string());
}
Expand All @@ -820,3 +820,222 @@ async fn test_fake_mint_multiple_unit_melt() -> Result<()> {

Ok(())
}

/// Test swap where input unit != output unit
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_fake_mint_input_output_mismatch() -> Result<()> {
let wallet = Wallet::new(
MINT_URL,
CurrencyUnit::Sat,
Arc::new(WalletMemoryDatabase::default()),
&Mnemonic::generate(12)?.to_seed_normalized(""),
None,
)?;

let mint_quote = wallet.mint_quote(100.into(), None).await?;

wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;

let proofs = wallet.mint(&mint_quote.id, SplitTarget::None, None).await?;

let wallet_usd = Wallet::new(
MINT_URL,
CurrencyUnit::Usd,
Arc::new(WalletMemoryDatabase::default()),
&Mnemonic::generate(12)?.to_seed_normalized(""),
None,
)?;

let usd_active_keyset_id = wallet_usd.get_active_mint_keyset().await?.id;

let inputs = proofs;

let pre_mint = PreMintSecrets::random(
usd_active_keyset_id,
inputs.total_amount()?,
&SplitTarget::None,
)?;

let swap_request = SwapRequest {
inputs,
outputs: pre_mint.blinded_messages(),
};

let http_client = HttpClient::new(MINT_URL.parse()?);
let response = http_client.post_swap(swap_request.clone()).await;

match response {
Err(err) => match err {
cdk::Error::UnsupportedUnit => (),
_ => {}
},
Ok(_) => {
bail!("Should not have allowed to mint with multiple units");
}
}

Ok(())
}

/// Test swap where input is less the output
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_fake_mint_swap_inflated() -> Result<()> {
let wallet = Wallet::new(
MINT_URL,
CurrencyUnit::Sat,
Arc::new(WalletMemoryDatabase::default()),
&Mnemonic::generate(12)?.to_seed_normalized(""),
None,
)?;

let mint_quote = wallet.mint_quote(100.into(), None).await?;

wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;

let proofs = wallet.mint(&mint_quote.id, SplitTarget::None, None).await?;
let active_keyset_id = wallet.get_active_mint_keyset().await?.id;
let pre_mint = PreMintSecrets::random(active_keyset_id, 101.into(), &SplitTarget::None)?;

let swap_request = SwapRequest {
inputs: proofs,
outputs: pre_mint.blinded_messages(),
};

let http_client = HttpClient::new(MINT_URL.parse()?);
let response = http_client.post_swap(swap_request.clone()).await;

match response {
Err(err) => match err {
cdk::Error::TransactionUnbalanced(_, _, _) => (),
err => {
bail!("Wrong mint error returned: {}", err.to_string());
}
},
Ok(_) => {
bail!("Should not have allowed to mint with multiple units");
}
}

Ok(())
}

/// Test swap where input unit != output unit
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_fake_mint_duplicate_proofs_swap() -> Result<()> {
let wallet = Wallet::new(
MINT_URL,
CurrencyUnit::Sat,
Arc::new(WalletMemoryDatabase::default()),
&Mnemonic::generate(12)?.to_seed_normalized(""),
None,
)?;

let mint_quote = wallet.mint_quote(100.into(), None).await?;

wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;

let proofs = wallet.mint(&mint_quote.id, SplitTarget::None, None).await?;

let active_keyset_id = wallet.get_active_mint_keyset().await?.id;

let inputs = vec![proofs[0].clone(), proofs[0].clone()];

let pre_mint =
PreMintSecrets::random(active_keyset_id, inputs.total_amount()?, &SplitTarget::None)?;

let swap_request = SwapRequest {
inputs: inputs.clone(),
outputs: pre_mint.blinded_messages(),
};

let http_client = HttpClient::new(MINT_URL.parse()?);
let response = http_client.post_swap(swap_request.clone()).await;

match response {
Err(err) => match err {
cdk::Error::DuplicateInputs => (),
err => {
bail!(
"Wrong mint error returned, expected duplicate inputs: {}",
err.to_string()
);
}
},
Ok(_) => {
bail!("Should not have allowed duplicate inputs");
}
}

let blinded_message = pre_mint.blinded_messages();

let outputs = vec![blinded_message[0].clone(), blinded_message[0].clone()];

let swap_request = SwapRequest { inputs, outputs };

let http_client = HttpClient::new(MINT_URL.parse()?);
let response = http_client.post_swap(swap_request.clone()).await;

match response {
Err(err) => match err {
cdk::Error::DuplicateOutputs => (),
err => {
bail!(
"Wrong mint error returned, expected duplicate outputs: {}",
err.to_string()
);
}
},
Ok(_) => {
bail!("Should not have allow duplicate inputs");
}
}

Ok(())
}

/// Test duplicate proofs in melt
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_fake_mint_duplicate_proofs_melt() -> Result<()> {
let wallet = Wallet::new(
MINT_URL,
CurrencyUnit::Sat,
Arc::new(WalletMemoryDatabase::default()),
&Mnemonic::generate(12)?.to_seed_normalized(""),
None,
)?;

let mint_quote = wallet.mint_quote(100.into(), None).await?;

wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;

let proofs = wallet.mint(&mint_quote.id, SplitTarget::None, None).await?;

let inputs = vec![proofs[0].clone(), proofs[0].clone()];

let invoice = create_fake_invoice(7000, "".to_string());

let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;

let melt_request = MeltBolt11Request {
quote: melt_quote.id,
inputs,
outputs: None,
};

let http_client = HttpClient::new(MINT_URL.parse()?);
let response = http_client.post_melt(melt_request.clone()).await;

match response {
Err(err) => match err {
cdk::Error::DuplicateInputs => (),
err => {
bail!("Wrong mint error returned: {}", err.to_string());
}
},
Ok(_) => {
bail!("Should not have allow duplicate inputs");
}
}

Ok(())
}
Loading

0 comments on commit a8ec526

Please sign in to comment.