libeufin

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

commit 7477ca4281127d9aea06418df72c43fe9f43cc2c
parent fae2a018d0de3cc92e01ded53f2ac462999980de
Author: Antoine A <>
Date:   Wed,  8 Nov 2023 00:50:25 +0000

Improve monitor

Diffstat:
Mbank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt | 3++-
Mbank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt | 18++++++++++++------
Mbank/src/main/kotlin/tech/libeufin/bank/db/Database.kt | 56+++++++++++++++++++++++++++++++++++---------------------
Mbank/src/test/kotlin/CoreBankApiTest.kt | 20--------------------
Mbank/src/test/kotlin/StatsTest.kt | 119++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Mbank/src/test/kotlin/WireGatewayApiTest.kt | 42++++++------------------------------------
Mbank/src/test/kotlin/helpers.kt | 42++++++++++++++++++++++++++++++++++++++++++
Mdatabase-versioning/libeufin-bank-0001.sql | 36++++++++++++++++++++----------------
Mdatabase-versioning/libeufin-bank-procedures.sql | 107++++++++++++++++++++++++++++++++++++++++++-------------------------------------
9 files changed, 253 insertions(+), 190 deletions(-)

diff --git a/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt @@ -37,6 +37,7 @@ import org.slf4j.LoggerFactory import tech.libeufin.util.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import kotlinx.coroutines.future.await private val logger: Logger = LoggerFactory.getLogger("tech.libeufin.bank.accountsMgmtHandlers") @@ -527,7 +528,7 @@ private fun Routing.coreBankCashoutApi(db: Database, ctx: BankConfig) { val process = ProcessBuilder(tanScript, res.tanInfo).start() try { process.outputWriter().use { it.write(res.tanCode) } - process.waitFor(10, TimeUnit.MINUTES) + process.onExit().await() } catch (e: Exception) { process.destroy() } diff --git a/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt b/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt @@ -118,15 +118,19 @@ data class TokenRequest( @Serializable sealed class MonitorResponse { - abstract val talerPayoutCount: Long - abstract val talerPayoutInternalVolume: TalerAmount + abstract val talerInCount: Long + abstract val talerInInternalVolume: TalerAmount + abstract val talerOutCount: Long + abstract val talerOutInternalVolume: TalerAmount } @Serializable @SerialName("just-payouts") data class MonitorJustPayouts( - override val talerPayoutCount: Long, - override val talerPayoutInternalVolume: TalerAmount + override val talerInCount: Long, + override val talerInInternalVolume: TalerAmount, + override val talerOutCount: Long, + override val talerOutInternalVolume: TalerAmount ) : MonitorResponse() @Serializable @@ -136,8 +140,10 @@ data class MonitorWithCashout( val cashinExternalVolume: TalerAmount, val cashoutCount: Long, val cashoutExternalVolume: TalerAmount, - override val talerPayoutCount: Long, - override val talerPayoutInternalVolume: TalerAmount + override val talerInCount: Long, + override val talerInInternalVolume: TalerAmount, + override val talerOutCount: Long, + override val talerOutInternalVolume: TalerAmount ) : MonitorResponse() /** diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/Database.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/Database.kt @@ -621,10 +621,9 @@ class Database(dbConfig: String, internal val bankCurrency: String, internal val if (it.getBoolean("out_creditor_is_exchange")) { val rowId = it.getLong("out_credit_row_id") if (metadata is IncomingTxMetadata) { - val stmt = conn.prepareStatement("CALL register_incoming(?, ?, ?)") + val stmt = conn.prepareStatement("CALL register_incoming(?, ?)") stmt.setBytes(1, metadata.reservePub.raw) stmt.setLong(2, rowId) - stmt.setLong(3, creditorAccountId) stmt.executeUpdate() } else { // TODO bounce @@ -828,14 +827,17 @@ class Database(dbConfig: String, internal val bankCurrency: String, internal val val stmt = conn.prepareStatement(""" SELECT cashin_count - ,(cashin_volume_in_fiat).val as cashin_volume_in_fiat_val - ,(cashin_volume_in_fiat).frac as cashin_volume_in_fiat_frac + ,(cashin_volume).val as cashin_volume_val + ,(cashin_volume).frac as cashin_volume_frac ,cashout_count - ,(cashout_volume_in_fiat).val as cashout_volume_in_fiat_val - ,(cashout_volume_in_fiat).frac as cashout_volume_in_fiat_frac - ,internal_taler_payments_count - ,(internal_taler_payments_volume).val as internal_taler_payments_volume_val - ,(internal_taler_payments_volume).frac as internal_taler_payments_volume_frac + ,(cashout_volume).val as cashout_volume_val + ,(cashout_volume).frac as cashout_volume_frac + ,taler_in_count + ,(taler_in_volume).val as taler_in_volume_val + ,(taler_in_volume).frac as taler_in_volume_frac + ,taler_out_count + ,(taler_out_volume).val as taler_out_volume_val + ,(taler_out_volume).frac as taler_out_volume_frac FROM stats_get_frame(now()::timestamp, ?::stat_timeframe_enum, ?) """) stmt.setString(1, params.timeframe.name) @@ -849,28 +851,40 @@ class Database(dbConfig: String, internal val bankCurrency: String, internal val MonitorWithCashout( cashinCount = it.getLong("cashin_count"), cashinExternalVolume = TalerAmount( - value = it.getLong("cashin_volume_in_fiat_val"), - frac = it.getInt("cashin_volume_in_fiat_frac"), + value = it.getLong("cashin_volume_val"), + frac = it.getInt("cashin_volume_frac"), currency = this ), cashoutCount = it.getLong("cashout_count"), cashoutExternalVolume = TalerAmount( - value = it.getLong("cashout_volume_in_fiat_val"), - frac = it.getInt("cashout_volume_in_fiat_frac"), + value = it.getLong("cashout_volume_val"), + frac = it.getInt("cashout_volume_frac"), currency = this ), - talerPayoutCount = it.getLong("internal_taler_payments_count"), - talerPayoutInternalVolume = TalerAmount( - value = it.getLong("internal_taler_payments_volume_val"), - frac = it.getInt("internal_taler_payments_volume_frac"), + talerInCount = it.getLong("taler_in_count"), + talerInInternalVolume = TalerAmount( + value = it.getLong("taler_in_volume_val"), + frac = it.getInt("taler_in_volume_frac"), + currency = bankCurrency + ), + talerOutCount = it.getLong("taler_out_count"), + talerOutInternalVolume = TalerAmount( + value = it.getLong("taler_out_volume_val"), + frac = it.getInt("taler_out_volume_frac"), currency = bankCurrency ) ) } ?: MonitorJustPayouts( - talerPayoutCount = it.getLong("internal_taler_payments_count"), - talerPayoutInternalVolume = TalerAmount( - value = it.getLong("internal_taler_payments_volume_val"), - frac = it.getInt("internal_taler_payments_volume_frac"), + talerInCount = it.getLong("taler_in_count"), + talerInInternalVolume = TalerAmount( + value = it.getLong("taler_in_volume_val"), + frac = it.getInt("taler_in_volume_frac"), + currency = bankCurrency + ), + talerOutCount = it.getLong("taler_out_count"), + talerOutInternalVolume = TalerAmount( + value = it.getLong("taler_out_volume_val"), + frac = it.getInt("taler_out_volume_frac"), currency = bankCurrency ) ) diff --git a/bank/src/test/kotlin/CoreBankApiTest.kt b/bank/src/test/kotlin/CoreBankApiTest.kt @@ -1030,26 +1030,6 @@ class CoreBankWithdrawalApiTest { class CoreBankCashoutApiTest { - suspend fun smsCode(info: String): String? { - val file = File("/tmp/tan-$info.txt"); - if (file.exists()) { - val code = file.readText() - file.delete() - return code; - } else { - return null - } - } - - - private suspend fun ApplicationTestBuilder.convert(amount: String): TalerAmount { - // Check conversion - client.get("/cashout-rate?amount_debit=$amount").assertOk().run { - val resp = json<ConversionResponse>() - return resp.amount_credit - } - } - // POST /accounts/{USERNAME}/cashouts @Test fun create() = bankSetup { _ -> diff --git a/bank/src/test/kotlin/StatsTest.kt b/bank/src/test/kotlin/StatsTest.kt @@ -25,6 +25,7 @@ import io.ktor.server.testing.* import java.time.* import java.util.* import kotlin.test.* +import kotlin.reflect.full.declaredMemberProperties import kotlinx.serialization.json.Json import org.junit.Test import tech.libeufin.bank.* @@ -34,37 +35,79 @@ class StatsTest { @Test fun transfer() = bankSetup { _ -> setMaxDebt("exchange", TalerAmount("KUDOS:1000")) - - suspend fun transfer(amount: TalerAmount) { - client.post("/accounts/exchange/taler-wire-gateway/transfer") { - basicAuth("exchange", "exchange-password") - jsonBody { - "request_uid" to randHashCode() - "amount" to amount - "exchange_base_url" to "http://exchange.example.com/" - "wtid" to randShortHashCode() - "credit_account" to "payto://iban/MERCHANT-IBAN-XYZ" + setMaxDebt("customer", TalerAmount("KUDOS:1000")) + client.patch("/accounts/customer") { + basicAuth("customer", "customer-password") + jsonBody(json { + "cashout_payto_uri" to IbanPayTo(genIbanPaytoUri()) + "challenge_contact_data" to json { + "phone" to "+99" } - }.assertOk() + }) + }.assertNoContent() + + suspend fun cashout(amount: String) { + client.post("/accounts/customer/cashouts") { + basicAuth("customer", "customer-password") + jsonBody(json { + "request_uid" to randShortHashCode() + "amount_debit" to amount + "amount_credit" to convert(amount) + }) + }.assertOk().run { + val uuid = json<CashoutPending>().cashout_id + client.post("/accounts/customer/cashouts/$uuid/confirm") { + basicAuth("customer", "customer-password") + jsonBody { "tan" to smsCode("+99") } + }.assertNoContent() + } } - suspend fun monitor(count: Long, amount: TalerAmount) { + suspend fun monitor(countName: String, volumeName: String, count: Long, amount: String) { Timeframe.entries.forEach { timestamp -> client.get("/monitor?timestamp=${timestamp.name}") { basicAuth("admin", "admin-password") }.assertOk().run { val resp = json<MonitorResponse>() - assertEquals(count, resp.talerPayoutCount) - assertEquals(amount, resp.talerPayoutInternalVolume) + assertEquals(count, resp.javaClass.kotlin.declaredMemberProperties.first { it.name == countName }.get(resp)) + assertEquals(TalerAmount(amount), resp.javaClass.kotlin.declaredMemberProperties.first { it.name == volumeName }.get(resp)) } } } - monitor(0, TalerAmount("KUDOS:0")) - transfer(TalerAmount("KUDOS:10.0")) - monitor(1, TalerAmount("KUDOS:10.0")) - transfer(TalerAmount("KUDOS:30.5")) - monitor(2, TalerAmount("KUDOS:40.5")) - transfer(TalerAmount("KUDOS:42")) - monitor(3, TalerAmount("KUDOS:82.5")) + suspend fun monitorTalerOut(count: Long, amount: String) = monitor("talerOutCount" , "talerOutInternalVolume", count, amount) + suspend fun monitorTalerIn(count: Long, amount: String) = monitor("talerInCount" , "talerInInternalVolume", count, amount) + suspend fun monitorCashin(count: Long, amount: String) = monitor("cashinCount" , "cashinExternalVolume", count, amount) + suspend fun monitorCashout(count: Long, amount: String) = monitor("cashoutCount" , "cashoutExternalVolume", count, amount) + + monitorTalerOut(0, "KUDOS:0") + monitorTalerIn(0, "KUDOS:0") + monitorCashin(0, "FIAT:0") + monitorCashout(0, "FIAT:0") + + transfer("KUDOS:10.0") + monitorTalerOut(1, "KUDOS:10.0") + transfer("KUDOS:30.5") + monitorTalerOut(2, "KUDOS:40.5") + transfer("KUDOS:42") + monitorTalerOut(3, "KUDOS:82.5") + + addIncoming("KUDOS:3") + monitorTalerIn(1, "KUDOS:3") + addIncoming("KUDOS:7.6") + monitorTalerIn(2, "KUDOS:10.6") + addIncoming("KUDOS:12.3") + monitorTalerIn(3, "KUDOS:22.9") + + cashout("KUDOS:3") + monitorCashout(1, "FIAT:3.747") + cashout("KUDOS:7.6") + monitorCashout(2, "FIAT:13.244") + cashout("KUDOS:12.3") + monitorCashout(3, "FIAT:28.616") + + monitorTalerOut(3, "KUDOS:82.5") + monitorTalerIn(3, "KUDOS:22.9") + monitorCashin(0, "FIAT:0") + monitorCashout(3, "FIAT:28.616") } @Test @@ -73,7 +116,7 @@ class StatsTest { suspend fun register(now: OffsetDateTime, amount: TalerAmount) { val stmt = conn.prepareStatement( - "CALL stats_register_internal_taler_payment(?::timestamp, (?, ?)::taler_amount)" + "CALL stats_register_payment('taler_out', ?::timestamp, (?, ?)::taler_amount)" ) stmt.setObject(1, now) stmt.setLong(2, amount.value) @@ -88,17 +131,14 @@ class StatsTest { count: Long, amount: TalerAmount ) { - val stmt = conn.prepareStatement( - """ + val stmt = conn.prepareStatement(""" SELECT - internal_taler_payments_count - ,(internal_taler_payments_volume).val as internal_taler_payments_volume_val - ,(internal_taler_payments_volume).frac as internal_taler_payments_volume_frac + taler_out_count + ,(taler_out_volume).val as taler_out_volume_val + ,(taler_out_volume).frac as taler_out_volume_frac FROM stats_get_frame(?::timestamp, ?::stat_timeframe_enum, ?) - """ - ) + """) stmt.setObject(1, now) - stmt.setString(2, timeframe.name) if (which != null) { stmt.setInt(3, which) @@ -106,16 +146,15 @@ class StatsTest { stmt.setNull(3, java.sql.Types.INTEGER) } stmt.oneOrNull { - val talerPayoutCount = it.getLong("internal_taler_payments_count") - val talerPayoutInternalVolume = - TalerAmount( - value = it.getLong("internal_taler_payments_volume_val"), - frac = it.getInt("internal_taler_payments_volume_frac"), - currency = "KUDOS" - ) - println("$timeframe $talerPayoutCount $talerPayoutInternalVolume") - assertEquals(count, talerPayoutCount) - assertEquals(amount, talerPayoutInternalVolume) + val talerOutCount = it.getLong("taler_out_count") + val talerOutInternalVolume = TalerAmount( + value = it.getLong("taler_out_volume_val"), + frac = it.getInt("taler_out_volume_frac"), + currency = "KUDOS" + ) + println("$timeframe $talerOutCount $talerOutInternalVolume") + assertEquals(count, talerOutCount) + assertEquals(amount, talerOutInternalVolume) }!! } diff --git a/bank/src/test/kotlin/WireGatewayApiTest.kt b/bank/src/test/kotlin/WireGatewayApiTest.kt @@ -17,36 +17,6 @@ import kotlin.test.assertNotNull import randHashCode class WireGatewayApiTest { - suspend fun Database.genTransfer(from: String, to: IbanPayTo, amount: String = "KUDOS:10") { - exchange.transfer( - req = TransferRequest( - request_uid = randHashCode(), - amount = TalerAmount(amount), - exchange_base_url = ExchangeUrl("http://exchange.example.com/"), - wtid = randShortHashCode(), - credit_account = to - ), - username = from, - timestamp = Instant.now() - ).run { - assertEquals(TalerTransferResult.SUCCESS, txResult) - } - } - - suspend fun Database.genIncoming(to: String, from: IbanPayTo) { - exchange.addIncoming( - req = AddIncomingRequest( - reserve_pub = randShortHashCode(), - amount = TalerAmount("KUDOS:10"), - debit_account = from, - ), - username = to, - timestamp = Instant.now() - ).run { - assertEquals(TalerAddIncomingResult.SUCCESS, txResult) - } - } - suspend fun Database.genTransaction(from: String, to: IbanPayTo, subject: String) { bankTransaction( creditAccountPayto = to, @@ -245,7 +215,7 @@ class WireGatewayApiTest { // Gen three transactions using clean add incoming logic repeat(3) { - db.genIncoming("exchange", IbanPayTo("payto://iban/MERCHANT-IBAN-XYZ")) + addIncoming("KUDOS:10") } // Should not show up in the taler wire gateway API history db.genTransaction("merchant", IbanPayTo("payto://iban/exchange-IBAN-XYZ"), "bogus") @@ -311,7 +281,7 @@ class WireGatewayApiTest { } } delay(200) - db.genIncoming("exchange", IbanPayTo("payto://iban/MERCHANT-IBAN-XYZ")) + addIncoming("KUDOS:10") } // Test trigger by raw transaction @@ -357,7 +327,7 @@ class WireGatewayApiTest { // Testing ranges. repeat(20) { - db.genIncoming("exchange", IbanPayTo("payto://iban/MERCHANT-IBAN-XYZ")) + addIncoming("KUDOS:10") } // forward range: @@ -414,7 +384,7 @@ class WireGatewayApiTest { // Gen three transactions using clean transfer logic repeat(3) { - db.genTransfer("exchange", IbanPayTo("payto://iban/MERCHANT-IBAN-XYZ")) + transfer("KUDOS:10") } // Should not show up in the taler wire gateway API history db.genTransaction("exchange", IbanPayTo("payto://iban/MERCHANT-IBAN-XYZ"), "bogus") @@ -465,12 +435,12 @@ class WireGatewayApiTest { } } delay(200) - db.genTransfer("exchange", IbanPayTo("payto://iban/MERCHANT-IBAN-XYZ")) + transfer("KUDOS:10") } // Testing ranges. repeat(20) { - db.genTransfer("exchange", IbanPayTo("payto://iban/MERCHANT-IBAN-XYZ")) + transfer("KUDOS:10") } // forward range: diff --git a/bank/src/test/kotlin/helpers.kt b/bank/src/test/kotlin/helpers.kt @@ -9,6 +9,7 @@ import net.taler.common.errorcodes.TalerErrorCode import kotlin.test.* import tech.libeufin.bank.* import java.io.ByteArrayOutputStream +import java.io.File import java.util.zip.DeflaterOutputStream import tech.libeufin.util.CryptoUtil import tech.libeufin.util.* @@ -102,6 +103,47 @@ suspend fun ApplicationTestBuilder.assertBalance(account: String, info: CreditDe } } +suspend fun ApplicationTestBuilder.transfer(amount: String) { + client.post("/accounts/exchange/taler-wire-gateway/transfer") { + basicAuth("exchange", "exchange-password") + jsonBody { + "request_uid" to randHashCode() + "amount" to TalerAmount(amount) + "exchange_base_url" to "http://exchange.example.com/" + "wtid" to randShortHashCode() + "credit_account" to "payto://iban/MERCHANT-IBAN-XYZ" + } + }.assertOk() +} + +suspend fun ApplicationTestBuilder.addIncoming(amount: String) { + client.post("/accounts/exchange/taler-wire-gateway/admin/add-incoming") { + basicAuth("admin", "admin-password") + jsonBody { + "amount" to TalerAmount(amount) + "reserve_pub" to randEddsaPublicKey() + "debit_account" to "payto://iban/MERCHANT-IBAN-XYZ" + } + }.assertOk() +} + +suspend fun ApplicationTestBuilder.convert(amount: String): TalerAmount { + client.get("/cashout-rate?amount_debit=$amount").assertOk().run { + return json<ConversionResponse>().amount_credit + } +} + +suspend fun smsCode(info: String): String? { + val file = File("/tmp/tan-$info.txt"); + if (file.exists()) { + val code = file.readText() + file.delete() + return code; + } else { + return null + } +} + /* ----- Assert ----- */ diff --git a/database-versioning/libeufin-bank-0001.sql b/database-versioning/libeufin-bank-0001.sql @@ -250,27 +250,31 @@ COMMENT ON COLUMN taler_withdrawal_operations.confirmation_done -- end of: Taler integration -- start of: Statistics -CREATE TABLE IF NOT EXISTS regional_stats ( +CREATE TABLE IF NOT EXISTS bank_stats ( timeframe stat_timeframe_enum NOT NULL ,start_time timestamp NOT NULL - ,cashin_count BIGINT NOT NULL - ,cashin_volume_in_fiat taler_amount NOT NULL - ,cashout_count BIGINT NOT NULL - ,cashout_volume_in_fiat taler_amount NOT NULL - ,internal_taler_payments_count BIGINT NOT NULL - ,internal_taler_payments_volume taler_amount NOT NULL + ,cashin_count BIGINT NOT NULL DEFAULT 0 + ,cashin_volume taler_amount NOT NULL DEFAULT (0, 0) + ,cashout_count BIGINT NOT NULL DEFAULT 0 + ,cashout_volume taler_amount NOT NULL DEFAULT (0, 0) + ,taler_in_count BIGINT NOT NULL DEFAULT 0 + ,taler_in_volume taler_amount NOT NULL DEFAULT (0, 0) + ,taler_out_count BIGINT NOT NULL DEFAULT 0 + ,taler_out_volume taler_amount NOT NULL DEFAULT (0, 0) ,PRIMARY KEY (start_time, timeframe) ); -- TODO garbage collection -COMMENT ON TABLE regional_stats IS 'Stores statistics about the regional currency usage.'; -COMMENT ON COLUMN regional_stats.timeframe IS 'particular timeframe that this row accounts for'; -COMMENT ON COLUMN regional_stats.start_time IS 'timestamp of the start of the timeframe that this row accounts for, truncated according to the precision of the timeframe'; -COMMENT ON COLUMN regional_stats.cashin_count IS 'how many cashin operations took place in the timeframe'; -COMMENT ON COLUMN regional_stats.cashin_volume_in_fiat IS 'how much fiat currency was cashed in in the timeframe'; -COMMENT ON COLUMN regional_stats.cashout_count IS 'how many cashout operations took place in the timeframe'; -COMMENT ON COLUMN regional_stats.cashout_volume_in_fiat IS 'how much fiat currency was payed by the bank to customers in the timeframe'; -COMMENT ON COLUMN regional_stats.internal_taler_payments_count IS 'how many internal payments were made by a Taler exchange'; -COMMENT ON COLUMN regional_stats.internal_taler_payments_volume IS 'how much internal currency was paid by a Taler exchange'; +COMMENT ON TABLE bank_stats IS 'Stores statistics about the bank usage.'; +COMMENT ON COLUMN bank_stats.timeframe IS 'particular timeframe that this row accounts for'; +COMMENT ON COLUMN bank_stats.start_time IS 'timestamp of the start of the timeframe that this row accounts for, truncated according to the precision of the timeframe'; +COMMENT ON COLUMN bank_stats.cashin_count IS 'how many cashin operations took place in the timeframe'; +COMMENT ON COLUMN bank_stats.cashin_volume IS 'how much fiat currency was cashed in in the timeframe'; +COMMENT ON COLUMN bank_stats.cashout_count IS 'how many cashout operations took place in the timeframe'; +COMMENT ON COLUMN bank_stats.cashout_volume IS 'how much fiat currency was payed by the bank to customers in the timeframe'; +COMMENT ON COLUMN bank_stats.taler_out_count IS 'how many internal payments were made by a Taler exchange'; +COMMENT ON COLUMN bank_stats.taler_out_volume IS 'how much internal currency was paid by a Taler exchange'; +COMMENT ON COLUMN bank_stats.taler_in_count IS 'how many internal payments were made to a Taler exchange'; +COMMENT ON COLUMN bank_stats.taler_in_volume IS 'how much internal currency was paid to a Taler exchange'; -- end of: Statistics diff --git a/database-versioning/libeufin-bank-procedures.sql b/database-versioning/libeufin-bank-procedures.sql @@ -265,7 +265,7 @@ DECLARE local_amount taler_amount; local_bank_account_id BIGINT; BEGIN --- Register outgoing transaction +-- register outgoing transaction INSERT INTO taler_exchange_outgoing ( request_uid, @@ -279,10 +279,11 @@ INSERT in_tx_row_id ); -- TODO check if not drain +-- update stats SELECT (amount).val, (amount).frac, bank_account_id INTO local_amount.val, local_amount.frac, local_bank_account_id FROM bank_account_transactions WHERE bank_transaction_id=in_tx_row_id; -CALL stats_register_internal_taler_payment(now()::TIMESTAMP, local_amount); +CALL stats_register_payment('taler_out', now()::TIMESTAMP, local_amount); -- notify new transaction PERFORM pg_notify('outgoing_tx', local_bank_account_id || ' ' || in_tx_row_id); END $$; @@ -291,10 +292,12 @@ COMMENT ON PROCEDURE register_outgoing CREATE OR REPLACE PROCEDURE register_incoming( IN in_reserve_pub BYTEA, - IN in_tx_row_id BIGINT, - IN in_exchange_bank_account_id BIGINT + IN in_tx_row_id BIGINT ) LANGUAGE plpgsql AS $$ +DECLARE +local_amount taler_amount; +local_bank_account_id BIGINT; BEGIN -- Register incoming transaction INSERT @@ -305,8 +308,13 @@ INSERT in_reserve_pub, in_tx_row_id ); +-- update stats +SELECT (amount).val, (amount).frac, bank_account_id +INTO local_amount.val, local_amount.frac, local_bank_account_id +FROM bank_account_transactions WHERE bank_transaction_id=in_tx_row_id; +CALL stats_register_payment('taler_in', now()::TIMESTAMP, local_amount); -- notify new transaction -PERFORM pg_notify('incoming_tx', in_exchange_bank_account_id || ' ' || in_tx_row_id); +PERFORM pg_notify('incoming_tx', local_bank_account_id || ' ' || in_tx_row_id); END $$; COMMENT ON PROCEDURE register_incoming IS 'Register a bank transaction as a taler incoming transaction'; @@ -484,7 +492,7 @@ IF out_debitor_balance_insufficient THEN RETURN; END IF; -- Register incoming transaction -CALL register_incoming(in_reserve_pub, out_tx_row_id, exchange_bank_account_id); +CALL register_incoming(in_reserve_pub, out_tx_row_id); END $$; -- TODO new comment COMMENT ON FUNCTION taler_add_incoming IS 'function that (1) inserts the TWG requests' @@ -741,7 +749,7 @@ UPDATE taler_withdrawal_operations WHERE withdrawal_uuid=in_withdrawal_uuid; -- Register incoming transaction -CALL register_incoming(reserve_pub_local, tx_row_id, exchange_bank_account_id); +CALL register_incoming(reserve_pub_local, tx_row_id); END $$; COMMENT ON FUNCTION confirm_taler_withdrawal IS 'Set a withdrawal operation as confirmed and wire the funds to the exchange.'; @@ -1188,6 +1196,9 @@ END IF; UPDATE cashout_operations SET local_transaction = tx_id WHERE cashout_uuid=in_cashout_uuid; + +-- update stats +CALL stats_register_payment('cashout', now()::TIMESTAMP, amount_credit_local); END $$; CREATE OR REPLACE FUNCTION challenge_create ( @@ -1282,11 +1293,13 @@ CREATE OR REPLACE FUNCTION stats_get_frame( IN in_timeframe stat_timeframe_enum, IN which INTEGER, OUT cashin_count BIGINT, - OUT cashin_volume_in_fiat taler_amount, + OUT cashin_volume taler_amount, OUT cashout_count BIGINT, - OUT cashout_volume_in_fiat taler_amount, - OUT internal_taler_payments_count BIGINT, - OUT internal_taler_payments_volume taler_amount + OUT cashout_volume taler_amount, + OUT taler_in_count BIGINT, + OUT taler_in_volume taler_amount, + OUT taler_out_count BIGINT, + OUT taler_out_volume taler_amount ) LANGUAGE plpgsql AS $$ DECLARE @@ -1301,61 +1314,55 @@ BEGIN END; SELECT s.cashin_count - ,(s.cashin_volume_in_fiat).val - ,(s.cashin_volume_in_fiat).frac + ,(s.cashin_volume).val + ,(s.cashin_volume).frac ,s.cashout_count - ,(s.cashout_volume_in_fiat).val - ,(s.cashout_volume_in_fiat).frac - ,s.internal_taler_payments_count - ,(s.internal_taler_payments_volume).val - ,(s.internal_taler_payments_volume).frac + ,(s.cashout_volume).val + ,(s.cashout_volume).frac + ,s.taler_in_count + ,(s.taler_in_volume).val + ,(s.taler_in_volume).frac + ,s.taler_out_count + ,(s.taler_out_volume).val + ,(s.taler_out_volume).frac INTO cashin_count - ,cashin_volume_in_fiat.val - ,cashin_volume_in_fiat.frac + ,cashin_volume.val + ,cashin_volume.frac ,cashout_count - ,cashout_volume_in_fiat.val - ,cashout_volume_in_fiat.frac - ,internal_taler_payments_count - ,internal_taler_payments_volume.val - ,internal_taler_payments_volume.frac - FROM regional_stats AS s + ,cashout_volume.val + ,cashout_volume.frac + ,taler_in_count + ,taler_in_volume.val + ,taler_in_volume.frac + ,taler_out_count + ,taler_out_volume.val + ,taler_out_volume.frac + FROM bank_stats AS s WHERE s.timeframe = in_timeframe AND s.start_time = local_start_time; END $$; -CREATE OR REPLACE PROCEDURE stats_register_internal_taler_payment( +CREATE OR REPLACE PROCEDURE stats_register_payment( + IN name TEXT, IN now TIMESTAMP, IN amount taler_amount ) LANGUAGE plpgsql AS $$ DECLARE frame stat_timeframe_enum; + query TEXT; BEGIN + query = format('INSERT INTO bank_stats AS s ' + '(timeframe, start_time, %1$I_count, %1$I_volume) ' + 'VALUES ($1, $2, 1, $3) ' + 'ON CONFLICT (timeframe, start_time) DO UPDATE ' + 'SET %1$I_count=s.%1$I_count+1, ' + ' %1$I_volume=(SELECT amount_add(s.%1$I_volume, $3))', + name); + FOREACH frame IN ARRAY enum_range(null::stat_timeframe_enum) LOOP - INSERT INTO regional_stats AS s ( - timeframe - ,start_time - ,cashin_count - ,cashin_volume_in_fiat - ,cashout_count - ,cashout_volume_in_fiat - ,internal_taler_payments_count - ,internal_taler_payments_volume - ) - VALUES ( - frame - ,date_trunc(frame::text, now) - ,0 - ,(0, 0)::taler_amount - ,0 - ,(0, 0)::taler_amount - ,1 - ,amount - ) - ON CONFLICT (timeframe, start_time) DO UPDATE - SET internal_taler_payments_count = s.internal_taler_payments_count+1 - ,internal_taler_payments_volume = (SELECT amount_add(s.internal_taler_payments_volume, amount)); + EXECUTE query USING frame, date_trunc(frame::text, now), amount; END LOOP; END $$;