gnunet-svn
[Top][All Lists]
Advanced

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

[libeufin] branch master updated: TWG POST /transfer.


From: gnunet
Subject: [libeufin] branch master updated: TWG POST /transfer.
Date: Thu, 21 Sep 2023 18:23:24 +0200

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

ms pushed a commit to branch master
in repository libeufin.

The following commit(s) were added to refs/heads/master by this push:
     new 15e29754 TWG POST /transfer.
15e29754 is described below

commit 15e29754ebfed4c65fdde94f05a4a898595d9775
Author: MS <ms@taler.net>
AuthorDate: Thu Sep 21 18:22:18 2023 +0200

    TWG POST /transfer.
    
    Completing the response by including the bank transaction ID.
---
 .../src/main/kotlin/tech/libeufin/bank/Database.kt | 74 ++++++++++++++++------
 .../kotlin/tech/libeufin/bank/talerWebHandlers.kt  |  2 +
 .../tech/libeufin/bank/talerWireGatewayHandlers.kt | 20 +++---
 bank/src/main/kotlin/tech/libeufin/bank/types.kt   | 16 +++--
 bank/src/test/kotlin/DatabaseTest.kt               |  2 +-
 bank/src/test/kotlin/TalerApiTest.kt               |  6 +-
 database-versioning/libeufin-bank-0001.sql         |  2 +-
 database-versioning/procedures.sql                 | 67 +++++++++++++-------
 8 files changed, 127 insertions(+), 62 deletions(-)

diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Database.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
index dff0b951..bac4c7eb 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
@@ -686,7 +686,13 @@ class Database(private val dbConfig: String) {
         return true
     }
 
-    // Values coming from the wallet.
+    /**
+     * Associates a reserve public key and an exchange to
+     * a Taler withdrawal.  Returns true on success, false
+     * otherwise.
+     *
+     * Checking for idempotency is entirely on the Kotlin side.
+     */
     fun talerWithdrawalSetDetails(
         opUuid: UUID,
         exchangePayto: String,
@@ -705,6 +711,9 @@ class Database(private val dbConfig: String) {
         return myExecute(stmt)
     }
 
+    /**
+     *
+     */
     fun talerWithdrawalConfirm(
         opUuid: UUID,
         timestamp: Long,
@@ -717,7 +726,8 @@ class Database(private val dbConfig: String) {
             SELECT
               out_nx_op,
               out_nx_exchange,
-              out_insufficient_funds
+              out_insufficient_funds,
+              out_already_confirmed_conflict
             FROM confirm_taler_withdrawal(?, ?, ?, ?, ?);
         """
         )
@@ -733,6 +743,7 @@ class Database(private val dbConfig: String) {
             if (it.getBoolean("out_nx_op")) return 
WithdrawalConfirmationResult.OP_NOT_FOUND
             if (it.getBoolean("out_nx_exchange")) return 
WithdrawalConfirmationResult.EXCHANGE_NOT_FOUND
             if (it.getBoolean("out_insufficient_funds")) return 
WithdrawalConfirmationResult.BALANCE_INSUFFICIENT
+            if (it.getBoolean("out_already_confirmed_conflict")) return 
WithdrawalConfirmationResult.CONFLICT
         }
         return WithdrawalConfirmationResult.SUCCESS
     }
@@ -899,40 +910,58 @@ class Database(private val dbConfig: String) {
             )
         }
     }
-
+    data class TalerTransferFromDb(
+        // Only used when this type if defined from a DB record
+        val timestamp: Long,
+        val debitTxRowId: Long,
+        val requestUid: String,
+        val amount: TalerAmount,
+        val exchangeBaseUrl: String,
+        val wtid: String,
+        val creditAccount: String
+    )
     // Gets a Taler transfer request, given its UID.
-    fun talerTransferGetFromUid(requestUid: String): TransferRequest? {
+    fun talerTransferGetFromUid(requestUid: String): TalerTransferFromDb? {
         reconnect()
         val stmt = prepare("""
             SELECT
               wtid
-              ,(amount).val AS amount_value
-              ,(amount).frac AS amount_frac
               ,exchange_base_url
-              ,credit_account_payto
-              FROM taler_exchange_transfers
+              ,(tfr.amount).val AS amount_value
+              ,(tfr.amount).frac AS amount_frac
+              ,tfr.credit_account_payto
+              ,tfr.bank_transaction
+              ,txs.transaction_date AS timestamp
+              FROM taler_exchange_transfers AS tfr
+                JOIN bank_account_transactions AS txs
+                  ON bank_transaction=txs.bank_transaction_id
               WHERE request_uid = ?;
         """)
         stmt.setString(1, requestUid)
         val res = stmt.executeQuery()
         res.use {
             if (!it.next()) return null
-            return TransferRequest(
+            return TalerTransferFromDb(
                 wtid = it.getString("wtid"),
                 amount = TalerAmount(
                     value = it.getLong("amount_value"),
                     frac = it.getInt("amount_frac"),
                 ),
-                credit_account = it.getString("credit_account_payto"),
-                exchange_base_url = it.getString("exchange_base_url"),
-                request_uid = requestUid,
-                // FIXME: fix the following two after setting the 
bank_transaction_id on this row.
-                row_id = 0L,
-                timestamp = 0L
+                creditAccount = it.getString("credit_account_payto"),
+                exchangeBaseUrl = it.getString("exchange_base_url"),
+                requestUid = requestUid,
+                debitTxRowId = it.getLong("bank_transaction"),
+                timestamp = it.getLong("timestamp")
             )
         }
     }
 
+    data class TalerTransferDbResult(
+        val txResult: BankTransactionResult,
+        // Row ID of the debit bank transaction
+        // of a successful case.  Null upon errors
+        val txRowId: Long? = null
+    )
     /**
      * This function calls the SQL function that (1) inserts the TWG
      * requests details into the database and (2) performs the actual
@@ -951,13 +980,14 @@ class Database(private val dbConfig: String) {
         acctSvcrRef: String = "not used",
         pmtInfId: String = "not used",
         endToEndId: String = "not used",
-        ): BankTransactionResult {
+        ): TalerTransferDbResult {
         reconnect()
         // FIXME: future versions should return the exchange's latest bank 
transaction ID
         val stmt = prepare("""
             SELECT
               out_exchange_balance_insufficient
               ,out_nx_creditor
+              ,out_tx_row_id
               FROM
                 taler_transfer (
                   ?,
@@ -988,9 +1018,15 @@ class Database(private val dbConfig: String) {
         res.use {
             if (!it.next())
                 throw internalServerError("SQL function taler_transfer did not 
return anything.")
-            if (it.getBoolean("out_exchange_balance_insufficient")) return 
BankTransactionResult.CONFLICT
-            if (it.getBoolean("out_nx_creditor")) return 
BankTransactionResult.NO_CREDITOR
-            return BankTransactionResult.SUCCESS
+            if (it.getBoolean("out_nx_creditor"))
+                return TalerTransferDbResult(BankTransactionResult.NO_CREDITOR)
+            if (it.getBoolean("out_exchange_balance_insufficient"))
+                return TalerTransferDbResult(BankTransactionResult.CONFLICT)
+            val txRowId = it.getLong("out_tx_row_id")
+            return TalerTransferDbResult(
+                txResult = BankTransactionResult.SUCCESS,
+                txRowId = txRowId
+            )
         }
     }
 }
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/talerWebHandlers.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/talerWebHandlers.kt
index f449cb54..c27ea435 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/talerWebHandlers.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/talerWebHandlers.kt
@@ -163,6 +163,8 @@ fun Routing.talerWebHandlers() {
                     hint = "Exchange to withdraw from not found",
                     talerEc = TalerErrorCode.TALER_EC_END // FIXME
                 )
+            WithdrawalConfirmationResult.CONFLICT ->
+                throw internalServerError("Bank didn't check for idempotency")
             WithdrawalConfirmationResult.SUCCESS ->
                 call.respondText(
                     "{}",
diff --git 
a/bank/src/main/kotlin/tech/libeufin/bank/talerWireGatewayHandlers.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/talerWireGatewayHandlers.kt
index d6ef6910..55a97c24 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/talerWireGatewayHandlers.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/talerWireGatewayHandlers.kt
@@ -77,17 +77,13 @@ fun Routing.talerWireGatewayHandlers() {
         if (maybeDoneAlready != null) {
             val isIdempotent =
                 maybeDoneAlready.amount == req.amount
-                        && maybeDoneAlready.credit_account == 
req.credit_account
-                        && maybeDoneAlready.exchange_base_url == 
req.exchange_base_url
+                        && maybeDoneAlready.creditAccount == req.credit_account
+                        && maybeDoneAlready.exchangeBaseUrl == 
req.exchange_base_url
                         && maybeDoneAlready.wtid == req.wtid
             if (isIdempotent) {
-                val timestamp = maybeDoneAlready.timestamp
-                    ?: throw internalServerError("Timestamp not found on 
idempotent request")
-                val rowId = maybeDoneAlready.row_id
-                    ?: throw internalServerError("Row ID not found on 
idempotent request")
                 call.respond(TransferResponse(
-                    timestamp = timestamp,
-                    row_id = rowId
+                    timestamp = maybeDoneAlready.timestamp,
+                    row_id = maybeDoneAlready.debitTxRowId
                 ))
                 return@post
             }
@@ -105,19 +101,21 @@ fun Routing.talerWireGatewayHandlers() {
             exchangeBankAccountId = exchangeBankAccount.expectRowId(),
             timestamp = transferTimestamp
         )
-        if (dbRes == Database.BankTransactionResult.CONFLICT)
+        if (dbRes.txResult == Database.BankTransactionResult.CONFLICT)
             throw conflict(
                 "Insufficient balance for exchange",
                 TalerErrorCode.TALER_EC_END // FIXME
             )
-        if (dbRes == Database.BankTransactionResult.NO_CREDITOR)
+        if (dbRes.txResult == Database.BankTransactionResult.NO_CREDITOR)
             throw notFound(
                 "Creditor account was not found",
                 TalerErrorCode.TALER_EC_END // FIXME
             )
+        val debitRowId = dbRes.txRowId
+            ?: throw internalServerError("Database did not return the debit tx 
row ID")
         call.respond(TransferResponse(
             timestamp = transferTimestamp,
-            row_id = 0 // FIXME!
+            row_id = debitRowId
         ))
         return@post
     }
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/types.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/types.kt
index cda4664d..bfe6d2fb 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/types.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/types.kt
@@ -442,7 +442,16 @@ enum class WithdrawalConfirmationResult {
     SUCCESS,
     OP_NOT_FOUND,
     EXCHANGE_NOT_FOUND,
-    BALANCE_INSUFFICIENT
+    BALANCE_INSUFFICIENT,
+    /**
+     * This state indicates that the withdrawal was already
+     * confirmed BUT Kotlin did not detect it and still invoked
+     * the SQL procedure to confirm the withdrawal.  This is
+     * conflictual because only Kotlin is responsible to check
+     * for idempotency, and this state witnesses a failure in
+     * this regard.
+     */
+    CONFLICT
 }
 
 // GET /config response from the Taler Integration API.
@@ -556,10 +565,7 @@ data class TransferRequest(
     val amount: TalerAmount,
     val exchange_base_url: String,
     val wtid: String,
-    val credit_account: String,
-    // Only used when this type if defined from a DB record
-    val timestamp: Long? = null, // when this request got finalized with a 
wire transfer
-    val row_id: Long? = null // DB row ID of this record
+    val credit_account: String
 )
 
 // TWG's response to merchant payouts
diff --git a/bank/src/test/kotlin/DatabaseTest.kt 
b/bank/src/test/kotlin/DatabaseTest.kt
index 9083d870..322903d6 100644
--- a/bank/src/test/kotlin/DatabaseTest.kt
+++ b/bank/src/test/kotlin/DatabaseTest.kt
@@ -100,7 +100,7 @@ class DatabaseTest {
             exchangeBankAccountId = 1L,
             timestamp = getNowUs()
         )
-        assert(res == Database.BankTransactionResult.SUCCESS)
+        assert(res.txResult == Database.BankTransactionResult.SUCCESS)
     }
 
     @Test
diff --git a/bank/src/test/kotlin/TalerApiTest.kt 
b/bank/src/test/kotlin/TalerApiTest.kt
index 757d1e82..5d53ff6d 100644
--- a/bank/src/test/kotlin/TalerApiTest.kt
+++ b/bank/src/test/kotlin/TalerApiTest.kt
@@ -70,19 +70,21 @@ class TalerApiTest {
                       "credit_account": "BAR-IBAN-ABC"
                     }
                 """.trimIndent()
-            client.post("/accounts/foo/taler-wire-gateway/transfer") {
+            val resp = 
client.post("/accounts/foo/taler-wire-gateway/transfer") {
                 basicAuth("foo", "pw")
                 contentType(ContentType.Application.Json)
                 expectSuccess = true
                 setBody(req)
             }
+            // println(resp.bodyAsText())
             // check idempotency
-            client.post("/accounts/foo/taler-wire-gateway/transfer") {
+            val idemResp = 
client.post("/accounts/foo/taler-wire-gateway/transfer") {
                 basicAuth("foo", "pw")
                 contentType(ContentType.Application.Json)
                 expectSuccess = true
                 setBody(req)
             }
+            // println(idemResp.bodyAsText())
             // Trigger conflict due to reused request_uid
             val r = client.post("/accounts/foo/taler-wire-gateway/transfer") {
                 basicAuth("foo", "pw")
diff --git a/database-versioning/libeufin-bank-0001.sql 
b/database-versioning/libeufin-bank-0001.sql
index e288f846..e09b8367 100644
--- a/database-versioning/libeufin-bank-0001.sql
+++ b/database-versioning/libeufin-bank-0001.sql
@@ -370,7 +370,7 @@ CREATE TABLE IF NOT EXISTS taler_exchange_transfers
   ,exchange_base_url TEXT NOT NULL
   ,credit_account_payto TEXT NOT NULL
   ,amount taler_amount NOT NULL
-  ,bank_transaction BIGINT UNIQUE -- NOT NULL FIXME: make this not null.
+  ,bank_transaction BIGINT UNIQUE NOT NULL
     REFERENCES bank_account_transactions(bank_transaction_id)
       ON DELETE RESTRICT
       ON UPDATE RESTRICT
diff --git a/database-versioning/procedures.sql 
b/database-versioning/procedures.sql
index 3968389e..25cc8df8 100644
--- a/database-versioning/procedures.sql
+++ b/database-versioning/procedures.sql
@@ -98,7 +98,8 @@ CREATE OR REPLACE FUNCTION taler_transfer(
   IN in_payment_information_id TEXT,
   IN in_end_to_end_id TEXT,
   OUT out_exchange_balance_insufficient BOOLEAN,
-  OUT out_nx_creditor BOOLEAN
+  OUT out_nx_creditor BOOLEAN,
+  OUT out_tx_row_id BIGINT
 )
 LANGUAGE plpgsql
 AS $$
@@ -106,23 +107,13 @@ DECLARE
 maybe_balance_insufficient BOOLEAN;
 receiver_bank_account_id BIGINT;
 payment_subject TEXT;
+exchange_debit_tx_id BIGINT;
 BEGIN
 
-INSERT
-  INTO taler_exchange_transfers (
-    request_uid,
-    wtid,
-    exchange_base_url,
-    credit_account_payto,
-    amount
-    -- FIXME: this needs the bank transaction row ID here.
-) VALUES (
-  in_request_uid,
-  in_wtid,
-  in_exchange_base_url,
-  in_credit_account_payto,
-  in_amount
-);
+-- First creating the bank transaction, then updating
+-- the transfer request table, because that needs to point
+-- at the bank transaction.
+
 SELECT
   bank_account_id
   INTO receiver_bank_account_id
@@ -137,8 +128,11 @@ out_nx_creditor=FALSE;
 SELECT CONCAT(in_wtid, ' ', in_exchange_base_url)
   INTO payment_subject;
 SELECT
-  out_balance_insufficient
-  INTO maybe_balance_insufficient
+  out_balance_insufficient,
+  out_debit_row_id
+  INTO
+    maybe_balance_insufficient,
+    exchange_debit_tx_id
   FROM bank_wire_transfer(
     receiver_bank_account_id,
     in_exchange_bank_account_id,
@@ -154,6 +148,23 @@ THEN
   out_exchange_balance_insufficient=TRUE;
 END IF;
 out_exchange_balance_insufficient=FALSE;
+INSERT
+  INTO taler_exchange_transfers (
+    request_uid,
+    wtid,
+    exchange_base_url,
+    credit_account_payto,
+    amount,
+    bank_transaction
+) VALUES (
+  in_request_uid,
+  in_wtid,
+  in_exchange_base_url,
+  in_credit_account_payto,
+  in_amount,
+  exchange_debit_tx_id
+);
+out_tx_row_id = exchange_debit_tx_id;
 END $$;
 COMMENT ON FUNCTION taler_transfer(
   text,
@@ -182,7 +193,8 @@ CREATE OR REPLACE FUNCTION confirm_taler_withdrawal(
   -- it conflicts with the return column of the called
   -- function that moves the funds.  FIXME?
   OUT out_insufficient_funds BOOLEAN,
-  OUT out_nx_exchange BOOLEAN
+  OUT out_nx_exchange BOOLEAN,
+  OUT out_already_confirmed_conflict BOOLEAN
 )
 LANGUAGE plpgsql
 AS $$
@@ -217,8 +229,10 @@ END IF;
 out_nx_op=FALSE;
 IF (confirmation_done_local)
 THEN
-  RETURN; -- nothing to do, idempotentially returning.
+  out_already_confirmed_conflict=TRUE
+  RETURN; -- Kotlin should have checked for idempotency before reaching here!
 END IF;
+out_already_confirmed_conflict=FALSE;
 -- exists and wasn't confirmed, do it.
 UPDATE taler_withdrawal_operations
   SET confirmation_done = true
@@ -269,7 +283,9 @@ CREATE OR REPLACE FUNCTION bank_wire_transfer(
   IN in_end_to_end_id TEXT,
   OUT out_nx_creditor BOOLEAN,
   OUT out_nx_debtor BOOLEAN,
-  OUT out_balance_insufficient BOOLEAN
+  OUT out_balance_insufficient BOOLEAN,
+  OUT out_credit_row_id BIGINT,
+  OUT out_debit_row_id BIGINT
 )
 LANGUAGE plpgsql
 AS $$
@@ -292,6 +308,8 @@ will_debtor_have_debt BOOLEAN;
 will_creditor_have_debt BOOLEAN;
 amount_at_least_debit BOOLEAN;
 potential_balance_ok BOOLEAN;
+new_debit_row_id BIGINT;
+new_credit_row_id BIGINT;
 BEGIN
 -- check debtor exists.
 SELECT
@@ -447,7 +465,8 @@ VALUES (
   in_end_to_end_id,
   'debit',
   in_debtor_account_id
-);
+) RETURNING bank_transaction_id INTO new_debit_row_id;
+out_debit_row_id=new_debit_row_id;
 
 -- debtor side:
 INSERT INTO bank_account_transactions (
@@ -477,7 +496,9 @@ VALUES (
   in_end_to_end_id, -- does this interest the receiving party?
   'credit',
   in_creditor_account_id
-);
+) RETURNING bank_transaction_id INTO new_credit_row_id;
+out_credit_row_id=new_credit_row_id;
+
 -- checks and balances set up, now update bank accounts.
 UPDATE bank_accounts
 SET

-- 
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]