commit 467711b15cc899d37e777ffbe096f95c25f6b82b
parent b638ddb63240f54f5ae2e710a151abd366ce0716
Author: Antoine A <>
Date: Thu, 7 Dec 2023 13:16:39 +0000
Fix parsing incoming transaction subject
Diffstat:
11 files changed, 55 insertions(+), 90 deletions(-)
diff --git a/README b/README
@@ -9,7 +9,7 @@ be available before trying the installation:
- make
- python3-venv
-- openjdk-17-jre
+- openjdk-17-jre-headless
Run the following steps to install LibEuFin:
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Constants.kt b/bank/src/main/kotlin/tech/libeufin/bank/Constants.kt
@@ -39,7 +39,7 @@ val RESERVED_ACCOUNTS = setOf("admin", "bank")
const val MAX_BODY_LENGTH: Long = 4 * 1024 // 4kB
// API version
-const val COREBANK_API_VERSION: String = "2:0:2"
+const val COREBANK_API_VERSION: String = "2:1:2"
const val CONVERSION_API_VERSION: String = "0:0:0"
const val INTEGRATION_API_VERSION: String = "1:0:1"
-const val WIRE_GATEWAY_API_VERSION: String = "0:0:0"
-\ No newline at end of file
+const val WIRE_GATEWAY_API_VERSION: String = "0:1:0"
+\ No newline at end of file
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Metadata.kt b/bank/src/main/kotlin/tech/libeufin/bank/Metadata.kt
@@ -18,35 +18,13 @@
*/
package tech.libeufin.bank
-sealed interface TxMetadata {
- // TODO versioning ?
- companion object {
- fun parse(subject: String): TxMetadata? {
- // IncomingTxMetadata
- try {
- return IncomingTxMetadata(EddsaPublicKey(subject))
- } catch (e: Exception) { }
-
- // OutgoingTxMetadata
- try {
- val (wtid, exchangeBaseUrl) = subject.split(" ", limit=2)
- return OutgoingTxMetadata(ShortHashCode(wtid), ExchangeUrl(exchangeBaseUrl))
- } catch (e: Exception) { }
-
- // No well formed metadata
- return null
- }
-
- fun encode(metadata: TxMetadata): String {
- return when (metadata) {
- is IncomingTxMetadata -> "${metadata.reservePub}"
- is OutgoingTxMetadata -> "${metadata.wtid} ${metadata.exchangeBaseUrl.url}"
- }
- }
+private val PATTERN = Regex("[a-z0-9A-Z]{52}")
+
+fun parseIncomingTxMetadata(subject: String): EddsaPublicKey? {
+ val match = PATTERN.find(subject)?.value ?: return null;
+ try {
+ return EddsaPublicKey(match)
+ } catch (e: Exception) {
+ return null
}
-
- fun encode(): String = TxMetadata.encode(this)
-}
-
-data class IncomingTxMetadata(val reservePub: EddsaPublicKey): TxMetadata
-data class OutgoingTxMetadata(val wtid: ShortHashCode, val exchangeBaseUrl: ExchangeUrl): TxMetadata
-\ No newline at end of file
+}
+\ 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
@@ -127,7 +127,7 @@ class ExchangeDAO(private val db: Database) {
login: String,
now: Instant
): TransferResult = db.serializable { conn ->
- val subject = OutgoingTxMetadata(req.wtid, req.exchange_base_url).encode()
+ val subject = "${req.wtid} ${req.exchange_base_url.url}"
val stmt = conn.prepareStatement("""
SELECT
out_debtor_not_found
@@ -192,7 +192,6 @@ class ExchangeDAO(private val db: Database) {
login: String,
now: Instant
): AddIncomingResult = db.serializable { conn ->
- val subject = IncomingTxMetadata(req.reserve_pub).encode()
val stmt = conn.prepareStatement("""
SELECT
out_creditor_not_found
@@ -211,7 +210,7 @@ class ExchangeDAO(private val db: Database) {
""")
stmt.setBytes(1, req.reserve_pub.raw)
- stmt.setString(2, subject)
+ stmt.setString(2, "Manual incoming ${req.reserve_pub}")
stmt.setLong(3, req.amount.value)
stmt.setInt(4, req.amount.frac)
stmt.setString(5, req.debit_account.canonical)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/TransactionDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/TransactionDAO.kt
@@ -82,33 +82,29 @@ class TransactionDAO(private val db: Database) {
val creditRowId = it.getLong("out_credit_row_id")
val debitAccountId = it.getLong("out_debit_bank_account_id")
val debitRowId = it.getLong("out_debit_row_id")
- val metadata = TxMetadata.parse(subject)
val exchangeCreditor = it.getBoolean("out_creditor_is_exchange")
val exchangeDebtor = it.getBoolean("out_debtor_is_exchange")
if (exchangeCreditor && exchangeDebtor) {
- val kind = when (metadata) {
- is IncomingTxMetadata -> "an incoming taler "
- is OutgoingTxMetadata -> "an outgoing taler"
- null -> "a common"
- };
- logger.warn("exchange account $exchangeDebtor sent $kind transaction to exchange account $exchangeCreditor, this should never happens and is not bounced to prevent bouncing loop")
+ logger.warn("exchange account $exchangeDebtor sent a manual transaction to exchange account $exchangeCreditor, this should never happens and is not bounced to prevent bouncing loop, may fail in the future")
} else if (exchangeCreditor) {
- val bounce = if (metadata is IncomingTxMetadata) {
+ val reservePub = parseIncomingTxMetadata(subject)
+ val bounceCause = if (reservePub != null) {
val registered = conn.prepareStatement("CALL register_incoming(?, ?)").run {
- setBytes(1, metadata.reservePub.raw)
+ setBytes(1, reservePub.raw)
setLong(2, creditRowId)
executeProcedureViolation()
}
if (!registered) {
logger.warn("exchange account $creditAccountId received an incoming taler transaction $creditRowId with an already used reserve public key")
-
+ "reserve public key reuse"
+ } else {
+ null
}
- !registered
} else {
- logger.warn("exchange account $creditAccountId received a transaction $creditRowId with malformed metadata")
- true
+ logger.warn("exchange account $creditAccountId received a manual transaction $creditRowId with malformed metadata")
+ "malformed metadata"
}
- if (bounce) {
+ if (bounceCause != null) {
// No error can happens because an opposite transaction already took place in the same transaction
conn.prepareStatement("""
SELECT bank_wire_transfer(
@@ -119,7 +115,7 @@ class TransactionDAO(private val db: Database) {
).run {
setLong(1, debitAccountId)
setLong(2, creditAccountId)
- setString(3, "Bounce $creditRowId") // TODO better subject
+ setString(3, "Bounce $creditRowId: $bounceCause")
setLong(4, amount.value)
setInt(5, amount.frac)
setLong(6, now)
@@ -127,22 +123,7 @@ class TransactionDAO(private val db: Database) {
}
}
} else if (exchangeDebtor) {
- if (metadata is OutgoingTxMetadata) {
- val registered = conn.prepareStatement("CALL register_outgoing(NULL, ?, ?, ?, ?, ?, ?)").run {
- setBytes(1, metadata.wtid.raw)
- setString(2, metadata.exchangeBaseUrl.url)
- setLong(3, debitAccountId)
- setLong(4, creditAccountId)
- setLong(5, debitRowId)
- setLong(6, creditRowId)
- executeProcedureViolation()
- }
- if (!registered) {
- logger.warn("exchange account $debitAccountId sent an outgoing taler transaction $debitRowId with an already used withdraw ID, use the API to catch this error")
- }
- } else {
- logger.warn("exchange account $debitAccountId sent a transaction $debitRowId with malformed metadata, use the API instead")
- }
+ logger.warn("exchange account $debitAccountId sent a manual transaction $debitRowId which will not be recorderd as a taler outgoing transaction, use the API instead")
}
BankTransactionResult.Success(debitRowId)
}
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/WithdrawalDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/WithdrawalDAO.kt
@@ -103,7 +103,6 @@ class WithdrawalDAO(private val db: Database) {
exchangePayto: IbanPayTo,
reservePub: EddsaPublicKey
): WithdrawalSelectionResult = db.serializable { conn ->
- val subject = IncomingTxMetadata(reservePub).encode()
val stmt = conn.prepareStatement("""
SELECT
out_no_op,
@@ -117,7 +116,7 @@ class WithdrawalDAO(private val db: Database) {
)
stmt.setObject(1, uuid)
stmt.setBytes(2, reservePub.raw)
- stmt.setString(3, subject)
+ stmt.setString(3, "Taler withdrawal $reservePub")
stmt.setString(4, exchangePayto.canonical)
stmt.executeQuery().use {
when {
diff --git a/bank/src/test/kotlin/CoreBankApiTest.kt b/bank/src/test/kotlin/CoreBankApiTest.kt
@@ -755,9 +755,9 @@ class CoreBankTransactionsApiTest {
assertBalance("exchange", "+KUDOS:0")
tx("merchant", "KUDOS:1", "exchange", "") // Bounce common to transaction
tx("merchant", "KUDOS:1", "exchange", "Malformed") // Bounce malformed transaction
- val reserve_pub = randShortHashCode();
- tx("merchant", "KUDOS:1", "exchange", IncomingTxMetadata(reserve_pub).encode()) // Accept incoming
- tx("merchant", "KUDOS:1", "exchange", IncomingTxMetadata(reserve_pub).encode()) // Bounce reserve_pub reuse
+ val reserve_pub = randEddsaPublicKey();
+ tx("merchant", "KUDOS:1", "exchange", randIncomingSubject(reserve_pub)) // Accept incoming
+ tx("merchant", "KUDOS:1", "exchange", randIncomingSubject(reserve_pub)) // Bounce reserve_pub reuse
assertBalance("merchant", "-KUDOS:1")
assertBalance("exchange", "+KUDOS:1")
@@ -768,8 +768,8 @@ class CoreBankTransactionsApiTest {
tx("exchange", "KUDOS:1", "merchant", "Malformed") // Warn malformed transaction
val wtid = randShortHashCode()
val exchange = ExchangeUrl("http://exchange.example.com/")
- tx("exchange", "KUDOS:1", "merchant", OutgoingTxMetadata(wtid, exchange).encode()) // Accept outgoing
- tx("exchange", "KUDOS:1", "merchant", OutgoingTxMetadata(wtid, exchange).encode()) // Warn wtid reuse
+ tx("exchange", "KUDOS:1", "merchant", randOutgoingSubject(wtid, exchange)) // Accept outgoing
+ tx("exchange", "KUDOS:1", "merchant", randOutgoingSubject(wtid, exchange)) // Warn wtid reuse
assertBalance("merchant", "+KUDOS:3")
assertBalance("exchange", "-KUDOS:3")
}
diff --git a/bank/src/test/kotlin/RevenueApiTest.kt b/bank/src/test/kotlin/RevenueApiTest.kt
@@ -39,16 +39,16 @@ class RevenueApiTest {
ids = { it.incoming_transactions.map { it.row_id } },
registered = listOf(
{
- // Transactions using clean add incoming logic
+ // Transactions using clean transfer logic
transfer("KUDOS:10")
- },
- {
- // Transactions using raw bank transaction logic
- tx("exchange", "KUDOS:10", "merchant", OutgoingTxMetadata(randShortHashCode(), ExchangeUrl("http://exchange.example.com/")).encode())
}
),
ignored = listOf(
{
+ // Ignore manual incoming transaction
+ tx("exchange", "KUDOS:10", "merchant", "${randShortHashCode()} http://exchange.example.com/")
+ },
+ {
// Ignore malformed incoming transaction
tx("merchant", "KUDOS:10", "exchange", "ignored")
},
diff --git a/bank/src/test/kotlin/WireGatewayApiTest.kt b/bank/src/test/kotlin/WireGatewayApiTest.kt
@@ -140,7 +140,7 @@ class WireGatewayApiTest {
},
{
// Transactions using raw bank transaction logic
- tx("merchant", "KUDOS:10", "exchange", IncomingTxMetadata(randShortHashCode()).encode())
+ tx("merchant", "KUDOS:10", "exchange", "history test with ${randShortHashCode()} reserve pub")
},
{
// Transaction using withdraw logic
@@ -175,13 +175,13 @@ class WireGatewayApiTest {
{
// Transactions using clean add incoming logic
transfer("KUDOS:10")
- },
- {
- // Transactions using raw bank transaction logic
- tx("exchange", "KUDOS:10", "merchant", OutgoingTxMetadata(randShortHashCode(), ExchangeUrl("http://exchange.example.com/")).encode())
}
),
ignored = listOf(
+ {
+ // gnore manual incoming transaction
+ tx("exchange", "KUDOS:10", "merchant", "${randShortHashCode()} http://exchange.example.com/")
+ },
{
// Ignore malformed incoming transaction
tx("merchant", "KUDOS:10", "exchange", "ignored")
diff --git a/bank/src/test/kotlin/helpers.kt b/bank/src/test/kotlin/helpers.kt
@@ -411,4 +411,12 @@ fun randBase32Crockford(lenght: Int) = Base32Crockford.encode(randBytes(lenght))
fun randHashCode(): HashCode = HashCode(randBase32Crockford(64))
fun randShortHashCode(): ShortHashCode = ShortHashCode(randBase32Crockford(32))
-fun randEddsaPublicKey(): EddsaPublicKey = EddsaPublicKey(randBase32Crockford(32))
-\ No newline at end of file
+fun randEddsaPublicKey(): EddsaPublicKey = EddsaPublicKey(randBase32Crockford(32))
+
+fun randIncomingSubject(reservePub: EddsaPublicKey): String {
+ return "$reservePub"
+}
+
+fun randOutgoingSubject(wtid: ShortHashCode, url: ExchangeUrl): String {
+ return "$wtid $url"
+}
+\ No newline at end of file
diff --git a/bank/src/test/kotlin/routines.kt b/bank/src/test/kotlin/routines.kt
@@ -148,7 +148,7 @@ inline suspend fun <reified B> ApplicationTestBuilder.historyRoutine(
}
launch { // Check polling timeout
assertTime(200, 300) {
- history("delta=1&start=${id+10}&long_poll_ms=200")
+ history("delta=1&start=${id+nbTotal*3}&long_poll_ms=200")
.assertNoContent()
}
}