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 }