commit f1b08eed8215d1f866ef35fe86f728a2aabd5e3c
parent 28b9f91c8566ac7e414f826fd82f03195eadb2cd
Author: Antoine A <>
Date: Fri, 24 Apr 2026 10:38:00 +0200
nexus: add bench
Diffstat:
| M | Cargo.lock | | | 3 | --- |
| M | src/api.rs | | | 43 | ++++++++++++------------------------------- |
| A | src/bench.rs | | | 285 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | src/config.rs | | | 6 | +++--- |
| M | src/db.rs | | | 96 | ++++--------------------------------------------------------------------------- |
| M | src/db/initiated.rs | | | 13 | +++++++------ |
| M | src/db/payment.rs | | | 46 | ++++++++++++++++++++++------------------------ |
| M | src/lib.rs | | | 260 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
8 files changed, 593 insertions(+), 159 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -3110,9 +3110,6 @@ dependencies = [
[[package]]
name = "taler-build"
version = "1.5.0"
-dependencies = [
- "const_format",
-]
[[package]]
name = "taler-common"
diff --git a/src/api.rs b/src/api.rs
@@ -309,11 +309,8 @@ impl PreparedTransfer for NexusApi {
}
#[cfg(test)]
-mod test {
- use std::{
- str::FromStr as _,
- sync::{Arc, LazyLock},
- };
+pub mod test {
+ use std::sync::Arc;
use sqlx::PgPool;
use taler_api::{api::TalerRouter as _, auth::AuthMethod, subject::OutgoingSubject};
@@ -321,10 +318,6 @@ mod test {
api_revenue::RevenueConfig,
api_transfer::PreparedTransferConfig,
api_wire::{OutgoingHistory, TransferState, WireConfig},
- types::{
- amount::Currency,
- payto::{PaytoURI, payto},
- },
};
use taler_test_utils::{
Router,
@@ -339,25 +332,13 @@ mod test {
use crate::{
CONFIG_SOURCE,
api::NexusApi,
- db::{
- payment::register_out_tx,
- test::{check_in, gen_out_pay},
- },
+ db::{payment::register_out_tx, test::check_in},
+ test::{ACCOUNT, CURR, gen_out_pay},
};
- static ACCOUNT: LazyLock<PaytoURI> =
- LazyLock::new(|| payto("payto://iban/CH4189144589712575493?receiver-name=Test"));
-
- async fn setup() -> (Router, PgPool) {
+ pub async fn api_setup() -> (Router, PgPool) {
let (_, pool) = db_test_setup(CONFIG_SOURCE).await;
- let api = Arc::new(
- NexusApi::start(
- pool.clone(),
- ACCOUNT.clone(),
- Currency::from_str("TEST").unwrap(),
- )
- .await,
- );
+ let api = Arc::new(NexusApi::start(pool.clone(), ACCOUNT.clone(), CURR).await);
let server = Router::new()
.wire_gateway(api.clone(), AuthMethod::None)
.prepared_transfer(api.clone())
@@ -369,7 +350,7 @@ mod test {
#[tokio::test]
async fn config() {
- let (server, _) = setup().await;
+ let (server, _) = api_setup().await;
server
.get("/taler-wire-gateway/config")
.await
@@ -386,7 +367,7 @@ mod test {
#[tokio::test]
async fn transfer() {
- let (server, _) = setup().await;
+ let (server, _) = api_setup().await;
transfer_routine(&server, TransferState::pending, &ACCOUNT).await;
// TODO
/*db.initiated.batchSubmissionSuccess(1, Instant.now(), "ORDER1")
@@ -402,7 +383,7 @@ mod test {
#[tokio::test]
async fn outgoing_history() {
- let (server, pool) = setup().await;
+ let (server, pool) = api_setup().await;
routine_pagination::<OutgoingHistory>(
&server,
"/taler-wire-gateway/history/outgoing",
@@ -421,19 +402,19 @@ mod test {
#[tokio::test]
async fn admin_add_incoming() {
- let (server, _) = setup().await;
+ let (server, _) = api_setup().await;
admin_add_incoming_routine(&server, &ACCOUNT, true).await;
}
#[tokio::test]
async fn revenue() {
- let (server, _) = setup().await;
+ let (server, _) = api_setup().await;
revenue_routine(&server, &ACCOUNT, true).await;
}
#[tokio::test]
async fn registration() {
- let (server, pool) = setup().await;
+ let (server, pool) = api_setup().await;
registration_routine(&server, &ACCOUNT, || check_in(&pool)).await;
}
}
diff --git a/src/bench.rs b/src/bench.rs
@@ -0,0 +1,285 @@
+/*
+* This file is part of LibEuFin.
+* Copyright (C) 2026 Taler Systems S.A.
+
+* LibEuFin is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation; either version 3, or
+* (at your option) any later version.
+
+* LibEuFin is distributed in the hope that it will be useful, but
+* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General
+* Public License for more details.
+
+* You should have received a copy of the GNU Affero General Public
+* License along with LibEuFin; see the file COPYING. If not, see
+* <http://www.gnu.org/licenses/>
+*/
+
+#[cfg(test)]
+mod test {
+ use std::fmt::Write as _;
+
+ use aws_lc_rs::signature::{Ed25519KeyPair, KeyPair as _};
+ use compact_str::{CompactString, format_compact};
+ use jiff::Timestamp;
+ use serde_json::json;
+ use taler_api::{crypto::eddsa_sign, subject::subject_fmt_qr_bill};
+ use taler_common::{
+ api_common::{EddsaPublicKey, HashCode, ShortHashCode},
+ bench::{Bench, h32, h64},
+ error_code::ErrorCode,
+ };
+ use taler_test_utils::server::TestServer as _;
+
+ use crate::{
+ api::test::api_setup,
+ test::{
+ ACCOUNT, malformed_completeted_in, malformed_in, malformed_incomplete_in,
+ prepared_completeted_in, prepared_in, prepared_incomplete_in, register_incomplete_out,
+ register_out, talerable_completeted_in, talerable_in, talerable_incomplete_in,
+ },
+ };
+
+ #[tokio::test]
+ pub async fn bench_db() {
+ let (server, db) = api_setup().await;
+ let amount = 100;
+ let iter = 100;
+ let amount = amount.max(10);
+ let accounts_pubs: Vec<_> = (0..amount * 2)
+ .map(|_| {
+ let key_pair = Ed25519KeyPair::generate().unwrap();
+ let pub_key = EddsaPublicKey::try_from(key_pair.public_key().as_ref()).unwrap();
+ (key_pair, pub_key)
+ })
+ .collect();
+ let mut b = Bench::new(&db, iter, amount);
+ b.table("incoming_transactions(amount, subject, execution_time, debit_payto, uetr, tx_id, acct_svcr_ref)", |f, i| {
+ let subject = if i % 4 == 0 { CompactString::const_new("\\N")} else {format_compact!("subject {i}")};
+ let debtor = ACCOUNT.as_ref().as_str() ;
+
+ if i % 3 == 0 {
+ writeln!(f, "(20,0)\t{subject}\t0\t{debtor}\t{}\t\\N\t\\N", uuid::Uuid::new_v4())?;
+ writeln!(f, "(21,0)\t{subject}\t0\t{debtor}\t\\N\tTX_ID{}\t\\N", i*2)?;
+ writeln!(f, "(22,0)\t{subject}\t0\t{debtor}\t\\N\t\\N\tREF{}", i*2)
+ } else if i%3 == 1 {
+ writeln!(f, "(30,0)\t{subject}\t0\t{debtor}\t{}\tTX_ID{}\t\\N", uuid::Uuid::new_v4(), i*2)?;
+ writeln!(f, "(31,0)\t{subject}\t0\t{debtor}\t\\N\tTX_ID{}\tREF{}", i*2+1, i*2)?;
+ writeln!(f, "(32,0)\t{subject}\t0\t{debtor}\t{}\t\\N\tREF{}", uuid::Uuid::new_v4(), i*2+1)
+ } else {
+ writeln!(f, "(40,0)\t{subject}\t0\t{debtor}\t{}\tTX_ID{}\tREF{}", uuid::Uuid::new_v4(), i*2, i*2)?;
+ writeln!(f, "(40,0)\t{subject}\t0\t{debtor}\t{}\tTX_ID{}\tREF{}", uuid::Uuid::new_v4(), i*2+1, i*2+1)
+ }
+ }).await;
+ b.table("outgoing_transactions(amount, subject, execution_time, credit_payto, end_to_end_id, acct_svcr_ref)", |f, i| {
+ let subject = if i % 4 == 0 { CompactString::const_new("\\N")} else {format_compact!("subject {i}")};
+ let creditor =ACCOUNT.as_ref().as_str();
+
+ if i % 2 == 0 {
+ writeln!(f, "(40,0)\t{subject}\t0\t{creditor}\t\\N\tREF{}", i*2)?;
+ writeln!(f, "(41,0)\t{subject}\t0\t{creditor}\tE2E_ID{}\t\\N", i*2)
+ } else {
+ writeln!(f, "(40,0)\t{subject}\t0\t{creditor}\tE2E_ID{}\tREF{}", i*2, i*2)?;
+ writeln!(f, "(41,0)\t{subject}\t0\t{creditor}\tE2E_ID{}\tREF{}", i*2+1, i*2+1)
+ }
+ }).await;
+ b.table("initiated_outgoing_transactions(amount, subject, initiation_time, credit_payto, outgoing_transaction_id, end_to_end_id)", |f, i| {
+ writeln!(f, "(42,0)\tsubject\t0\t{}\t{}\tE2E_ID{i}", &*ACCOUNT , i*2)
+ }).await;
+ b.table("prepared_transfers(type, account_pub, authorization_pub, authorization_sig, recurrent, reference_number, registered_at, incoming_transaction_id)", |f, i| {
+ let ty = if i%2==0 {"reserve"} else {"kyc"};
+ let recurrent = if i%3 == 0 {"true" } else {"false"};
+ let incoming_transaction_id = if i % 5 == 0 { CompactString::const_new("\\N") }else {format_compact!("{}", i*2)};
+
+ let reference_number = subject_fmt_qr_bill(accounts_pubs[i].1.as_ref());
+ let key = hex::encode( accounts_pubs[i].1.as_ref());
+ let sig = h64();
+ writeln!(f, "{ty}\t\\\\x{key}\t\\\\x{key}\t\\\\x{sig}\t{recurrent}\t{reference_number}\t0\t{incoming_transaction_id}")
+ }).await;
+ b.table(
+ "pending_recurrent_incoming_transactions(incoming_transaction_id, authorization_pub)",
+ |f, i| {
+ let key = hex::encode(accounts_pubs[i].1.as_ref());
+ writeln!(f, "{}\t\\\\x{key}", i * 2)
+ },
+ )
+ .await;
+ b.table(
+ "bounced_transactions(incoming_transaction_id, initiated_outgoing_transaction_id)",
+ |f, i| {
+ if i % 10 == 0 {
+ writeln!(f, "{}\t{}", i / 2, i / 2)
+ } else {
+ Ok(())
+ }
+ },
+ )
+ .await;
+ b.table(
+ "talerable_incoming_transactions(type, metadata, incoming_transaction_id)",
+ |f, i| {
+ let hex = h32();
+ let ty = if i % 2 == 0 { "reserve" } else { "kyc" };
+ writeln!(f, "{ty}\t\\\\x{hex}\t{}", i * 2)
+ },
+ )
+ .await;
+ b.table(
+ "talerable_outgoing_transactions(wtid, exchange_base_url, outgoing_transaction_id)",
+ |f, i| {
+ let hex = h32();
+ writeln!(f, "\\\\x{hex}\thttp://exchange.example.com/\t{}", i * 2 - 1)
+ },
+ )
+ .await;
+ b.table("transfer_operations(initiated_outgoing_transaction_id, request_uid, wtid, exchange_base_url)", |f, i| {
+ let h32 = h32();
+ let h64 = h64();
+ writeln!(f, "{i}\t\\\\x{h64}\t\\\\x{h32}\turl")
+ }).await;
+
+ // Warm HTTP client
+ server.get("/taler-revenue/config").await.assert_ok();
+
+ // Register
+ b.measure("register_in", |_| malformed_in(&db)).await;
+ b.measure("register_incomplete_in", |_| malformed_incomplete_in(&db))
+ .await;
+ b.measure("register_completed_in", |_| malformed_completeted_in(&db))
+ .await;
+ b.measure("register_talerable_in", |_| talerable_in(&db))
+ .await;
+ b.measure("register_talerable_incomplete_in", |_| {
+ talerable_incomplete_in(&db)
+ })
+ .await;
+ b.measure("register_talerable_completed_in", |_| {
+ talerable_completeted_in(&db)
+ })
+ .await;
+ b.measure("register_prepared_in", |_| prepared_in(&db))
+ .await;
+ b.measure("register_prepared_incomplete_in", |_| {
+ prepared_incomplete_in(&db)
+ })
+ .await;
+ b.measure("register_prepared_completed_in", |_| {
+ prepared_completeted_in(&db)
+ })
+ .await;
+ b.measure("register_out", |_| register_out(&db)).await;
+ b.measure("register_incomplete_out", |_| register_incomplete_out(&db))
+ .await;
+
+ // Revenue api
+ b.measure("transaction_revenue", async |_| {
+ server.get("/taler-revenue/history").await.assert_ok()
+ })
+ .await;
+
+ // Wire gateway
+ b.measure("wg_transfer", async |_| {
+ server
+ .post("/taler-wire-gateway/transfer")
+ .json(&json!({
+ "request_uid": HashCode::rand(),
+ "amount": "KUDOS:0.0001",
+ "exchange_base_url": "http://exchange.example.com/",
+ "wtid": ShortHashCode::rand(),
+ "credit_account": &*ACCOUNT
+ }))
+ .await
+ .assert_ok()
+ })
+ .await;
+ b.measure("wg_transfer_get", async |i| {
+ server
+ .get(&format!("/taler-wire-gateway/transfers/{}", i + 1))
+ .await
+ .assert_ok()
+ })
+ .await;
+ b.measure("wg_transfer_page", async |_| {
+ server
+ .get("/taler-wire-gateway/transfers")
+ .await
+ .assert_ok()
+ })
+ .await;
+ b.measure("wg_transfer_page_filter", async |_| {
+ server
+ .get("/taler-wire-gateway/transfers?status=success")
+ .await
+ .assert_no_content()
+ })
+ .await;
+ b.measure("wg_add", async |_| {
+ server
+ .post("/taler-wire-gateway/admin/add-incoming")
+ .json(&json!({
+ "amount": "KUDOS:0.0001",
+ "reserve_pub": EddsaPublicKey::rand(),
+ "debit_account": &*ACCOUNT
+ }))
+ .await
+ .assert_ok()
+ })
+ .await;
+ b.measure("wg_incoming", async |_| {
+ server
+ .get("/taler-wire-gateway/history/incoming")
+ .await
+ .assert_ok()
+ })
+ .await;
+ b.measure("wg_outgoing", async |_| {
+ server
+ .get("/taler-wire-gateway/history/outgoing")
+ .await
+ .assert_ok()
+ })
+ .await;
+
+ // Wire transfer
+ b.measure("wt_register", async |i| {
+ let (pair, key) = &accounts_pubs[i];
+
+ server
+ .post("/taler-prepared-transfer/registration")
+ .json(&json!({
+ "credit_amount": "KUDOS:55",
+ "type": "reserve",
+ "alg": "EdDSA",
+ "account_pub": key,
+ "authorization_pub": key,
+ "authorization_sig": eddsa_sign(&pair, key.as_ref()),
+ "recurrent":false
+ }))
+ .await
+ .assert_ok()
+ })
+ .await;
+ b.measure("wt_unregister", async |i| {
+ let (pair, key) = &accounts_pubs[i];
+ let now = Timestamp::now().to_string();
+ let req = json!({
+ "timestamp": &now,
+ "authorization_pub": key,
+ "authorization_sig": eddsa_sign(&pair, now.as_ref()),
+ });
+ server
+ .post("/taler-prepared-transfer/unregistration")
+ .json(&req)
+ .await
+ .assert_no_content();
+ server
+ .post("/taler-prepared-transfer/unregistration")
+ .json(&req)
+ .await
+ .assert_error(ErrorCode::BANK_TRANSACTION_NOT_FOUND);
+ })
+ .await;
+ }
+}
diff --git a/src/config.rs b/src/config.rs
@@ -17,7 +17,7 @@
* <http://www.gnu.org/licenses/>
*/
-use std::{cell::OnceCell, str::FromStr, time::Duration};
+use std::{cell::OnceCell, time::Duration};
use jiff::{
Timestamp,
@@ -92,7 +92,7 @@ pub struct NexusIngestCfg {
}
impl NexusIngestCfg {
- pub fn simple(account_type: AccountType, currency: &Currency) -> Self {
+ pub const fn simple(account_type: AccountType, currency: &Currency) -> Self {
Self {
account_type,
ignore_txs_before: Timestamp::UNIX_EPOCH,
@@ -100,7 +100,7 @@ impl NexusIngestCfg {
restriction_payto_regex: None,
bounce_deduce_fee: false,
bounce_fee: Amount::zero(currency),
- currency: Currency::from_str("KUDOS").unwrap(),
+ currency: Currency::KUDOS,
}
}
}
diff --git a/src/db.rs b/src/db.rs
@@ -93,108 +93,20 @@ pub async fn ebics_first(db: &PgPool) -> sqlx::Result<Option<CompactString>> {
#[cfg(test)]
pub mod test {
- use std::{str::FromStr, sync::LazyLock};
-
- use compact_str::CompactString;
- use jiff::Timestamp;
use sqlx::{PgPool, Postgres, Row, pool::PoolConnection, postgres::PgRow};
use taler_api::db::TypeHelper;
- use taler_common::{
- db::IncomingType,
- types::{
- amount::{Amount, Currency},
- payto::IbanPayto,
- },
- };
+ use taler_common::db::IncomingType;
use taler_test_utils::routine::Status;
use crate::{
CONFIG_SOURCE,
- db::{
- ebics_first, ebics_register, ebics_remove,
- initiated::{PaymentInitiationResult, initiate},
- },
- model::{InId, InTx, Initiated, OutId, OutTx},
- rand_ebics_id,
+ db::{ebics_first, ebics_register, ebics_remove},
};
- pub static CURRENCY: LazyLock<Currency> = LazyLock::new(|| "KUDOS".parse().unwrap());
-
- pub async fn setup() -> (PoolConnection<Postgres>, PgPool) {
+ pub async fn db_setup() -> (PoolConnection<Postgres>, PgPool) {
taler_test_utils::db::db_test_setup(CONFIG_SOURCE).await
}
- /** Generates an outgoing payment, given its subject */
- pub fn gen_out_pay(subject: impl Into<Box<str>>) -> OutTx {
- OutTx {
- id: OutId {
- msg_id: None,
- e2e_id: Some(rand_ebics_id()),
- sref: None,
- },
- amount: Amount::new(&CURRENCY, 44, 0),
- debit_fee: Amount::zero(&CURRENCY),
- creditor: Some(
- IbanPayto::from_str("payto://iban/CH4189144589712575493?receiver-name=Test")
- .unwrap()
- .as_payto(),
- ),
- subject: Some(subject.into()),
- execution_time: Timestamp::now(),
- }
- }
-
- /** Generates a payment initiation, given its subject and end-to-end ID */
- pub fn gen_init_pay(
- end_to_end_id: impl Into<CompactString>,
- subject: impl Into<String>,
- ) -> Initiated {
- Initiated {
- id: 0,
- amount: Amount::new(&CURRENCY, 44, 0),
- creditor: IbanPayto::from_str("payto://iban/CH4189144589712575493?receiver-name=Test")
- .unwrap()
- .as_payto(),
- subject: subject.into(),
- initiation_time: Timestamp::now(),
- e2e_id: end_to_end_id.into(),
- }
- }
-
- /** Generates an incoming payment, given its subject */
- pub fn gen_in_pay(subject: impl Into<Box<str>>) -> InTx {
- InTx {
- id: InId::new(None, Some(rand_ebics_id()), None),
- amount: Amount::new(&CURRENCY, 44, 0),
- credit_fee: Amount::zero(&CURRENCY),
- debtor: Some(
- IbanPayto::from_str("payto://iban/DE84500105177118117964?receiver-name=John+Smith")
- .unwrap()
- .as_payto(),
- ),
- subject: Some(subject.into()),
- execution_time: Timestamp::now(),
- }
- }
-
- pub async fn gen_initiate(
- db: &PgPool,
- end_to_end_id: impl Into<CompactString>,
- subject: impl Into<String>,
- ) -> PaymentInitiationResult {
- let init = gen_init_pay(end_to_end_id, subject);
- initiate(
- &db,
- &init.amount,
- &init.subject,
- &init.creditor,
- &init.initiation_time,
- &init.e2e_id,
- )
- .await
- .unwrap()
- }
-
pub async fn check_count(db: &PgPool, nb_tx: usize, nb_bounce: usize) {
sqlx::query(
"
@@ -298,7 +210,7 @@ pub mod test {
#[tokio::test]
pub async fn ebics_pending() {
- let (_, db) = setup().await;
+ let (_, db) = db_setup().await;
let ids = ["first", "second", "third"];
for id in ids {
diff --git a/src/db/initiated.rs b/src/db/initiated.rs
@@ -459,16 +459,17 @@ mod test {
batch_sub_success, initiated_submittable, order_failure, order_step, order_success,
tx_status_update,
},
- test::{CURRENCY, check_count, gen_in_pay, gen_initiate, gen_out_pay, setup},
+ test::{check_count, db_setup},
},
model::{SubmissionState, Tx},
rand_ebics_id,
+ test::{CURR, gen_in_pay, gen_initiate, gen_out_pay},
worker::{register_outgoing, register_tx},
};
#[tokio::test]
pub async fn initiated_skip() {
- let (_, db) = setup().await;
+ let (_, db) = db_setup().await;
let cfg = Config::from_file(CONFIG_SOURCE, Some("libeufin-nexus/conf/skip.conf")).unwrap();
let cfg = NexusCfg::parse(cfg).unwrap();
let cfg = cfg.ingest().unwrap();
@@ -521,7 +522,7 @@ mod test {
pub async fn initiated_status() {
use SubmissionState::*;
- let (_, db) = setup().await;
+ let (_, db) = db_setup().await;
let check_parts = async |batch_id: u64,
batch_status: SubmissionState,
@@ -636,7 +637,7 @@ mod test {
assert!(r.try_get_flag(0).unwrap());
Ok(())
}).fetch_one(db).await.unwrap();
- let submitibale = initiated_submittable(db, &CURRENCY).await.unwrap();
+ let submitibale = initiated_submittable(db, &CURR).await.unwrap();
lambda(
submitibale
.iter()
@@ -835,7 +836,7 @@ mod test {
#[tokio::test]
pub async fn initiated_submittables() {
- let (_, db) = setup().await;
+ let (_, db) = db_setup().await;
let now = Timestamp::now();
for i in 0..6 {
assert!(matches!(
@@ -850,7 +851,7 @@ mod test {
let check_ids = async |ids: &[&str]| {
assert_eq!(
ids,
- initiated_submittable(&db, &CURRENCY)
+ initiated_submittable(&db, &CURR)
.await
.unwrap()
.iter()
diff --git a/src/db/payment.rs b/src/db/payment.rs
@@ -321,20 +321,18 @@ mod test {
InResult, IncomingBounceRegistrationResult, OutgoingRegistrationResult,
register_in_malformed,
},
- test::{
- CURRENCY, check_in_count, check_in_state, check_out_count, gen_in_pay,
- gen_initiate, gen_out_pay, setup,
- },
+ test::{check_in_count, check_in_state, check_out_count, db_setup},
transfer::{RegistrationResult, transfer_register},
},
model::{InId, InTx, OutBatch, OutId, OutTx},
rand_ebics_id,
+ test::{CURR, gen_in_pay, gen_initiate, gen_out_pay},
worker::{register_incoming, register_outgoing, register_outgoing_batch},
};
#[tokio::test]
async fn out_tx() {
- let (_, db) = setup().await;
+ let (_, db) = db_setup().await;
// Register initiated transactions
for subject in [
"initiated by nexus".to_owned(),
@@ -446,7 +444,7 @@ mod test {
#[tokio::test]
async fn out_batch() {
- let (_, db) = setup().await;
+ let (_, db) = db_setup().await;
// Init batch
let wtid = ShortHashCode::rand();
for subject in [
@@ -467,7 +465,7 @@ mod test {
// Register batch
register_outgoing_batch(
&db,
- &CURRENCY,
+ &CURR,
&OutBatch {
msg_id: "BATCH".into(),
execution_time: Timestamp::now(),
@@ -494,7 +492,7 @@ mod test {
.unwrap();
register_outgoing_batch(
&db,
- &CURRENCY,
+ &CURR,
&OutBatch {
msg_id: "BATCH_MANUAL".into(),
execution_time: Timestamp::now(),
@@ -513,7 +511,7 @@ mod test {
.unwrap();
register_outgoing_batch(
&db,
- &CURRENCY,
+ &CURR,
&OutBatch {
msg_id: "BATCH_MANUAL".into(),
execution_time: Timestamp::now(),
@@ -526,7 +524,7 @@ mod test {
#[tokio::test]
async fn in_bounce() {
- let (_, db) = setup().await;
+ let (_, db) = db_setup().await;
// Creating and bouncing one incoming transaction
let payment = gen_in_pay("incoming and bounce");
@@ -593,8 +591,8 @@ mod test {
",
)
.try_map(|r: PgRow| {
- assert_eq!(r.try_get_amount("in_amount", &CURRENCY)?, payment.amount);
- assert_eq!(r.try_get_amount("bounce_amount", &CURRENCY)?, bounce_amount);
+ assert_eq!(r.try_get_amount("in_amount", &CURR)?, payment.amount);
+ assert_eq!(r.try_get_amount("bounce_amount", &CURR)?, bounce_amount);
Ok(())
})
.fetch_one(&db)
@@ -604,9 +602,9 @@ mod test {
#[tokio::test]
async fn in_simple() {
- let (_, db) = setup().await;
+ let (_, db) = db_setup().await;
- let cfg = NexusIngestCfg::simple(AccountType::Exchange, &CURRENCY);
+ let cfg = NexusIngestCfg::simple(AccountType::Exchange, &CURR);
// Register
let incoming = gen_in_pay("test".to_owned());
@@ -650,9 +648,9 @@ mod test {
#[tokio::test]
async fn in_talerable() {
- let (_, db) = setup().await;
+ let (_, db) = db_setup().await;
- let cfg = NexusIngestCfg::simple(AccountType::Exchange, &CURRENCY);
+ let cfg = NexusIngestCfg::simple(AccountType::Exchange, &CURR);
let key = EddsaPublicKey::rand();
let subject = format!("test with {key} reserve pub");
@@ -713,8 +711,8 @@ mod test {
#[tokio::test]
async fn in_mapping() {
- let (_, db) = setup().await;
- let cfg = NexusIngestCfg::simple(AccountType::Exchange, &CURRENCY);
+ let (_, db) = db_setup().await;
+ let cfg = NexusIngestCfg::simple(AccountType::Exchange, &CURR);
let first = EddsaPublicKey::rand();
let auth_pub = EddsaPublicKey::rand();
let auth_sig = EddsaSignature::rand();
@@ -838,8 +836,8 @@ mod test {
#[tokio::test]
async fn in_reference() {
- let (_, db) = setup().await;
- let cfg = NexusIngestCfg::simple(AccountType::Exchange, &CURRENCY);
+ let (_, db) = db_setup().await;
+ let cfg = NexusIngestCfg::simple(AccountType::Exchange, &CURR);
let first = EddsaPublicKey::rand();
let auth_pub = EddsaPublicKey::rand();
let auth_sig = EddsaSignature::rand();
@@ -962,8 +960,8 @@ mod test {
#[tokio::test]
async fn in_recover_info() {
- let (_, db) = setup().await;
- let cfg = NexusIngestCfg::simple(AccountType::Exchange, &CURRENCY);
+ let (_, db) = db_setup().await;
+ let cfg = NexusIngestCfg::simple(AccountType::Exchange, &CURR);
async fn check_content(db: &PgPool, p: &InTx) {
sqlx::query(
@@ -1087,8 +1085,8 @@ mod test {
#[tokio::test]
pub async fn in_horror() {
- let (_, db) = setup().await;
- let cfg = NexusIngestCfg::simple(AccountType::Exchange, &CURRENCY);
+ let (_, db) = db_setup().await;
+ let cfg = NexusIngestCfg::simple(AccountType::Exchange, &CURR);
// Check we do not bounce already registered talerable transaction
let key = EddsaPublicKey::rand();
diff --git a/src/lib.rs b/src/lib.rs
@@ -80,6 +80,7 @@ use crate::{
};
pub mod api;
+pub mod bench;
pub mod config;
pub mod crypto;
pub mod db;
@@ -656,3 +657,262 @@ pub async fn run(cfg: Config, cmd: &Cmd, ebics_log: &EbicsLogger) -> anyhow::Res
}
Ok(())
}
+
+#[cfg(test)]
+pub mod test {
+ use std::{str::FromStr as _, sync::LazyLock};
+
+ use compact_str::CompactString;
+ use jiff::Timestamp;
+ use sqlx::PgPool;
+ use taler_api::subject::{fmt_in_subject, subject_fmt_qr_bill};
+ use taler_common::{
+ api_common::{EddsaPublicKey, EddsaSignature},
+ db::IncomingType,
+ types::{
+ amount::{Amount, Currency},
+ payto::{IbanPayto, PaytoURI, payto},
+ },
+ };
+
+ use crate::{
+ config::{AccountType, NexusIngestCfg},
+ db::{
+ initiated::{PaymentInitiationResult, initiate},
+ transfer::{RegistrationResult, transfer_register},
+ },
+ model::{InId, InTx, Initiated, OutId, OutTx},
+ rand_ebics_id,
+ worker::{register_incoming, register_outgoing},
+ };
+
+ pub const CURR: Currency = Currency::KUDOS;
+ pub static ACCOUNT: LazyLock<PaytoURI> =
+ LazyLock::new(|| payto("payto://iban/CH4189144589712575493?receiver-name=Test"));
+
+ /** Generates an outgoing payment, given its subject */
+ pub fn gen_out_pay(subject: impl Into<Box<str>>) -> OutTx {
+ OutTx {
+ id: OutId {
+ msg_id: None,
+ e2e_id: Some(rand_ebics_id()),
+ sref: None,
+ },
+ amount: Amount::new(&CURR, 44, 0),
+ debit_fee: Amount::zero(&CURR),
+ creditor: Some(
+ IbanPayto::from_str("payto://iban/CH4189144589712575493?receiver-name=Test")
+ .unwrap()
+ .as_payto(),
+ ),
+ subject: Some(subject.into()),
+ execution_time: Timestamp::now(),
+ }
+ }
+
+ /** Generates a payment initiation, given its subject and end-to-end ID */
+ pub fn gen_init_pay(
+ end_to_end_id: impl Into<CompactString>,
+ subject: impl Into<String>,
+ ) -> Initiated {
+ Initiated {
+ id: 0,
+ amount: Amount::new(&CURR, 44, 0),
+ creditor: IbanPayto::from_str("payto://iban/CH4189144589712575493?receiver-name=Test")
+ .unwrap()
+ .as_payto(),
+ subject: subject.into(),
+ initiation_time: Timestamp::now(),
+ e2e_id: end_to_end_id.into(),
+ }
+ }
+
+ /** Generates an incoming payment, given its subject */
+ pub fn gen_in_pay(subject: impl Into<Box<str>>) -> InTx {
+ InTx {
+ id: InId::new(None, Some(rand_ebics_id()), None),
+ amount: Amount::new(&CURR, 44, 0),
+ credit_fee: Amount::zero(&CURR),
+ debtor: Some(
+ IbanPayto::from_str("payto://iban/DE84500105177118117964?receiver-name=John+Smith")
+ .unwrap()
+ .as_payto(),
+ ),
+ subject: Some(subject.into()),
+ execution_time: Timestamp::now(),
+ }
+ }
+
+ pub async fn gen_initiate(
+ db: &PgPool,
+ end_to_end_id: impl Into<CompactString>,
+ subject: impl Into<String>,
+ ) -> PaymentInitiationResult {
+ let init = gen_init_pay(end_to_end_id, subject);
+ initiate(
+ &db,
+ &init.amount,
+ &init.subject,
+ &init.creditor,
+ &init.initiation_time,
+ &init.e2e_id,
+ )
+ .await
+ .unwrap()
+ }
+
+ const CFG: NexusIngestCfg = NexusIngestCfg::simple(AccountType::Exchange, &CURR);
+
+ async fn prepare(db: &PgPool) -> String {
+ let key = EddsaPublicKey::rand();
+ let sig = EddsaSignature::rand();
+ let reference_number = subject_fmt_qr_bill(key.as_ref());
+ assert_eq!(
+ RegistrationResult::Success,
+ transfer_register(
+ db,
+ IncomingType::reserve,
+ &key,
+ &key,
+ &sig,
+ false,
+ &reference_number,
+ &Timestamp::now()
+ )
+ .await
+ .unwrap()
+ );
+ return reference_number;
+ }
+
+ /// Register a talerable reserve prepared incoming transaction
+ pub async fn prepared_in(db: &PgPool) {
+ let ref_nb = prepare(db).await;
+ register_incoming(db, &CFG, &gen_in_pay(ref_nb))
+ .await
+ .unwrap();
+ }
+
+ /// Register an incomplete talerable reserve prepared incoming transaction
+ pub async fn prepared_incomplete_in(db: &PgPool) {
+ let ref_nb = prepare(db).await;
+ let incomplete = InTx {
+ subject: None,
+ debtor: None,
+ ..gen_in_pay(ref_nb)
+ };
+ register_incoming(db, &CFG, &incomplete).await.unwrap();
+ }
+
+ /// Register a completed talerable reserve prepared incoming transaction
+ pub async fn prepared_completeted_in(db: &PgPool) {
+ let ref_nb = prepare(db).await;
+ let original = gen_in_pay(ref_nb);
+ let incomplete = InTx {
+ subject: None,
+ debtor: None,
+ ..original.clone()
+ };
+ register_incoming(db, &CFG, &incomplete).await.unwrap();
+ register_incoming(db, &CFG, &original).await.unwrap();
+ }
+
+ /// Register a talerable reserve incoming transaction
+ pub async fn talerable_in(db: &PgPool) {
+ register_incoming(
+ db,
+ &CFG,
+ &gen_in_pay(fmt_in_subject(
+ IncomingType::reserve,
+ &EddsaPublicKey::rand(),
+ )),
+ )
+ .await
+ .unwrap();
+ }
+
+ /// Register a talerable kyc incoming transaction
+ pub async fn talerable_kyc_in(db: &PgPool) {
+ register_incoming(
+ db,
+ &CFG,
+ &gen_in_pay(fmt_in_subject(IncomingType::kyc, &EddsaPublicKey::rand())),
+ )
+ .await
+ .unwrap();
+ }
+
+ /// Register an incomplete talerable reserve incoming transaction
+ pub async fn talerable_incomplete_in(db: &PgPool) {
+ let incomplete = InTx {
+ subject: None,
+ debtor: None,
+ ..gen_in_pay(fmt_in_subject(
+ IncomingType::reserve,
+ &EddsaPublicKey::rand(),
+ ))
+ };
+ register_incoming(db, &CFG, &incomplete).await.unwrap();
+ }
+
+ /// Register a completed talerable reserve incoming transaction
+ pub async fn talerable_completeted_in(db: &PgPool) {
+ let original = gen_in_pay(fmt_in_subject(
+ IncomingType::reserve,
+ &EddsaPublicKey::rand(),
+ ));
+ let incomplete = InTx {
+ subject: None,
+ debtor: None,
+ ..original.clone()
+ };
+ register_incoming(db, &CFG, &incomplete).await.unwrap();
+ register_incoming(db, &CFG, &original).await.unwrap();
+ }
+
+ /// Register incoming malformed transaction
+ pub async fn malformed_in(db: &PgPool) {
+ register_incoming(db, &CFG, &gen_in_pay("ignored"))
+ .await
+ .unwrap();
+ }
+
+ /// Register incoming incomplete malformed incoming transaction
+ pub async fn malformed_incomplete_in(db: &PgPool) {
+ let incomplete = InTx {
+ subject: None,
+ debtor: None,
+ ..gen_in_pay("ignored")
+ };
+ register_incoming(db, &CFG, &incomplete).await.unwrap();
+ }
+
+ /// Register incoming completed malformed transaction
+ pub async fn malformed_completeted_in(db: &PgPool) {
+ let original = gen_in_pay("ignored");
+ let incomplete = InTx {
+ subject: None,
+ debtor: None,
+ ..original.clone()
+ };
+ register_incoming(db, &CFG, &incomplete).await.unwrap();
+ register_incoming(db, &CFG, &original).await.unwrap();
+ }
+
+ /** Register an outgoing transaction */
+ pub async fn register_out(db: &PgPool) {
+ register_outgoing(db, &gen_out_pay("ignored"))
+ .await
+ .unwrap();
+ }
+
+ /** Register an incomplete outgoing transaction */
+ pub async fn register_incomplete_out(db: &PgPool) {
+ let incomplete = OutTx {
+ subject: None,
+ creditor: None,
+ ..gen_out_pay("ignored")
+ };
+ register_outgoing(db, &incomplete).await.unwrap();
+ }
+}