From 5b56ad9ed803b3a1105e6333716dcfca7593a3f1 Mon Sep 17 00:00:00 2001 From: MS Date: Tue, 18 Jul 2023 13:49:55 +0200 Subject: Getting the balance in constant time. --- .../kotlin/tech/libeufin/sandbox/CircuitApi.kt | 9 +-- .../src/main/kotlin/tech/libeufin/sandbox/DB.kt | 5 +- .../tech/libeufin/sandbox/EbicsProtocolBackend.kt | 69 ---------------------- .../src/main/kotlin/tech/libeufin/sandbox/Main.kt | 19 ++---- .../kotlin/tech/libeufin/sandbox/bankAccount.kt | 39 +++++++----- 5 files changed, 36 insertions(+), 105 deletions(-) (limited to 'sandbox/src/main/kotlin/tech/libeufin') diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/CircuitApi.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/CircuitApi.kt index 956ccba7..ebd72d3c 100644 --- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/CircuitApi.kt +++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/CircuitApi.kt @@ -146,12 +146,6 @@ fun generateCashoutSubject( " to ${amountCredit.currency}:${amountCredit.amount}" } -fun BigDecimal.roundToTwoDigits(): BigDecimal { - // val twoDigitsRounding = MathContext(2) - // return this.round(twoDigitsRounding) - return this.setScale(2, RoundingMode.HALF_UP) -} - /** * By default, it takes the amount in the regional currency * and applies ratio and fees to convert it to fiat. If the @@ -522,8 +516,7 @@ fun circuitApi(circuitRoute: Route) { // check that the balance is sufficient val balance = getBalance( user, - demobank.name, - withPending = true + demobank.name ) val balanceCheck = balance - amountDebitAsNumber if (balanceCheck < BigDecimal.ZERO && balanceCheck.abs() > BigDecimal(demobank.config.usersDebtLimit)) diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt index c71d1ee7..87db263e 100644 --- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt +++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt @@ -34,6 +34,7 @@ import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.transactions.TransactionManager import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transactionManager +import tech.libeufin.sandbox.CashoutSubmissionsTable.nullable import tech.libeufin.util.* import java.sql.Connection import kotlin.reflect.* @@ -498,6 +499,7 @@ class BankAccountTransactionEntity(id: EntityID) : LongEntity(id) { * own multiple bank accounts. */ object BankAccountsTable : IntIdTable() { + val balance = text("balance").default("0") val iban = text("iban") val bic = text("bic").default("SANDBOXX") val label = text("label").uniqueIndex("accountLabelIndex") @@ -538,6 +540,7 @@ object BankAccountsTable : IntIdTable() { class BankAccountEntity(id: EntityID) : IntEntity(id) { companion object : IntEntityClass(BankAccountsTable) + var balance by BankAccountsTable.balance var iban by BankAccountsTable.iban var bic by BankAccountsTable.bic var label by BankAccountsTable.label @@ -555,7 +558,7 @@ object BankAccountStatementsTable : IntIdTable() { val xmlMessage = text("xmlMessage") val bankAccount = reference("bankAccount", BankAccountsTable) // Signed BigDecimal representing a Camt.053 CLBD field. - val balanceClbd = text("balanceClbd") + val balanceClbd = text("balanceClbd").nullable() } class BankAccountStatementEntity(id: EntityID) : IntEntity(id) { diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt index fc240963..658d6373 100644 --- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt +++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt @@ -282,8 +282,6 @@ fun buildCamtString( type: Int, subscriberIban: String, history: MutableList, - balancePrcd: BigDecimal, // Balance up to freshHistory (excluded). - balanceClbd: BigDecimal, currency: String ): SandboxCamt { /** @@ -359,45 +357,6 @@ fun buildCamtString( } } } - element("Bal") { - element("Tp/CdOrPrtry/Cd") { - /* Balance type, in a coded format. PRCD stands - for "Previously closed booked" and shows the - balance at the time _before_ all the entries - reported in this document were posted to the - involved bank account. */ - text("PRCD") - } - element("Amt") { - attribute("Ccy", currency) - text(balancePrcd.abs().toPlainString()) - } - element("CdtDbtInd") { - text(getCreditDebitInd(balancePrcd)) - } - element("Dt/Dt") { - // date of this balance - text(dashedDate) - } - } - element("Bal") { - element("Tp/CdOrPrtry/Cd") { - /* CLBD stands for "Closing booked balance", and it - is calculated by summing the PRCD with all the - entries reported in this document */ - text("CLBD") - } - element("Amt") { - attribute("Ccy", currency) - text(balanceClbd.abs().toPlainString()) - } - element("CdtDbtInd") { - text(getCreditDebitInd(balanceClbd)) - } - element("Dt/Dt") { - text(dashedDate) - } - } history.forEach { this.element("Ntry") { element("Amt") { @@ -532,38 +491,10 @@ private fun constructCamtResponse( } } if (history.size == 0) throw EbicsNoDownloadDataAvailable() - - /** - * PRCD balance: balance mentioned in the last statement. This - * will be normally zero, because statements need to be explicitly created. - * - * CLBD balance: PRCD + transactions accounted in the current C52. - * Alternatively, that could be changed into: PRCD + all the pending - * transactions. This way, the CLBD balance would closer reflect the - * latest (pending) activities. - */ - val prcdBalance = getBalance(bankAccount, withPending = false) - val clbdBalance = run { - var base = prcdBalance - history.forEach { tx -> - when (tx.direction) { - XLibeufinBankDirection.DEBIT -> base -= parseDecimal(tx.amount) - XLibeufinBankDirection.CREDIT -> base += parseDecimal(tx.amount) - else -> { - logger.error("Transaction with subject '${tx.subject}' is " + - "inconsistent: neither DBIT nor CRDT") - throw internalServerError("Transactions internal error.") - } - } - } - base - } val camtData = buildCamtString( type, bankAccount.iban, history, - balancePrcd = prcdBalance, - balanceClbd = clbdBalance, bankAccount.demoBank.config.currency ) val paymentsList: String = if (logger.isDebugEnabled) { diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt index ad9417f1..fab1fca6 100644 --- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt +++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt @@ -264,14 +264,10 @@ class Camt053Tick : CliktCommand( * Resorting the closing (CLBD) balance of the last statement; will * become the PRCD balance of the _new_ one. */ - val lastBalance = getBalance(accountIter, withPending = false) - val balanceClbd = getBalance(accountIter, withPending = true) val camtData = buildCamtString( 53, accountIter.iban, newStatements[accountIter.label]!!, - balanceClbd = balanceClbd, - balancePrcd = lastBalance, currency = accountIter.demoBank.config.currency ) BankAccountStatementEntity.new { @@ -279,7 +275,6 @@ class Camt053Tick : CliktCommand( creationTime = getUTCnow().toInstant().epochSecond xmlMessage = camtData.camtMessage bankAccount = accountIter - this.balanceClbd = balanceClbd.toPlainString() } } BankAccountFreshTransactionsTable.deleteAll() @@ -802,7 +797,7 @@ val sandboxApp: Application.() -> Unit = { val bankAccount = getBankAccountFromLabel(label, demobank) if (!allowOwnerOrAdmin(username, label)) throw unauthorized("'${username}' has no rights over '$label'") - val balance = getBalance(bankAccount, withPending = true) + val balance = getBalance(bankAccount) object { val balance = "${bankAccount.demoBank.config.currency}:${balance}" val iban = bankAccount.iban @@ -1453,10 +1448,11 @@ val sandboxApp: Application.() -> Unit = { val authGranted = !WITH_AUTH || bankAccount.isPublic || username == "admin" if (!authGranted && bankAccount.owner != username) throw forbidden("Customer '$username' cannot access bank account '$accountAccessed'") - val balance = getBalance(bankAccount, withPending = true) + val balance = getBalance(bankAccount) + logger.debug("Balance of '$username': ${balance.toPlainString()}") call.respond(object { val balance = object { - val amount = "${demobank.config.currency}:${balance.abs(). toPlainString()}" + val amount = "${demobank.config.currency}:${balance.abs().toPlainString()}" val credit_debit_indicator = if (balance < BigDecimal.ZERO) "debit" else "credit" } val paytoUri = buildIbanPaytoUri( @@ -1574,10 +1570,7 @@ val sandboxApp: Application.() -> Unit = { BankAccountsTable.demoBank eq demobank.id ) }.forEach { - val balanceIter = getBalance( - it, - withPending = true, - ) + val balanceIter = getBalance(it) ret.publicAccounts.add( PublicAccountInfo( balance = "${demobank.config.currency}:$balanceIter", @@ -1632,7 +1625,7 @@ val sandboxApp: Application.() -> Unit = { demobank = demobank.name, isPublic = req.isPublic ) - val balance = getBalance(newAccount.bankAccount, withPending = true) + val balance = getBalance(newAccount.bankAccount) call.respond(object { val balance = getBalanceForJson(balance, demobank.config.currency) val paytoUri = buildIbanPaytoUri( diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt index 748962d5..23c24dbf 100644 --- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt +++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt @@ -1,13 +1,13 @@ package tech.libeufin.sandbox import io.ktor.http.* +import org.jetbrains.exposed.sql.StdOutSqlLogger +import org.jetbrains.exposed.sql.addLogger import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.transactions.transaction import tech.libeufin.util.* import java.math.BigDecimal - - /** * Check whether the given bank account would surpass the * debit threshold, in case the potential amount gets transferred. @@ -21,7 +21,7 @@ fun maybeDebit( "Demobank '${demobankName}' not found when trying to check the debit threshold" + " for user $accountLabel" ) - val balance = getBalance(accountLabel, demobankName, withPending = true) + val balance = getBalance(accountLabel, demobankName) val maxDebt = if (accountLabel == "admin") { demobank.config.bankDebtLimit } else demobank.config.usersDebtLimit @@ -52,16 +52,18 @@ fun getBalanceForJson(value: BigDecimal, currency: String): BalanceJson { ) } +fun getBalance(bankAccount: BankAccountEntity): BigDecimal { + return BigDecimal(bankAccount.balance) +} + /** - * The last balance is the one mentioned in the bank account's - * last statement. If the bank account does not have any statement - * yet, then zero is returned. When 'withPending' is true, it adds - * the pending transactions to it. - * - * Note: because transactions are searched after the bank accounts - * (numeric) id, the research in the database is not ambiguous. + * This function balances _in bank account statements_. A statement + * witnesses the bank account after a given business time slot. Therefore + * _this_ type of balance is not guaranteed to hold the _actual_ and + * more up-to-date bank account. It'll be used when Sandbox will support + * the issuing of bank statement. */ -fun getBalance( +fun getBalanceForStatement( bankAccount: BankAccountEntity, withPending: Boolean = true ): BigDecimal { @@ -104,8 +106,7 @@ fun getBalance( // Gets the balance of 'accountLabel', which is hosted at 'demobankName'. fun getBalance(accountLabel: String, - demobankName: String = "default", - withPending: Boolean = true + demobankName: String = "default" ): BigDecimal { val demobank = getDemobank(demobankName) ?: throw SandboxError( HttpStatusCode.InternalServerError, @@ -124,7 +125,7 @@ fun getBalance(accountLabel: String, demobank, withBankFault = true ) - return getBalance(account, withPending) + return getBalance(account) } /** @@ -190,6 +191,7 @@ fun wireTransfer( val timeStamp = getUTCnow().toInstant().toEpochMilli() val transactionRef = getRandomString(8) transaction { + // addLogger(StdOutSqlLogger) BankAccountTransactionEntity.new { creditorIban = creditAccount.iban creditorBic = creditAccount.bic @@ -224,6 +226,15 @@ fun wireTransfer( this.demobank = demobank this.pmtInfId = pmtInfId } + + // Adjusting the balances (acceptable debit conditions checked before). + // Debit: + val newDebitBalance = (BigDecimal(debitAccount.balance) - amountAsNumber).roundToTwoDigits() + debitAccount.balance = newDebitBalance.toPlainString() // FIXME: that's ignored! + // Credit: + val newCreditBalance = (BigDecimal(creditAccount.balance) + amountAsNumber).roundToTwoDigits() + creditAccount.balance = newCreditBalance.toPlainString() + // Signaling this wire transfer's event. if (this.isPostgres()) { val creditChannel = buildChannelName( -- cgit v1.2.3