libeufin

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

bench.kt (10325B)


      1 /*
      2  * This file is part of LibEuFin.
      3  * Copyright (C) 2024, 2025, 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 import org.junit.Test
     21 import org.postgresql.jdbc.PgConnection
     22 import io.ktor.client.request.*
     23 import tech.libeufin.common.*
     24 import tech.libeufin.common.crypto.CryptoUtil
     25 import tech.libeufin.common.test.*
     26 import tech.libeufin.nexus.*
     27 import tech.libeufin.nexus.cli.*
     28 import kotlin.math.max
     29 import java.util.UUID;
     30 import java.time.Instant
     31 
     32 class Bench {
     33 
     34     /** Generate [amount] rows to fill the database */
     35     fun genData(conn: PgConnection, amount: Int) {
     36         val amount = max(amount, 10)
     37         val token32 = ByteArray(32)
     38         val token64 = ByteArray(64)
     39 
     40         val accountPubs = List(amount*2) { EddsaPublicKey.randEdsaKey() } 
     41 
     42         conn.genData(amount, sequenceOf(
     43             "incoming_transactions(amount, subject, execution_time, debit_payto, uetr, tx_id, acct_svcr_ref)" to {
     44                 val subject = if (it % 4 == 0) null else "subject ${it}"
     45                 val debtor = if (it % 3 == 0) null else "debit_payto"
     46                 if (it % 3 == 0) {
     47                     "(20,0)\t$subject\t0\t$debtor\t${UUID.randomUUID()}\t\\N\t\\N\n" + 
     48                     "(21,0)\t$subject\t0\t$debtor\t\\N\tTX_ID${it*2}\t\\N\n" + 
     49                     "(22,0)\t$subject\t0\t$debtor\t\\N\t\\N\tREF${it*2}\n"
     50                 } else if (it % 3 == 1) {
     51                     "(30,0)\t$subject\t0\t$debtor\t${UUID.randomUUID()}\tTX_ID${it*2}\t\\N\n" + 
     52                     "(31,0)\t$subject\t0\t$debtor\t\\N\tTX_ID${it*2+1}\tREF${it*2}\n" +
     53                     "(32,0)\t$subject\t0\t$debtor\t${UUID.randomUUID()}\t\\N\tREF${it*2+1}\n"
     54                 } else {
     55                     "(40,0)\t$subject\t0\t$debtor\t${UUID.randomUUID()}\tTX_ID${it*2}\tREF${it*2}\n" +
     56                     "(41,0)\t$subject\t0\t$debtor\t${UUID.randomUUID()}\tTX_ID${it*2+1}\tREF${it*2+1}\n" 
     57                 }
     58             },
     59             "outgoing_transactions(amount, subject, execution_time, credit_payto, end_to_end_id, acct_svcr_ref)" to {
     60                 val subject = if (it % 4 == 0) null else "subject ${it}"
     61                 val creditor = if (it % 3 == 0) null else "credit_payto"
     62                 if (it % 2 == 0) {
     63                     "(30,0)\t$subject\t0\t$creditor\t\\N\tREF${it*2}\n" + 
     64                     "(31,0)\t$subject\t0\t$creditor\tE2E_ID${it*2}\t\\N\n"
     65                 } else {
     66                     "(30,0)\t$subject\t0\t$creditor\tE2E_ID${it*2}\tREF${it*2}\n" + 
     67                     "(31,0)\t$subject\t0\t$creditor\tE2E_ID${it*2+1}\tREF${it*2+1}\n"
     68                 }
     69             },
     70             "initiated_outgoing_transactions(amount, subject, initiation_time, credit_payto, outgoing_transaction_id, end_to_end_id)" to {
     71                 "(42,0)\tsubject\t0\tcredit_payto\t${it*2}\tE2E_ID$it\n"
     72             },
     73             "prepared_transfers(type, account_pub, authorization_pub, authorization_sig, recurrent, reference_number, registered_at, incoming_transaction_id)" to {
     74                 val type = if (it % 2 == 0) "reserve" else "kyc"
     75                 val recurrent = if (it % 3 == 0) "true" else "false"
     76                 val incoming_transaction_id = if (it % 5 == 0) "\\N" else "${it*2}"
     77                 val reference_number = subjectFmtQrBill(accountPubs[it])
     78                 val pub = accountPubs[it].raw.encodeHex()
     79                 val sig = token64.rand().encodeHex()
     80                 "$type\t\\\\x$pub\t\\\\x$pub\t\\\\x$sig\t$recurrent\t$reference_number\t0\t$incoming_transaction_id\n"
     81             },
     82             "pending_recurrent_incoming_transactions(incoming_transaction_id, authorization_pub)" to {
     83                 val hex = accountPubs[it].raw.encodeHex()
     84                 "${it*2}\t\\\\x$hex\n"
     85             },
     86             "bounced_transactions(incoming_transaction_id, initiated_outgoing_transaction_id)" to {
     87                 if (it % 10 == 0) {
     88                     "${it/2}\t${it/3}\n"
     89                 } else {
     90                     ""
     91                 }
     92             },
     93             "talerable_incoming_transactions(type, metadata, incoming_transaction_id)" to {
     94                 val hex = token32.rand().encodeHex()
     95                 if (it % 2 == 0) {
     96                     "reserve\t\\\\x$hex\t${it*2}\n"
     97                 } else {
     98                     "kyc\t\\\\x$hex\t${it*2}\n"
     99                 }
    100             },
    101             "talerable_outgoing_transactions(wtid, exchange_base_url, outgoing_transaction_id)" to {
    102                 val hex = token32.rand().encodeHex()
    103                 "\\\\x$hex\turl\t${it*2-1}\n"
    104             },
    105             "transfer_operations(initiated_outgoing_transaction_id, request_uid, wtid, exchange_base_url)" to {
    106                 val hex32 = token32.rand().encodeHex()
    107                 val hex64 = token64.rand().encodeHex()
    108                 "$it\t\\\\x$hex64\t\\\\x$hex32\turl\n"
    109             }
    110         ))
    111     }
    112 
    113     @Test
    114     fun benchDb() {
    115         val ingestCfg = NexusIngestConfig.default(AccountType.exchange)
    116 
    117         bench { AMOUNT -> serverSetup { db ->
    118             // Generate data
    119             db.conn { genData(it, AMOUNT) }
    120 
    121             val accountPubs = List(AMOUNT) { EddsaPublicKey.randEdsaKeyPair() } 
    122 
    123             // Warm HTTP client
    124             client.getA("/taler-revenue/config").assertOk()
    125             
    126             // Register
    127             measureAction("register_in") {
    128                 registerIn(db)
    129             }
    130             measureAction("register_incomplete_in") {
    131                 registerIncompleteIn(db)
    132             }
    133             measureAction("register_completed_in") {
    134                 registerCompletedIn(db)
    135             }
    136             measureAction("register_out") {
    137                 registerOut(db)
    138             }
    139             measureAction("register_incomplete_out") {
    140                 registerIncompleteOut(db)
    141             }
    142             measureAction("register_reserve") {
    143                 talerableIn(db)
    144             }
    145             measureAction("register_prepared_reserve") {
    146                 talerablePreparedIn(db)
    147             }
    148             measureAction("register_kyc") {
    149                 talerableKycIn(db)
    150             }
    151 
    152             // Revenue API
    153             measureAction("transaction_revenue") {
    154                 client.getA("/taler-revenue/history").assertOk()
    155             }
    156 
    157             // Wire gateway
    158             measureAction("wg_transfer") {
    159                 client.postA("/taler-wire-gateway/transfer") {
    160                     json { 
    161                         "request_uid" to HashCode.rand()
    162                         "amount" to "CHF:0.0001"
    163                         "exchange_base_url" to "http://exchange.example.com/"
    164                         "wtid" to ShortHashCode.rand()
    165                         "credit_account" to grothoffPayto
    166                     }
    167                 }.assertOk()
    168             }
    169             measureAction("wg_transfer_get") {
    170                 client.getA("/taler-wire-gateway/transfers/42").assertOk()
    171             }
    172             measureAction("wg_transfer_page") {
    173                 client.getA("/taler-wire-gateway/transfers").assertOk()
    174             }
    175             measureAction("wg_transfer_page_filter") {
    176                 client.getA("/taler-wire-gateway/transfers?status=success").assertNoContent()
    177             }
    178             measureAction("wg_add") {
    179                 client.postA("/taler-wire-gateway/admin/add-incoming") {
    180                     json { 
    181                         "amount" to "CHF:0.0001"
    182                         "reserve_pub" to EddsaPublicKey.randEdsaKey()
    183                         "debit_account" to grothoffPayto
    184                     }
    185                 }.assertOk()
    186             }
    187             measureAction("wg_incoming") {
    188                 client.getA("/taler-wire-gateway/history/incoming")
    189                     .assertOk()
    190             }
    191             measureAction("wg_outgoing") {
    192                 client.getA("/taler-wire-gateway/history/outgoing")
    193                     .assertOk()
    194             }
    195 
    196             // Wire transfer
    197             measureAction("wt_register") {
    198                 val (priv, pub) = accountPubs[it]
    199                 val valid_req = obj {
    200                     "credit_amount" to "KUDOS:55"
    201                     "type" to "reserve"
    202                     "alg" to "ECDSA"
    203                     "account_pub" to pub
    204                     "authorization_pub" to pub
    205                     "authorization_sig" to CryptoUtil.eddsaSign(pub.raw, priv)
    206                     "recurrent" to false
    207                 }
    208                 client.post("/taler-wire-transfer-gateway/registration") {
    209                     json(valid_req)
    210                 }.assertOkJson<SubjectResult>()
    211                 client.post("/taler-wire-transfer-gateway/registration") {
    212                     json(valid_req)
    213                 }.assertOkJson<SubjectResult>()
    214             }
    215             measureAction("wt_unregister") {
    216                 val (priv, pub) = accountPubs[it]
    217                 val now = Instant.now().toString()
    218                 val valid_req = obj {
    219                     "timestamp" to now
    220                     "authorization_pub" to pub
    221                     "authorization_sig" to CryptoUtil.eddsaSign(now.toByteArray(), priv)
    222                 }
    223                 client.delete("/taler-wire-transfer-gateway/registration") {
    224                     json(valid_req)
    225                 }.assertNoContent()
    226                 client.delete("/taler-wire-transfer-gateway/registration") {
    227                     json(valid_req)
    228                 }.assertNotFound(TalerErrorCode.BANK_TRANSACTION_NOT_FOUND)
    229             }
    230 
    231             // Observability
    232             /*measureAction("metrics") {
    233                 client.get("/taler-observability/metrics")
    234                     .assertOk()
    235             }*/
    236         }}
    237     }
    238 }