gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[libeufin] branch master updated (e121d549 -> e292fa35)


From: gnunet
Subject: [libeufin] branch master updated (e121d549 -> e292fa35)
Date: Sat, 16 Mar 2024 02:23:20 +0100

This is an automated email from the git hooks/post-receive script.

antoine pushed a change to branch master
in repository libeufin.

    from e121d549 FIx cashin idempotency when conversion is enabled
     new e5e71862 Fix drop and account soft delete
     new e292fa35 Simplify error handling for microsecond overflows that never 
occur in practice

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../kotlin/tech/libeufin/bank/db/AccountDAO.kt     |  26 ++--
 .../kotlin/tech/libeufin/bank/db/CashoutDAO.kt     |  10 +-
 .../main/kotlin/tech/libeufin/bank/db/Database.kt  |  23 +---
 .../kotlin/tech/libeufin/bank/db/ExchangeDAO.kt    |   4 +-
 .../main/kotlin/tech/libeufin/bank/db/TanDAO.kt    |  12 +-
 .../main/kotlin/tech/libeufin/bank/db/TokenDAO.kt  |  16 +--
 .../kotlin/tech/libeufin/bank/db/TransactionDAO.kt |   4 +-
 .../kotlin/tech/libeufin/bank/db/WithdrawalDAO.kt  |   8 +-
 bank/src/test/kotlin/CoreBankApiTest.kt            |  21 +---
 bank/src/test/kotlin/StatsTest.kt                  |   2 +-
 common/src/main/kotlin/DB.kt                       |   4 +-
 common/src/main/kotlin/time.kt                     |  66 +++-------
 database-versioning/libeufin-bank-0003.sql         |   6 +
 database-versioning/libeufin-bank-drop.sql         |  17 ++-
 database-versioning/libeufin-bank-procedures.sql   | 139 +++++++--------------
 database-versioning/libeufin-nexus-drop.sql        |  12 +-
 nexus/src/main/kotlin/tech/libeufin/nexus/Log.kt   |   4 +-
 .../kotlin/tech/libeufin/nexus/db/InitiatedDAO.kt  |  14 +--
 .../kotlin/tech/libeufin/nexus/db/PaymentDAO.kt    |  12 +-
 19 files changed, 151 insertions(+), 249 deletions(-)

diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt
index f78947e7..7781d807 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt
@@ -52,7 +52,7 @@ class AccountDAO(private val db: Database) {
         checkPaytoIdempotent: Boolean,
         ctx: BankPaytoCtx
     ): AccountCreationResult = db.serializable { it ->
-        val now = Instant.now().toDbMicros() ?: throw faultyTimestampByBank()
+        val now = Instant.now().micros()
         it.transaction { conn ->
             val idempotent = conn.prepareStatement("""
                 SELECT password_hash, name=?
@@ -194,15 +194,17 @@ class AccountDAO(private val db: Database) {
         login: String, 
         is2fa: Boolean
     ): AccountDeletionResult = db.serializable { conn ->
+        val now = Instant.now().micros()
         val stmt = conn.prepareStatement("""
             SELECT
               out_not_found,
               out_balance_not_zero,
               out_tan_required
-              FROM account_delete(?,?);
+              FROM account_delete(?,?,?)
         """)
         stmt.setString(1, login)
-        stmt.setBoolean(2, is2fa)
+        stmt.setLong(2, now)
+        stmt.setBoolean(3, is2fa)
         stmt.executeQuery().use {
             when {
                 !it.next() -> throw internalServerError("Deletion returned 
nothing.")
@@ -265,7 +267,7 @@ class AccountDAO(private val db: Database) {
             FROM customers
                 JOIN bank_accounts 
                 ON customer_id=owning_customer_id
-            WHERE login=?
+            WHERE login=? AND deleted_at IS NULL
         """).run {
             setString(1, login)
             oneOrNull {
@@ -388,7 +390,8 @@ class AccountDAO(private val db: Database) {
     ): AccountPatchAuthResult = db.serializable {
         it.transaction { conn ->
             val (currentPwh, tanRequired) = conn.prepareStatement("""
-                SELECT password_hash, (NOT ? AND tan_channel IS NOT NULL) FROM 
customers WHERE login=?
+                SELECT password_hash, (NOT ? AND tan_channel IS NOT NULL) 
+                FROM customers WHERE login=? AND deleted_at IS NULL
             """).run {
                 setBoolean(1, is2fa)
                 setString(2, login)
@@ -415,7 +418,7 @@ class AccountDAO(private val db: Database) {
     /** Get password hash of account [login] */
     suspend fun passwordHash(login: String): String? = db.conn { conn ->
         val stmt = conn.prepareStatement("""
-            SELECT password_hash FROM customers WHERE login=?
+            SELECT password_hash FROM customers WHERE login=? AND deleted_at 
IS NULL
         """)
         stmt.setString(1, login)
         stmt.oneOrNull { 
@@ -432,9 +435,8 @@ class AccountDAO(private val db: Database) {
              ,name
              ,is_taler_exchange
             FROM bank_accounts
-                JOIN customers 
-                ON customer_id=owning_customer_id
-                WHERE login=?
+                JOIN customers ON customer_id=owning_customer_id
+            WHERE login=? AND deleted_at IS NULL
         """)
         stmt.setString(1, login)
         stmt.oneOrNull {
@@ -466,7 +468,7 @@ class AccountDAO(private val db: Database) {
             FROM customers 
                 JOIN bank_accounts
                     ON customer_id=owning_customer_id
-            WHERE login=?
+            WHERE login=? AND deleted_at IS NULL
         """)
         stmt.setString(1, login)
         stmt.oneOrNull {
@@ -512,7 +514,7 @@ class AccountDAO(private val db: Database) {
               bank_account_id
               FROM bank_accounts JOIN customers
                 ON owning_customer_id = customer_id 
-              WHERE is_public=true AND name LIKE ? AND
+              WHERE is_public=true AND name LIKE ? AND deleted_at IS NULL AND
             """,
             {
                 setString(1, params.loginFilter)
@@ -555,7 +557,7 @@ class AccountDAO(private val db: Database) {
             ,bank_account_id
             FROM bank_accounts JOIN customers
               ON owning_customer_id = customer_id 
-            WHERE name LIKE ? AND
+            WHERE name LIKE ? AND deleted_at IS NULL AND
             """,
             {
                 setString(1, params.loginFilter)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/CashoutDAO.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/db/CashoutDAO.kt
index 3f044ea1..45da0854 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/db/CashoutDAO.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/db/CashoutDAO.kt
@@ -66,7 +66,7 @@ class CashoutDAO(private val db: Database) {
         stmt.setLong(5, amountCredit.value)
         stmt.setInt(6, amountCredit.frac)
         stmt.setString(7, subject)
-        stmt.setLong(8, now.toDbMicros() ?: throw faultyTimestampByBank())
+        stmt.setLong(8, now.micros())
         stmt.setBoolean(9, is2fa)
         stmt.executeQuery().use {
             when {
@@ -104,7 +104,7 @@ class CashoutDAO(private val db: Database) {
                 JOIN bank_accounts ON bank_account=bank_account_id
                 JOIN customers ON owning_customer_id=customer_id
                 LEFT JOIN bank_account_transactions ON 
local_transaction=bank_transaction_id
-            WHERE cashout_id=? AND login=?
+            WHERE cashout_id=? AND login=? AND deleted_at IS NULL
         """)
         stmt.setLong(1, id)
         stmt.setString(2, login)
@@ -117,7 +117,7 @@ class CashoutDAO(private val db: Database) {
                 creation_time = it.getTalerTimestamp("creation_time"),
                 confirmation_time = when (val timestamp = 
it.getLong("confirmation_date")) {
                     0L -> null
-                    else -> 
TalerProtocolTimestamp(timestamp.microsToJavaInstant() ?: throw 
faultyTimestampByBank())
+                    else -> TalerProtocolTimestamp(timestamp.asInstant())
                 },
                 tan_channel = it.getString("tan_channel")?.run { 
TanChannel.valueOf(this) },
                 tan_info = it.getString("tan_info"),
@@ -134,7 +134,7 @@ class CashoutDAO(private val db: Database) {
             FROM cashout_operations
                 JOIN bank_accounts ON bank_account=bank_account_id
                 JOIN customers ON owning_customer_id=customer_id
-            WHERE
+            WHERE deleted_at IS NULL AND
         """) {
             GlobalCashoutInfo(
                 cashout_id = it.getLong("cashout_id"),
@@ -150,7 +150,7 @@ class CashoutDAO(private val db: Database) {
             FROM cashout_operations
                 JOIN bank_accounts ON bank_account=bank_account_id
                 JOIN customers ON owning_customer_id=customer_id
-            WHERE login = ? AND
+            WHERE login = ? AND deleted_at IS NULL AND
         """, 
             bind = { 
                 setString(1, login)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/Database.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/db/Database.kt
index 1880b67b..19706a7b 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/db/Database.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/db/Database.kt
@@ -35,25 +35,6 @@ import kotlin.math.abs
 
 private val logger: Logger = LoggerFactory.getLogger("libeufin-bank-db")
 
-/**
- * This error occurs in case the timestamp took by the bank for some
- * event could not be converted in microseconds.  Note: timestamp are
- * taken via the Instant.now(), then converted to nanos, and then divided
- * by 1000 to obtain the micros.
- *
- * It could be that a legitimate timestamp overflows in the process of
- * being converted to micros - as described above.  In the case of a timestamp,
- * the fault lies to the bank, because legitimate timestamps must (at the
- * time of writing!) go through the conversion to micros.
- *
- * On the other hand (and for the sake of completeness), in the case of a
- * timestamp that was calculated after a client-submitted duration, the 
overflow
- * lies to the client, because they must have specified a gigantic amount of 
time
- * that overflew the conversion to micros and should simply have specified 
"forever".
- */
-internal fun faultyTimestampByBank() = internalServerError("Bank took 
overflowing timestamp")
-internal fun faultyDurationByClient() = badRequest("Overflowing duration, 
please specify 'forever' instead.")
-
 class Database(dbConfig: String, internal val bankCurrency: String, internal 
val fiatCurrency: String?): DbPool(dbConfig, "libeufin_bank") {
     internal val notifWatcher: NotificationWatcher = 
NotificationWatcher(pgSource)
 
@@ -206,7 +187,5 @@ enum class AbortResult {
 }
 
 fun ResultSet.getTalerTimestamp(name: String): TalerProtocolTimestamp{
-    return TalerProtocolTimestamp(
-        getLong(name).microsToJavaInstant() ?: throw faultyTimestampByBank()
-    )
+    return TalerProtocolTimestamp(getLong(name).asInstant())
 }
\ No newline at end of file
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/ExchangeDAO.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/db/ExchangeDAO.kt
index 6540172a..69249f1a 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/db/ExchangeDAO.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/db/ExchangeDAO.kt
@@ -130,7 +130,7 @@ class ExchangeDAO(private val db: Database) {
         stmt.setString(6, req.exchange_base_url.url)
         stmt.setString(7, req.credit_account.canonical)
         stmt.setString(8, login)
-        stmt.setLong(9, now.toDbMicros() ?: throw faultyTimestampByBank())
+        stmt.setLong(9, now.micros())
 
         stmt.executeQuery().use {
             when {
@@ -191,7 +191,7 @@ class ExchangeDAO(private val db: Database) {
         stmt.setInt(4, req.amount.frac)
         stmt.setString(5, req.debit_account.canonical)
         stmt.setString(6, login)
-        stmt.setLong(7, now.toDbMicros() ?: throw faultyTimestampByBank())
+        stmt.setLong(7, now.micros())
 
         stmt.executeQuery().use {
             when {
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/TanDAO.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/db/TanDAO.kt
index 9017540d..c7328d65 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/db/TanDAO.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/db/TanDAO.kt
@@ -23,7 +23,7 @@ import tech.libeufin.bank.Operation
 import tech.libeufin.bank.TanChannel
 import tech.libeufin.bank.internalServerError
 import tech.libeufin.common.oneOrNull
-import tech.libeufin.common.toDbMicros
+import tech.libeufin.common.micros
 import java.time.Duration
 import java.time.Instant
 import java.util.concurrent.TimeUnit
@@ -46,7 +46,7 @@ class TanDAO(private val db: Database) {
         stmt.setString(1, body)
         stmt.setString(2, op.name)
         stmt.setString(3, code)
-        stmt.setLong(4, now.toDbMicros() ?: throw faultyTimestampByBank())
+        stmt.setLong(4, now.micros())
         stmt.setLong(5, TimeUnit.MICROSECONDS.convert(validityPeriod))
         stmt.setInt(6, retryCounter)
         stmt.setString(7, login)
@@ -76,7 +76,7 @@ class TanDAO(private val db: Database) {
         stmt.setLong(1, id)
         stmt.setString(2, login)
         stmt.setString(3, code)
-        stmt.setLong(4, now.toDbMicros() ?: throw faultyTimestampByBank())
+        stmt.setLong(4, now.micros())
         stmt.setLong(5, TimeUnit.MICROSECONDS.convert(validityPeriod))
         stmt.setInt(6, retryCounter)
         stmt.executeQuery().use {
@@ -100,7 +100,7 @@ class TanDAO(private val db: Database) {
     ) = db.serializable { conn ->
         val stmt = conn.prepareStatement("SELECT 
tan_challenge_mark_sent(?,?,?)")
         stmt.setLong(1, id)
-        stmt.setLong(2, now.toDbMicros() ?: throw faultyTimestampByBank())
+        stmt.setLong(2, now.micros())
         stmt.setLong(3, TimeUnit.MICROSECONDS.convert(retransmissionPeriod))
         stmt.executeQuery()
     }
@@ -129,7 +129,7 @@ class TanDAO(private val db: Database) {
         stmt.setLong(1, id)
         stmt.setString(2, login)
         stmt.setString(3, code)
-        stmt.setLong(4, now.toDbMicros() ?: throw faultyTimestampByBank())
+        stmt.setLong(4, now.micros())
         stmt.executeQuery().use {
             when {
                 !it.next() -> throw internalServerError("TAN try returned 
nothing")
@@ -163,7 +163,7 @@ class TanDAO(private val db: Database) {
             SELECT body, tan_challenges.tan_channel, tan_info
             FROM tan_challenges
                 JOIN customers ON customer=customer_id
-            WHERE challenge_id=? AND op=?::op_enum AND login=?
+            WHERE challenge_id=? AND op=?::op_enum AND login=? AND deleted_at 
IS NULL
         """)
         stmt.setLong(1, id)
         stmt.setString(2, op.name)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/TokenDAO.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/db/TokenDAO.kt
index 8a5c594c..ee1a1669 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/db/TokenDAO.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/db/TokenDAO.kt
@@ -22,9 +22,9 @@ package tech.libeufin.bank.db
 import tech.libeufin.bank.BearerToken
 import tech.libeufin.bank.TokenScope
 import tech.libeufin.common.executeUpdateViolation
-import tech.libeufin.common.microsToJavaInstant
+import tech.libeufin.common.asInstant
 import tech.libeufin.common.oneOrNull
-import tech.libeufin.common.toDbMicros
+import tech.libeufin.common.micros
 import java.time.Instant
 
 /** Data access logic for auth tokens */
@@ -40,7 +40,7 @@ class TokenDAO(private val db: Database) {
     ): Boolean = db.serializable { conn ->
         // TODO single query
         val bankCustomer = conn.prepareStatement("""
-            SELECT customer_id FROM customers WHERE login=?
+            SELECT customer_id FROM customers WHERE login=? AND deleted_at IS 
NULL
         """).run {
             setString(1, login)
             oneOrNull { it.getLong(1) }!!
@@ -56,8 +56,8 @@ class TokenDAO(private val db: Database) {
             ) VALUES (?, ?, ?, ?::token_scope_enum, ?, ?)
         """)
         stmt.setBytes(1, content)
-        stmt.setLong(2, creationTime.toDbMicros() ?: throw 
faultyTimestampByBank())
-        stmt.setLong(3, expirationTime.toDbMicros() ?: throw 
faultyDurationByClient())
+        stmt.setLong(2, creationTime.micros())
+        stmt.setLong(3, expirationTime.micros())
         stmt.setString(4, scope.name)
         stmt.setLong(5, bankCustomer)
         stmt.setBoolean(6, isRefreshable)
@@ -75,13 +75,13 @@ class TokenDAO(private val db: Database) {
               is_refreshable
             FROM bearer_tokens
                 JOIN customers ON bank_customer=customer_id
-            WHERE content=?;            
+            WHERE content=? AND deleted_at IS NULL
         """)
         stmt.setBytes(1, token)
         stmt.oneOrNull { 
             BearerToken(
-                creationTime = 
it.getLong("creation_time").microsToJavaInstant() ?: throw 
faultyDurationByClient(),
-                expirationTime = 
it.getLong("expiration_time").microsToJavaInstant() ?: throw 
faultyDurationByClient(),
+                creationTime = it.getLong("creation_time").asInstant(),
+                expirationTime = it.getLong("expiration_time").asInstant(),
                 login = it.getString("login"),
                 scope = TokenScope.valueOf(it.getString("scope")),
                 isRefreshable = it.getBoolean("is_refreshable")
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/TransactionDAO.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/db/TransactionDAO.kt
index 81fd558f..e82f0ba2 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/db/TransactionDAO.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/db/TransactionDAO.kt
@@ -51,7 +51,7 @@ class TransactionDAO(private val db: Database) {
         is2fa: Boolean,
         requestUid: ShortHashCode?,
     ): BankTransactionResult = db.serializable { conn ->
-        val now = timestamp.toDbMicros() ?: throw faultyTimestampByBank()
+        val now = timestamp.micros()
         conn.transaction {
             val stmt = conn.prepareStatement("""
                 SELECT 
@@ -165,7 +165,7 @@ class TransactionDAO(private val db: Database) {
             FROM bank_account_transactions
                 JOIN bank_accounts ON 
bank_account_transactions.bank_account_id=bank_accounts.bank_account_id
                 JOIN customers ON customer_id=owning_customer_id 
-               WHERE bank_transaction_id=? AND login=?
+               WHERE bank_transaction_id=? AND login=? AND deleted_at IS NULL
         """)
         stmt.setLong(1, rowId)
         stmt.setString(2, login)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/WithdrawalDAO.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/db/WithdrawalDAO.kt
index 330006a1..3a41c0d8 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/db/WithdrawalDAO.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/db/WithdrawalDAO.kt
@@ -56,7 +56,7 @@ class WithdrawalDAO(private val db: Database) {
         stmt.setObject(2, uuid)
         stmt.setLong(3, amount.value)
         stmt.setInt(4, amount.frac)
-        stmt.setLong(5, now.toDbMicros() ?: throw faultyTimestampByBank())
+        stmt.setLong(5, now.micros())
         stmt.executeQuery().use {
             when {
                 !it.next() ->
@@ -165,7 +165,7 @@ class WithdrawalDAO(private val db: Database) {
         )
         stmt.setString(1, login)
         stmt.setObject(2, uuid)
-        stmt.setLong(3, now.toDbMicros() ?: throw faultyTimestampByBank())
+        stmt.setLong(3, now.micros())
         stmt.setBoolean(4, is2fa)
         stmt.executeQuery().use {
             when {
@@ -189,7 +189,7 @@ class WithdrawalDAO(private val db: Database) {
             FROM taler_withdrawal_operations
                 JOIN bank_accounts ON wallet_bank_account=bank_account_id
                 JOIN customers ON customer_id=owning_customer_id
-            WHERE withdrawal_uuid=?
+            WHERE withdrawal_uuid=? AND deleted_at IS NULL
         """)
         stmt.setObject(1, uuid)
         stmt.oneOrNull { it.getString(1) }
@@ -250,7 +250,7 @@ class WithdrawalDAO(private val db: Database) {
                     FROM taler_withdrawal_operations
                         JOIN bank_accounts ON 
wallet_bank_account=bank_account_id
                         JOIN customers ON customer_id=owning_customer_id
-                    WHERE withdrawal_uuid=?
+                    WHERE withdrawal_uuid=? AND deleted_at IS NULL
                 """)
                 stmt.setObject(1, uuid)
                 stmt.oneOrNull {
diff --git a/bank/src/test/kotlin/CoreBankApiTest.kt 
b/bank/src/test/kotlin/CoreBankApiTest.kt
index b1b89cc9..0bd13207 100644
--- a/bank/src/test/kotlin/CoreBankApiTest.kt
+++ b/bank/src/test/kotlin/CoreBankApiTest.kt
@@ -137,8 +137,7 @@ class CoreBankTokenApiTest {
     @Test
     fun delete() = bankSetup { _ -> 
         // TODO test restricted
-        val token = client.post("/accounts/merchant/token") {
-            pwAuth("merchant")
+        val token = client.postA("/accounts/merchant/token") {
             json { "scope" to "readonly" }
         }.assertOkJson<TokenSuccessResponse>().access_token
         // Check OK
@@ -151,9 +150,7 @@ class CoreBankTokenApiTest {
         }.assertUnauthorized()
 
         // Checking merchant can still be served by basic auth, after token 
deletion.
-        client.get("/accounts/merchant") {
-            pwAuth("merchant")
-        }.assertOk()
+        client.getA("/accounts/merchant").assertOk()
     }
 }
 
@@ -424,19 +421,6 @@ class CoreBankAccountsApiTest {
             .assertConflict(TalerErrorCode.BANK_ACCOUNT_BALANCE_NOT_ZERO)
         // Successful deletion
         tx("john", "KUDOS:1", "customer")
-        // TODO remove with gc
-        db.conn { conn ->
-            val id = conn.prepareStatement("SELECT bank_account_id FROM 
bank_accounts JOIN customers ON customer_id=owning_customer_id WHERE login = 
?").run {
-                setString(1, "john")
-                oneOrNull {
-                    it.getLong(1)
-                }!!
-            }
-            conn.prepareStatement("DELETE FROM bank_account_transactions WHERE 
bank_account_id=?").run {
-                setLong(1, id)
-                execute()
-            }
-        }
         client.deleteA("/accounts/john")
             .assertChallenge()
             .assertNoContent()
@@ -513,7 +497,6 @@ class CoreBankAccountsApiTest {
             json(req)
         }.assertNoContent()
 
-        
         checkAdminOnly(
             obj(req) { "debit_threshold" to "KUDOS:100" },
             TalerErrorCode.BANK_NON_ADMIN_PATCH_DEBT_LIMIT
diff --git a/bank/src/test/kotlin/StatsTest.kt 
b/bank/src/test/kotlin/StatsTest.kt
index 3f235d4f..c34565fd 100644
--- a/bank/src/test/kotlin/StatsTest.kt
+++ b/bank/src/test/kotlin/StatsTest.kt
@@ -37,7 +37,7 @@ class StatsTest {
         suspend fun cashin(amount: String) {
             db.conn { conn ->
                 val stmt = conn.prepareStatement("SELECT 0 FROM cashin(?, ?, 
(?, ?)::taler_amount, ?)")
-                stmt.setLong(1, Instant.now().toDbMicros()!!)
+                stmt.setLong(1, Instant.now().micros())
                 stmt.setBytes(2, ShortHashCode.rand().raw)
                 val amount = TalerAmount(amount)
                 stmt.setLong(3, amount.value)
diff --git a/common/src/main/kotlin/DB.kt b/common/src/main/kotlin/DB.kt
index b44e1fcd..27b8eabe 100644
--- a/common/src/main/kotlin/DB.kt
+++ b/common/src/main/kotlin/DB.kt
@@ -276,9 +276,7 @@ fun resetDatabaseTables(conn: PgConnection, cfg: 
DatabaseConfig, sqlFilePrefix:
         SELECT EXISTS(SELECT 1 FROM information_schema.schemata WHERE 
schema_name='_v') AND
             EXISTS(SELECT 1 FROM information_schema.schemata WHERE 
schema_name='${sqlFilePrefix.replace("-", "_")}')
         """
-    ).oneOrNull {
-        it.getBoolean(1)
-    }!!
+    ).one{ it.getBoolean(1) }
     if (!isInitialized) {
         logger.info("versioning schema not present, not running drop sql")
         return
diff --git a/common/src/main/kotlin/time.kt b/common/src/main/kotlin/time.kt
index 834183ee..255004f5 100644
--- a/common/src/main/kotlin/time.kt
+++ b/common/src/main/kotlin/time.kt
@@ -26,61 +26,35 @@ import java.time.temporal.ChronoUnit
 
 private val logger: Logger = LoggerFactory.getLogger("libeufin-common")
 
-/**
- * Converts the 'this' Instant to the number of nanoseconds
- * since the Epoch.  It returns the result as Long, or null
- * if one arithmetic overflow occurred.
- */
-private fun Instant.toNanos(): Long? {
-    val oneSecNanos = ChronoUnit.SECONDS.duration.toNanos()
-    val nanoBase: Long = this.epochSecond * oneSecNanos
-    if (nanoBase != 0L && nanoBase / this.epochSecond != oneSecNanos) {
-        logger.error("Multiplication overflow: could not convert Instant to 
nanos.")
-        return null
-    }
-    val res = nanoBase + this.nano
-    if (res < nanoBase) {
-        logger.error("Addition overflow: could not convert Instant to nanos.")
-        return null
-    }
-    return res
-}
-
-/**
- * This function converts an Instant input to the
- * number of microseconds since the Epoch, except that
- * it yields Long.MAX if the Input is Instant.MAX.
- *
- * Takes the name after the way timestamps are designed
- * in the database: micros since Epoch, or Long.MAX for
- * "never".
- *
- * Returns the Long representation of 'this' or null
- * if that would overflow.
- */
-fun Instant.toDbMicros(): Long? {
-    if (this == Instant.MAX)
+/** 
+ * Convert Instant to microseconds since the epoch.
+ * 
+ * Returns Long.MAX_VALUE if instant is Instant.MAX
+ **/
+fun Instant.micros(): Long {
+    if (this == Instant.MAX) 
         return Long.MAX_VALUE
-    val nanos = this.toNanos() ?: run {
-        logger.error("Could not obtain micros to store to database, 
convenience conversion to nanos overflew.")
-        return null
+    try {
+        val micros = ChronoUnit.MICROS.between(Instant.EPOCH, this)
+        if (micros == Long.MAX_VALUE) throw ArithmeticException()
+        return micros
+    } catch (e: ArithmeticException) {
+        throw Exception("${this} is too big to be converted to micros 
resolution", e)
     }
-    return nanos / 1000L
 }
 
-/**
- * This helper is typically used to convert a timestamp expressed
- * in microseconds from the DB back to the Web application.  In case
- * of _any_ error, it logs it and returns null.
+/** 
+ * Convert microsecons to Instant.
+ * 
+ * Returns Instant.MAX if microseconds is Long.MAX_VALUE
  */
-fun Long.microsToJavaInstant(): Instant? {
+fun Long.asInstant(): Instant {
     if (this == Long.MAX_VALUE)
         return Instant.MAX
     return try {
         Instant.EPOCH.plus(this, ChronoUnit.MICROS)
-    } catch (e: Exception) {
-        logger.error(e.message)
-        return null
+    } catch (e: ArithmeticException ) {
+        throw Exception("${this} is too big to be converted to Instant", e)
     }
 }
 
diff --git a/database-versioning/libeufin-bank-0003.sql 
b/database-versioning/libeufin-bank-0003.sql
index 14f1075f..6956b3f8 100644
--- a/database-versioning/libeufin-bank-0003.sql
+++ b/database-versioning/libeufin-bank-0003.sql
@@ -24,5 +24,11 @@ CREATE TABLE bank_transaction_operations
     REFERENCES bank_account_transactions(bank_transaction_id)
       ON DELETE CASCADE
   );
+COMMENT ON TABLE bank_transaction_operations
+  IS 'Operation table for idempotent bank transactions.';
+
+ALTER TABLE customers ADD deleted_at INT8;
+COMMENT ON COLUMN customers.deleted_at
+  IS 'Indicates a deletion request, we keep the account in the database until 
all its transactions have been deleted for compliance.';
 
 COMMIT;
diff --git a/database-versioning/libeufin-bank-drop.sql 
b/database-versioning/libeufin-bank-drop.sql
index 7fbcc342..52ef772b 100644
--- a/database-versioning/libeufin-bank-drop.sql
+++ b/database-versioning/libeufin-bank-drop.sql
@@ -1,11 +1,16 @@
 BEGIN;
 
--- NOTE: The following unregistration would affect the
--- legacy database schema too.  That's acceptable as the
--- legacy schema is being removed.
-SELECT _v.unregister_patch('libeufin-bank-0001');
-SELECT _v.unregister_patch('libeufin-bank-0002');
-SELECT _v.unregister_patch('libeufin-bank-0003');
+DO
+$do$
+DECLARE
+    patch text;
+BEGIN
+    for patch in SELECT patch_name FROM _v.patches WHERE patch_name LIKE 
'libeufin_bank_%' loop 
+        PERFORM _v.unregister_patch(patch);
+    end loop;
+END
+$do$;
+
 DROP SCHEMA libeufin_bank CASCADE;
 
 COMMIT;
diff --git a/database-versioning/libeufin-bank-procedures.sql 
b/database-versioning/libeufin-bank-procedures.sql
index 8f82fc65..7ef7c3f1 100644
--- a/database-versioning/libeufin-bank-procedures.sql
+++ b/database-versioning/libeufin-bank-procedures.sql
@@ -142,6 +142,7 @@ COMMENT ON FUNCTION account_balance_is_sufficient IS 'Check 
if an account have e
 
 CREATE FUNCTION account_delete(
   IN in_login TEXT,
+  IN in_now INT8,
   IN in_is_tan BOOLEAN,
   OUT out_not_found BOOLEAN,
   OUT out_balance_not_zero BOOLEAN,
@@ -150,45 +151,26 @@ CREATE FUNCTION account_delete(
 LANGUAGE plpgsql AS $$
 DECLARE
 my_customer_id INT8;
-my_balance_val INT8;
-my_balance_frac INT4;
 BEGIN
--- check if login exists and if 2FA is required
-SELECT customer_id, (NOT in_is_tan AND tan_channel IS NOT NULL) 
-  INTO my_customer_id, out_tan_required
-  FROM customers
-  WHERE login = in_login;
-IF NOT FOUND THEN
-  out_not_found=TRUE;
-  RETURN;
-END IF;
-
--- get the balance
-SELECT
-  (balance).val as balance_val,
-  (balance).frac as balance_frac
-  INTO
-    my_balance_val,
-    my_balance_frac
-  FROM bank_accounts
-  WHERE owning_customer_id = my_customer_id;
-IF NOT FOUND THEN
-  RAISE EXCEPTION 'Invariant failed: customer lacks bank account';
-END IF;
-
--- check that balance is zero.
-IF my_balance_val != 0 OR my_balance_frac != 0 THEN
-  out_balance_not_zero=TRUE;
-  RETURN;
-END IF;
-
--- check tan required
-IF out_tan_required THEN
+-- check if account exists, has zero balance and if 2FA is required
+SELECT 
+   customer_id
+  ,(NOT in_is_tan AND tan_channel IS NOT NULL)
+  ,((balance).val != 0 OR (balance).frac != 0)
+  INTO 
+     my_customer_id
+    ,out_tan_required
+    ,out_balance_not_zero
+  FROM customers 
+    JOIN bank_accounts ON owning_customer_id = customer_id
+  WHERE login = in_login AND deleted_at IS NULL;
+IF NOT FOUND OR out_balance_not_zero OR out_tan_required THEN
+  out_not_found=NOT FOUND;
   RETURN;
 END IF;
 
 -- actual deletion
-DELETE FROM customers WHERE login = in_login;
+UPDATE customers SET deleted_at = in_now WHERE customer_id = my_customer_id;
 END $$;
 COMMENT ON FUNCTION account_delete IS 'Deletes an account if the balance is 
zero';
 
@@ -310,11 +292,9 @@ SELECT
   FROM bank_accounts 
       JOIN customers 
         ON customer_id=owning_customer_id
-  WHERE login = in_username;
-IF NOT FOUND THEN
-  out_debtor_not_found=TRUE;
-  RETURN;
-ELSIF out_debtor_not_exchange THEN
+  WHERE login = in_username AND deleted_at IS NULL;
+IF NOT FOUND OR out_debtor_not_exchange THEN
+  out_debtor_not_found=NOT FOUND;
   RETURN;
 END IF;
 -- Find receiver bank account id
@@ -323,10 +303,8 @@ SELECT
   INTO receiver_bank_account_id, out_both_exchanges
   FROM bank_accounts
   WHERE internal_payto_uri = in_credit_account_payto;
-IF NOT FOUND THEN
-  out_creditor_not_found=TRUE;
-  RETURN;
-ELSIF out_both_exchanges THEN
+IF NOT FOUND OR out_both_exchanges THEN
+  out_creditor_not_found=NOT FOUND;
   RETURN;
 END IF;
 -- Perform bank transfer
@@ -392,11 +370,9 @@ SELECT
   FROM bank_accounts 
       JOIN customers 
         ON customer_id=owning_customer_id
-  WHERE login = in_username;
-IF NOT FOUND THEN
-  out_creditor_not_found=TRUE;
-  RETURN;
-ELSIF out_creditor_not_exchange THEN
+  WHERE login = in_username AND deleted_at IS NULL;
+IF NOT FOUND OR out_creditor_not_exchange THEN
+  out_creditor_not_found=NOT FOUND;
   RETURN;
 END IF;
 -- Find sender bank account id
@@ -405,10 +381,8 @@ SELECT
   INTO sender_bank_account_id, out_both_exchanges
   FROM bank_accounts
   WHERE internal_payto_uri = in_debit_account_payto;
-IF NOT FOUND THEN
-  out_debtor_not_found=TRUE;
-  RETURN;
-ELSIF out_both_exchanges THEN
+IF NOT FOUND OR out_both_exchanges THEN
+  out_debtor_not_found=NOT FOUND;
   RETURN;
 END IF;
 -- Perform bank transfer
@@ -468,7 +442,7 @@ SELECT bank_account_id, is_taler_exchange, login='admin'
   INTO out_credit_bank_account_id, out_creditor_is_exchange, out_creditor_admin
   FROM bank_accounts
     JOIN customers ON customer_id=owning_customer_id
-  WHERE internal_payto_uri = in_credit_account_payto;
+  WHERE internal_payto_uri = in_credit_account_payto AND deleted_at IS NULL;
 IF NOT FOUND OR out_creditor_admin THEN
   out_creditor_not_found=NOT FOUND;
   RETURN;
@@ -478,7 +452,7 @@ SELECT bank_account_id, is_taler_exchange, 
out_credit_bank_account_id=bank_accou
   INTO out_debit_bank_account_id, out_debtor_is_exchange, out_same_account, 
out_tan_required
   FROM bank_accounts 
     JOIN customers ON customer_id=owning_customer_id
-  WHERE login = in_debit_account_username;
+  WHERE login = in_debit_account_username AND deleted_at IS NULL;
 IF NOT FOUND OR out_same_account THEN
   out_debtor_not_found=NOT FOUND;
   RETURN;
@@ -546,11 +520,9 @@ SELECT bank_account_id, is_taler_exchange
   INTO account_id, out_account_is_exchange
   FROM bank_accounts
   JOIN customers ON bank_accounts.owning_customer_id = customers.customer_id
-  WHERE login=in_account_username;
-IF NOT FOUND THEN
-  out_account_not_found=TRUE;
-  RETURN;
-ELSIF out_account_is_exchange THEN
+  WHERE login=in_account_username AND deleted_at IS NULL;
+IF NOT FOUND OR out_account_is_exchange THEN
+  out_account_not_found=NOT FOUND;
   RETURN;
 END IF;
 
@@ -598,10 +570,8 @@ SELECT
   INTO not_selected, out_status, out_already_selected
   FROM taler_withdrawal_operations
   WHERE withdrawal_uuid=in_withdrawal_uuid;
-IF NOT FOUND THEN
-  out_no_op=TRUE;
-  RETURN;
-ELSIF out_already_selected THEN
+IF NOT FOUND OR out_already_selected THEN
+  out_no_op=NOT FOUND;
   RETURN;
 END IF;
 
@@ -619,10 +589,8 @@ IF not_selected THEN
     INTO out_account_is_not_exchange
     FROM bank_accounts
     WHERE internal_payto_uri=in_selected_exchange_payto;
-  IF NOT FOUND THEN
-    out_account_not_found=TRUE;
-    RETURN;
-  ELSIF out_account_is_not_exchange THEN
+  IF NOT FOUND OR out_account_is_not_exchange THEN
+    out_account_not_found=NOT FOUND;
     RETURN;
   END IF;
 
@@ -649,10 +617,8 @@ UPDATE taler_withdrawal_operations
   WHERE withdrawal_uuid=in_withdrawal_uuid
   RETURNING confirmation_done
   INTO out_already_confirmed;
-IF NOT FOUND THEN
-  out_no_op=TRUE;
-  RETURN;
-ELSIF out_already_confirmed THEN
+IF NOT FOUND OR out_already_confirmed THEN
+  out_no_op=NOT FOUND;
   RETURN;
 END IF;
 
@@ -705,27 +671,20 @@ SELECT
   FROM taler_withdrawal_operations
     JOIN bank_accounts ON wallet_bank_account=bank_account_id
     JOIN customers ON owning_customer_id=customer_id
-  WHERE withdrawal_uuid=in_withdrawal_uuid AND login=in_login;
-IF NOT FOUND THEN
-  out_no_op=TRUE;
-  RETURN;
-ELSIF already_confirmed OR out_aborted OR out_not_selected THEN
+  WHERE withdrawal_uuid=in_withdrawal_uuid AND login=in_login AND deleted_at 
IS NULL;
+IF NOT FOUND OR already_confirmed OR out_aborted OR out_not_selected THEN
+  out_no_op=NOT FOUND;
   RETURN;
 END IF;
 
--- sending the funds to the exchange, but needs first its bank account row ID
+-- Check exchange account then 2faa
 SELECT
   bank_account_id
   INTO exchange_bank_account_id
   FROM bank_accounts
   WHERE internal_payto_uri = selected_exchange_payto_local;
-IF NOT FOUND THEN
-  out_exchange_not_found=TRUE;
-  RETURN;
-END IF;
-
--- Check 2FA
-IF out_tan_required THEN
+IF NOT FOUND OR out_tan_required THEN
+  out_exchange_not_found=NOT FOUND;
   RETURN;
 END IF;
 
@@ -1183,7 +1142,7 @@ DECLARE
 account_id INT8;
 BEGIN
 -- Retrieve account id
-SELECT customer_id INTO account_id FROM customers WHERE login = in_login;
+SELECT customer_id INTO account_id FROM customers WHERE login = in_login AND 
deleted_at IS NULL;
 -- Create challenge
 INSERT INTO tan_challenges (
   body,
@@ -1235,7 +1194,7 @@ SELECT customer_id, tan_channel, CASE tan_channel
     WHEN 'email' THEN email
   END
 INTO account_id, out_tan_channel, out_tan_info
-FROM customers WHERE login = in_login;
+FROM customers WHERE login = in_login AND deleted_at IS NULL;
 
 -- Recover expiration date
 SELECT 
@@ -1295,7 +1254,7 @@ DECLARE
 account_id INT8;
 BEGIN
 -- Retrieve account id
-SELECT customer_id INTO account_id FROM customers WHERE login = in_login;
+SELECT customer_id INTO account_id FROM customers WHERE login = in_login AND 
deleted_at IS NULL;
 -- Check challenge
 UPDATE tan_challenges SET 
   confirmation_date = CASE 
@@ -1309,10 +1268,8 @@ RETURNING
   retry_counter <= 0 AND confirmation_date IS NULL,
   in_now_date >= expiration_date AND confirmation_date IS NULL
 INTO out_ok, out_no_retry, out_expired;
-IF NOT FOUND THEN
-  out_no_op = true;
-  RETURN;
-ELSIF NOT out_ok OR out_no_retry OR out_expired THEN
+IF NOT FOUND OR NOT out_ok OR out_no_retry OR out_expired THEN
+  out_no_op = NOT FOUND;
   RETURN;
 END IF;
 
diff --git a/database-versioning/libeufin-nexus-drop.sql 
b/database-versioning/libeufin-nexus-drop.sql
index 77ac722a..199f1cb9 100644
--- a/database-versioning/libeufin-nexus-drop.sql
+++ b/database-versioning/libeufin-nexus-drop.sql
@@ -1,7 +1,15 @@
 BEGIN;
 
-SELECT _v.unregister_patch('libeufin-nexus-0001');
-SELECT _v.unregister_patch('libeufin-nexus-0002');
+DO
+$do$
+DECLARE
+    patch text;
+BEGIN
+    for patch in SELECT patch_name FROM _v.patches WHERE patch_name LIKE 
'libeufin_nexus_%' loop 
+        PERFORM _v.unregister_patch(patch);
+    end loop;
+END
+$do$;
 DROP SCHEMA libeufin_nexus CASCADE;
 
 COMMIT;
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Log.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Log.kt
index 1367c97c..fa74c791 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Log.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Log.kt
@@ -58,7 +58,7 @@ class FileLogger(path: String?) {
         // Subdir based on current day.
         val now = Instant.now()
         val asUtcDate = LocalDate.ofInstant(now, ZoneId.of("UTC"))
-        val nowMs = now.toDbMicros()
+        val nowMs = now.micros()
         // Creating the combined dir.
         val subDir = 
dir.resolve("${asUtcDate.year}-${asUtcDate.monthValue}-${asUtcDate.dayOfMonth}").resolve("fetch")
         subDir.createDirectories()
@@ -86,7 +86,7 @@ class FileLogger(path: String?) {
         // Subdir based on current day.
         val now = Instant.now()
         val asUtcDate = LocalDate.ofInstant(now, ZoneId.of("UTC"))
-        val nowMs = now.toDbMicros()
+        val nowMs = now.micros()
         // Creating the combined dir.
         val subDir = 
dir.resolve("${asUtcDate.year}-${asUtcDate.monthValue}-${asUtcDate.dayOfMonth}").resolve("submit")
         subDir.createDirectories()
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/db/InitiatedDAO.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/db/InitiatedDAO.kt
index eba3c78a..162fddee 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/db/InitiatedDAO.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/db/InitiatedDAO.kt
@@ -48,10 +48,7 @@ class InitiatedDAO(private val db: Database) {
         stmt.setInt(2, paymentData.amount.frac)
         stmt.setString(3, paymentData.wireTransferSubject)
         stmt.setString(4, paymentData.creditPaytoUri.toString())
-        val initiationTime = paymentData.initiationTime.toDbMicros() ?: run {
-            throw Exception("Initiation time could not be converted to 
microseconds for the database.")
-        }
-        stmt.setLong(5, initiationTime)
+        stmt.setLong(5, paymentData.initiationTime.micros())
         stmt.setString(6, paymentData.requestUid)
         if (stmt.executeUpdateViolation())
             return@conn PaymentInitiationResult.SUCCESS
@@ -73,7 +70,7 @@ class InitiatedDAO(private val db: Database) {
                 ,submission_counter = submission_counter + 1
             WHERE initiated_outgoing_transaction_id = ?
         """)
-        stmt.setLong(1, now.toDbMicros()!!)
+        stmt.setLong(1, now.micros())
         stmt.setString(2, orderId)
         stmt.setLong(3, id)
         stmt.execute()
@@ -93,7 +90,7 @@ class InitiatedDAO(private val db: Database) {
                 ,submission_counter = submission_counter + 1
             WHERE initiated_outgoing_transaction_id = ?
         """)
-        stmt.setLong(1, now.toDbMicros()!!)
+        stmt.setLong(1, now.micros())
         stmt.setString(2, msg)
         stmt.setLong(3, id)
         stmt.execute()
@@ -174,10 +171,7 @@ class InitiatedDAO(private val db: Database) {
     suspend fun submittable(currency: String): List<InitiatedPayment> = 
db.conn { conn ->
         fun extract(it: ResultSet): InitiatedPayment {
             val rowId = it.getLong("initiated_outgoing_transaction_id")
-            val initiationTime = 
it.getLong("initiation_time").microsToJavaInstant()
-            if (initiationTime == null) { // nexus fault
-                throw Exception("Found invalid timestamp at initiated payment 
with ID: $rowId")
-            }
+            val initiationTime = it.getLong("initiation_time").asInstant()
             return InitiatedPayment(
                 id = it.getLong("initiated_outgoing_transaction_id"),
                 amount = it.getAmount("amount", currency),
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/db/PaymentDAO.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/db/PaymentDAO.kt
index 2e315f38..2730a437 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/db/PaymentDAO.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/db/PaymentDAO.kt
@@ -38,8 +38,7 @@ class PaymentDAO(private val db: Database) {
             SELECT out_tx_id, out_initiated, out_found
             FROM register_outgoing((?,?)::taler_amount,?,?,?,?)
         """)
-        val executionTime = paymentData.executionTime.toDbMicros()
-            ?: throw Exception("Could not convert outgoing payment 
execution_time to microseconds")
+        val executionTime = paymentData.executionTime.micros()
         stmt.setLong(1, paymentData.amount.value)
         stmt.setInt(2, paymentData.amount.frac)
         stmt.setString(3, paymentData.wireTransferSubject)
@@ -72,10 +71,8 @@ class PaymentDAO(private val db: Database) {
             SELECT out_found, out_tx_id, out_bounce_id
             FROM 
register_incoming_and_bounce((?,?)::taler_amount,?,?,?,?,(?,?)::taler_amount,?)
         """)
-        val refundTimestamp = now.toDbMicros()
-            ?: throw Exception("Could not convert refund execution time from 
Instant.now() to microsends.")
-        val executionTime = paymentData.executionTime.toDbMicros()
-            ?: throw Exception("Could not convert payment execution time from 
Instant to microseconds.")
+        val refundTimestamp = now.micros()
+        val executionTime = paymentData.executionTime.micros()
         stmt.setLong(1, paymentData.amount.value)
         stmt.setInt(2, paymentData.amount.frac)
         stmt.setString(3, paymentData.wireTransferSubject)
@@ -109,8 +106,7 @@ class PaymentDAO(private val db: Database) {
             SELECT out_found, out_tx_id
             FROM register_incoming_and_talerable((?,?)::taler_amount,?,?,?,?,?)
         """)
-        val executionTime = paymentData.executionTime.toDbMicros()
-            ?: throw Exception("Could not convert payment execution time from 
Instant to microseconds.")
+        val executionTime = paymentData.executionTime.micros()
         stmt.setLong(1, paymentData.amount.value)
         stmt.setInt(2, paymentData.amount.frac)
         stmt.setString(3, paymentData.wireTransferSubject)

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]