[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.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [libeufin] branch master updated: TWG POST /transfer.,
gnunet <=