libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

bench.rs (10707B)


      1 /*
      2 * This file is part of LibEuFin.
      3 * Copyright (C) 2026 Taler Systems S.A.
      4 
      5 * LibEuFin is free software; you can redistribute it and/or modify
      6 * it under the terms of the GNU Affero General Public License as
      7 * published by the Free Software Foundation; either version 3, or
      8 * (at your option) any later version.
      9 
     10 * LibEuFin is distributed in the hope that it will be useful, but
     11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
     12 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General
     13 * Public License for more details.
     14 
     15 * You should have received a copy of the GNU Affero General Public
     16 * License along with LibEuFin; see the file COPYING.  If not, see
     17 * <http://www.gnu.org/licenses/>
     18 */
     19 
     20 use std::fmt::Write as _;
     21 
     22 use aws_lc_rs::signature::{Ed25519KeyPair, KeyPair as _};
     23 use compact_str::{CompactString, format_compact};
     24 use jiff::Timestamp;
     25 use serde_json::json;
     26 use sqlx::PgPool;
     27 use taler_api::subject::subject_fmt_qr_bill;
     28 use taler_common::{
     29     api::{
     30         EddsaPublicKey, EddsaSignature, HashCode, ShortHashCode,
     31         prepared::{PublicKeyAlg, RegistrationRequest, TransferType, Unregistration},
     32     },
     33     bench::{Bench, h32, h64},
     34     encoding::hex,
     35     error_code::ErrorCode,
     36     types::{
     37         amount::{Amount, Currency},
     38         time::TalerTimestamp,
     39     },
     40 };
     41 use taler_macros::db_test;
     42 use taler_test_utils::server::TestServer as _;
     43 
     44 use crate::{
     45     api::test::api_setup,
     46     test::{
     47         CLIENT, EXCHANGE, incomplete_out, malformed_completeted_in, malformed_in,
     48         malformed_incomplete_in, malformed_out, prepared_completeted_in, prepared_in,
     49         prepared_incomplete_in, talerable_completeted_in, talerable_in, talerable_incomplete_in,
     50         talerable_out,
     51     },
     52 };
     53 
     54 #[db_test]
     55 pub async fn bench_db(db: PgPool) {
     56     let server = api_setup(&db).await;
     57     let mut b = Bench::new(&db);
     58     let accounts_pubs: Vec<_> = (0..b.amount * 2)
     59         .map(|_| {
     60             let key_pair = Ed25519KeyPair::generate().unwrap();
     61             let pub_key = EddsaPublicKey::try_from(key_pair.public_key().as_ref()).unwrap();
     62             (key_pair, pub_key)
     63         })
     64         .collect();
     65 
     66     let uri = CLIENT.as_uri();
     67     b.table("incoming_transactions(amount, subject, execution_time, debit_payto, uetr, tx_id, acct_svcr_ref)", |f, i| {
     68         let subject = if i % 4 == 0 { CompactString::const_new("\\N")} else {format_compact!("subject {i}")};
     69 
     70         if i % 3 == 0 {
     71             writeln!(f, "(20,0)\t{subject}\t0\t{uri}\t{}\t\\N\t\\N", uuid::Uuid::new_v4())?;
     72             writeln!(f, "(21,0)\t{subject}\t0\t{uri}\t\\N\tTX_ID{}\t\\N", i*2)?;
     73             writeln!(f, "(22,0)\t{subject}\t0\t{uri}\t\\N\t\\N\tREF{}", i*2)
     74         } else if i%3 == 1 {
     75             writeln!(f, "(30,0)\t{subject}\t0\t{uri}\t{}\tTX_ID{}\t\\N", uuid::Uuid::new_v4(), i*2)?;
     76             writeln!(f, "(31,0)\t{subject}\t0\t{uri}\t\\N\tTX_ID{}\tREF{}", i*2+1, i*2)?;
     77             writeln!(f, "(32,0)\t{subject}\t0\t{uri}\t{}\t\\N\tREF{}", uuid::Uuid::new_v4(), i*2+1)
     78         } else {
     79             writeln!(f, "(40,0)\t{subject}\t0\t{uri}\t{}\tTX_ID{}\tREF{}", uuid::Uuid::new_v4(), i*2, i*2)?;
     80             writeln!(f, "(40,0)\t{subject}\t0\t{uri}\t{}\tTX_ID{}\tREF{}", uuid::Uuid::new_v4(), i*2+1, i*2+1)
     81         }
     82     }).await;
     83     b.table("outgoing_transactions(amount, subject, execution_time, credit_payto, end_to_end_id, acct_svcr_ref)", |f, i| {
     84         let subject = if i % 4 == 0 { CompactString::const_new("\\N")} else {format_compact!("subject {i}")};
     85 
     86 
     87         if i % 2 == 0 {
     88             writeln!(f, "(40,0)\t{subject}\t0\t{uri}\t\\N\tREF{}", i*2)?;
     89             writeln!(f, "(41,0)\t{subject}\t0\t{uri}\tE2E_ID{}\t\\N", i*2)
     90         } else {
     91             writeln!(f, "(40,0)\t{subject}\t0\t{uri}\tE2E_ID{}\tREF{}", i*2, i*2)?;
     92             writeln!(f, "(41,0)\t{subject}\t0\t{uri}\tE2E_ID{}\tREF{}", i*2+1, i*2+1)
     93         }
     94     }).await;
     95     b.table("initiated_outgoing_transactions(amount, subject, initiation_time, credit_payto, outgoing_transaction_id, end_to_end_id)", |f, i| {
     96         writeln!(f, "(42,0)\tsubject\t0\t{uri}\t{}\tE2E_ID{i}", i*2)
     97     }).await;
     98     b.table("prepared_transfers(type, account_pub, authorization_pub, authorization_sig, recurrent, reference_number, registered_at, incoming_transaction_id)", |f, i| {
     99         let ty = if i%2==0 {"reserve"} else {"kyc"};
    100         let recurrent = if i%3 == 0 {"true" } else {"false"};
    101         let incoming_transaction_id = if i % 5 == 0 { CompactString::const_new("\\N") }else {format_compact!("{}", i*2)};
    102 
    103         let reference_number = subject_fmt_qr_bill(accounts_pubs[i].1.as_ref());
    104         let key = hex::encode( accounts_pubs[i].1.as_ref());
    105         let sig =  h64();
    106         writeln!(f, "{ty}\t\\\\x{key}\t\\\\x{key}\t\\\\x{sig}\t{recurrent}\t{reference_number}\t0\t{incoming_transaction_id}")
    107     }).await;
    108     b.table(
    109         "pending_recurrent_incoming_transactions(incoming_transaction_id, authorization_pub)",
    110         |f, i| {
    111             let key = hex::encode(accounts_pubs[i].1.as_ref());
    112             writeln!(f, "{}\t\\\\x{key}", i * 2)
    113         },
    114     )
    115     .await;
    116     b.table(
    117         "bounced_transactions(incoming_transaction_id, initiated_outgoing_transaction_id)",
    118         |f, i| {
    119             if i % 10 == 0 {
    120                 writeln!(f, "{}\t{}", i / 2, i / 2)
    121             } else {
    122                 Ok(())
    123             }
    124         },
    125     )
    126     .await;
    127     b.table(
    128         "talerable_incoming_transactions(type, metadata, incoming_transaction_id)",
    129         |f, i| {
    130             let hex = h32();
    131             let ty = if i % 2 == 0 { "reserve" } else { "kyc" };
    132             writeln!(f, "{ty}\t\\\\x{hex}\t{}", i * 2)
    133         },
    134     )
    135     .await;
    136     b.table(
    137         "talerable_outgoing_transactions(wtid, exchange_base_url, outgoing_transaction_id)",
    138         |f, i| {
    139             let hex = h32();
    140             writeln!(f, "\\\\x{hex}\thttp://exchange.example.com/\t{}", i * 2 - 1)
    141         },
    142     )
    143     .await;
    144     b.table("transfer_operations(initiated_outgoing_transaction_id, request_uid, wtid, exchange_base_url)", |f, i| {
    145                 let h32 = h32();
    146                 let h64 = h64();
    147                 writeln!(f, "{i}\t\\\\x{h64}\t\\\\x{h32}\turl")
    148             }).await;
    149 
    150     // Warm HTTP client
    151     server.get("/taler-revenue/config").await.assert_ok();
    152 
    153     // Register
    154     b.measure("register_in", |_| malformed_in(&db)).await;
    155     b.measure("register_incomplete_in", |_| malformed_incomplete_in(&db))
    156         .await;
    157     b.measure("register_completed_in", |_| malformed_completeted_in(&db))
    158         .await;
    159     b.measure("register_talerable_in", |_| talerable_in(&db))
    160         .await;
    161     b.measure("register_talerable_incomplete_in", |_| {
    162         talerable_incomplete_in(&db)
    163     })
    164     .await;
    165     b.measure("register_talerable_completed_in", |_| {
    166         talerable_completeted_in(&db)
    167     })
    168     .await;
    169     b.measure("register_prepared_in", |_| prepared_in(&db))
    170         .await;
    171     b.measure("register_prepared_incomplete_in", |_| {
    172         prepared_incomplete_in(&db)
    173     })
    174     .await;
    175     b.measure("register_prepared_completed_in", |_| {
    176         prepared_completeted_in(&db)
    177     })
    178     .await;
    179     b.measure("register_out", |_| malformed_out(&db)).await;
    180     b.measure("register_talerable_out", |_| talerable_out(&db))
    181         .await;
    182     b.measure("register_incomplete_out", |_| incomplete_out(&db))
    183         .await;
    184 
    185     // Revenue api
    186     b.measure("transaction_revenue", async |_| {
    187         server.get("/taler-revenue/history").await.assert_ok()
    188     })
    189     .await;
    190 
    191     // Wire gateway
    192     b.measure("wg_transfer", async |_| {
    193         server
    194             .post("/taler-wire-gateway/transfer")
    195             .json(json!({
    196                 "request_uid": HashCode::rand(),
    197                 "amount": "KUDOS:0.0001",
    198                 "exchange_base_url": "http://exchange.example.com/",
    199                 "wtid": ShortHashCode::rand(),
    200                 "credit_account": *CLIENT
    201             }))
    202             .await
    203             .assert_ok()
    204     })
    205     .await;
    206     b.measure("wg_transfer_get", async |i| {
    207         server
    208             .get(format!("/taler-wire-gateway/transfers/{}", i + 1))
    209             .await
    210             .assert_ok()
    211     })
    212     .await;
    213     b.measure("wg_transfer_page", async |_| {
    214         server
    215             .get("/taler-wire-gateway/transfers")
    216             .await
    217             .assert_ok()
    218     })
    219     .await;
    220     b.measure("wg_transfer_page_filter", async |_| {
    221         server
    222             .get("/taler-wire-gateway/transfers?status=success")
    223             .await
    224             .assert_no_content()
    225     })
    226     .await;
    227     b.measure("wg_add", async |_| {
    228         server
    229             .post("/taler-wire-gateway/admin/add-incoming")
    230             .json(json!({
    231                 "amount": "KUDOS:0.0001",
    232                 "reserve_pub": EddsaPublicKey::rand(),
    233                 "debit_account": *CLIENT
    234             }))
    235             .await
    236             .assert_ok()
    237     })
    238     .await;
    239     b.measure("wg_incoming", async |_| {
    240         server
    241             .get("/taler-wire-gateway/history/incoming")
    242             .await
    243             .assert_ok()
    244     })
    245     .await;
    246     b.measure("wg_outgoing", async |_| {
    247         server
    248             .get("/taler-wire-gateway/history/outgoing")
    249             .await
    250             .assert_ok()
    251     })
    252     .await;
    253 
    254     // Wire transfer
    255     b.measure("wt_register", async |i| {
    256         let (pair, key) = &accounts_pubs[i];
    257         let req = RegistrationRequest {
    258             credit_account: EXCHANGE.as_uri(),
    259             r#type: TransferType::reserve,
    260             recurrent: false,
    261             credit_amount: Amount::new(&Currency::KUDOS, 55, 0),
    262             alg: PublicKeyAlg::EdDSA,
    263             account_pub: *key,
    264             authorization_pub: *key,
    265             authorization_sig: EddsaSignature::ZEROED,
    266         }
    267         .signed(pair);
    268         for _ in 0..2 {
    269             server
    270                 .post("/taler-prepared-transfer/registration")
    271                 .json(&req)
    272                 .await
    273                 .assert_ok()
    274         }
    275     })
    276     .await;
    277     b.measure("wt_unregister", async |i| {
    278         let (pair, key) = &accounts_pubs[i];
    279         let req = Unregistration {
    280             timestamp: TalerTimestamp::Timestamp(Timestamp::now()),
    281             authorization_pub: *key,
    282             authorization_sig: EddsaSignature::ZEROED,
    283         }
    284         .signed(pair);
    285         server
    286             .post("/taler-prepared-transfer/unregistration")
    287             .json(&req)
    288             .await
    289             .assert_no_content();
    290         server
    291             .post("/taler-prepared-transfer/unregistration")
    292             .json(&req)
    293             .await
    294             .assert_error(ErrorCode::BANK_TRANSACTION_NOT_FOUND);
    295     })
    296     .await;
    297 }