summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt4
-rw-r--r--sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt35
-rw-r--r--sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt10
3 files changed, 39 insertions, 10 deletions
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt
index f1fbe561..ef6d3704 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt
@@ -422,6 +422,8 @@ object TalerWithdrawalsTable : LongIdTable() {
* the payment arrived at the exchange's bank yet.
*/
val transferDone = bool("transferDone").default(false)
+ val reservePub = text("reservePub").nullable()
+ val selectedExchangePayto = text("selectedExchangePayto").nullable()
}
class TalerWithdrawalEntity(id: EntityID<Long>) : LongEntity(id) {
@@ -429,6 +431,8 @@ class TalerWithdrawalEntity(id: EntityID<Long>) : LongEntity(id) {
var wopid by TalerWithdrawalsTable.wopid
var selectionDone by TalerWithdrawalsTable.selectionDone
var transferDone by TalerWithdrawalsTable.transferDone
+ var reservePub by TalerWithdrawalsTable.reservePub
+ var selectedExchangePayto by TalerWithdrawalsTable.selectedExchangePayto
}
object BankAccountReportsTable : IntIdTable() {
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
index bc826211..ad90770e 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
@@ -70,6 +70,8 @@ import io.ktor.http.*
import io.ktor.http.content.*
import io.ktor.request.*
import io.ktor.util.date.*
+import kotlinx.coroutines.newSingleThreadContext
+import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
import tech.libeufin.util.*
import tech.libeufin.util.ebics_h004.EbicsResponse
import tech.libeufin.util.ebics_h004.EbicsTypes
@@ -364,6 +366,8 @@ suspend inline fun <reified T : Any> ApplicationCall.receiveJson(): T {
}
}
+val singleThreadContext = newSingleThreadContext("DB")
+
fun serverMain(dbName: String, port: Int) {
execThrowableOrTerminate { dbCreateTables(dbName) }
val myLogger = logger
@@ -983,6 +987,7 @@ fun serverMain(dbName: String, port: Int) {
// At this point, the three actors exist and a new withdraw operation can be created.
TalerWithdrawalEntity.new {
// wopid is autogenerated, and momentarily the only column
+
}
}
/**
@@ -1042,30 +1047,46 @@ fun serverMain(dbName: String, port: Int) {
val wopid: String = ensureNonNull(call.parameters["wopid"])
val body = call.receiveJson<TalerWithdrawalConfirmation>()
- transaction {
+ newSuspendedTransaction(context = singleThreadContext) {
var wo = TalerWithdrawalEntity.find {
TalerWithdrawalsTable.wopid eq UUID.fromString(wopid)
}.firstOrNull() ?: throw SandboxError(
HttpStatusCode.NotFound, "Withdrawal operation $wopid not found."
)
- if (wo.transferDone) {
- throw SandboxError(
+ if (wo.selectionDone) {
+ if (wo.transferDone) {
+ logger.info("Wallet performs again this operation that was paid out earlier: idempotent")
+ return@newSuspendedTransaction
+ }
+ // reservePub+exchange selected but not payed: check consistency
+ if (body.reserve_pub != wo.reservePub) throw SandboxError(
HttpStatusCode.Conflict,
- "This withdraw operation was already funded. Aborting"
+ "Selecting a different reserve from the one already selected"
+ )
+ if (body.selected_exchange != wo.selectedExchangePayto) throw SandboxError(
+ HttpStatusCode.Conflict,
+ "Selecting a different exchange from the one already selected"
)
}
- if (wo.selectionDone) {
- logger.warn("This withdraw operation was already confirmed, but not funded. Trying again")
- }
+ // here only if (1) no selection done or (2) _only_ selection done:
+ // both ways no transfer must have happened.
+ SandboxAssert(!wo.transferDone, "Sandbox allowed paid but unselected reserve")
+
wireTransfer(
"sandbox-account-customer",
"sandbox-account-exchange",
"$currencyEnv:5",
body.reserve_pub
)
+ wo.reservePub = body.reserve_pub
+ wo.selectedExchangePayto = body.selected_exchange
wo.selectionDone = true
wo.transferDone = true
}
+ /**
+ * NOTE: is this always guaranteed to run AFTER the suspended
+ * transaction block above?
+ */
call.respond(object {
val transfer_done = true
})
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt
index 0a432451..3d5236a7 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt
@@ -1,14 +1,14 @@
package tech.libeufin.sandbox
import io.ktor.http.*
-import org.apache.http.HttpStatus
import org.jetbrains.exposed.sql.and
+import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
+import org.jetbrains.exposed.sql.transactions.experimental.suspendedTransactionAsync
import org.jetbrains.exposed.sql.transactions.transaction
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import tech.libeufin.util.*
import java.math.BigDecimal
-import kotlin.system.exitProcess
private val logger: Logger = LoggerFactory.getLogger("tech.libeufin.sandbox")
@@ -117,12 +117,16 @@ fun historyForAccount(bankAccount: BankAccountEntity): MutableList<RawPayment> {
return history
}
+/**
+ * https://github.com/JetBrains/Exposed/wiki/Transactions#working-with-coroutines
+ * https://medium.com/androiddevelopers/threading-models-in-coroutines-and-android-sqlite-api-6cab11f7eb90
+ */
fun wireTransfer(
debitAccount: String, creditAccount: String,
amount: String, subjectArg: String
) {
- // check accounts exist
transaction {
+ // check accounts exist
val credit = BankAccountEntity.find {
BankAccountsTable.label eq creditAccount
}.firstOrNull() ?: run {