gnunet-svn
[Top][All Lists]
Advanced

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

[libeufin] branch master updated: nexus submit


From: gnunet
Subject: [libeufin] branch master updated: nexus submit
Date: Fri, 27 Oct 2023 16:04:08 +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 eb2f11f5 nexus submit
eb2f11f5 is described below

commit eb2f11f533bdde5b0c97335b025fd69847412bb5
Author: MS <ms@taler.net>
AuthorDate: Fri Oct 27 16:01:57 2023 +0200

    nexus submit
    
    Fetching the unsubmitted payment initiations from the
    database and submit them, in the loop or only once, according
    to the configuration.
---
 contrib/libeufin-nexus.conf                        |   2 +-
 .../src/main/kotlin/tech/libeufin/nexus/DbInit.kt  |   7 +
 .../main/kotlin/tech/libeufin/nexus/EbicsSetup.kt  |  14 +-
 .../main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt | 142 +++++++++++++++++++--
 nexus/src/test/kotlin/ConfigLoading.kt             |  26 ++++
 5 files changed, 170 insertions(+), 21 deletions(-)

diff --git a/contrib/libeufin-nexus.conf b/contrib/libeufin-nexus.conf
index 5db6b00b..e83a2f62 100644
--- a/contrib/libeufin-nexus.conf
+++ b/contrib/libeufin-nexus.conf
@@ -21,7 +21,7 @@ PARTNER_ID = myorg
 SYSTEM_ID = banksys
 
 # Name given by the bank to the bank account driven by Nexus.
-ACCOUNT_NUMBER = DE1234567890
+ACCOUNT_NUMBER = payto://iban/BIC/DE1234567890?receiver-name=Nexus-User
 
 # File that holds the bank EBICS keys.
 BANK_PUBLIC_KEYS_FILE = enc-auth-keys.json
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DbInit.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/DbInit.kt
index b973cdf2..d170eee0 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/DbInit.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/DbInit.kt
@@ -7,6 +7,13 @@ import tech.libeufin.util.initializeDatabaseTables
 import tech.libeufin.util.resetDatabaseTables
 import kotlin.system.exitProcess
 
+/**
+ * Runs the argument and fails the process, if that throws
+ * an exception.
+ *
+ * @param getLambda function that might return a value.
+ * @return the value from getLambda.
+ */
 fun <T>doOrFail(getLambda: () -> T): T =
     try {
         getLambda()
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt
index 85b47df3..f79fa7ab 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt
@@ -264,7 +264,7 @@ suspend fun doKeysRequestAndUpdateState(
  * @param configFile location of the configuration entry point.
  * @return internal representation of the configuration.
  */
-private fun extractEbicsConfig(configFile: String?): EbicsSetupConfig {
+fun extractEbicsConfig(configFile: String?): EbicsSetupConfig {
     val config = loadConfigOrFail(configFile)
     // Checking the config.
     val cfg = try {
@@ -327,16 +327,16 @@ class EbicsSetup: CliktCommand("Set up the EBICS 
subscriber") {
      * This function collects the main steps of setting up an EBICS access.
      */
     override fun run() {
-        val cfg = extractEbicsConfig(this.configFile)
+        val cfg = doOrFail { extractEbicsConfig(this.configFile) }
         if (checkFullConfig) {
             doOrFail {
-                cfg.config.requireNumber("nexus-ebics-submit", 
"frequency").apply {
-                    if (this < 0) throw Exception("section 
'nexus-ebics-submit' has negative frequency")
+                cfg.config.requireString("nexus-ebics-submit", 
"frequency").apply {
+                    checkFrequency(this)
                 }
-                cfg.config.requireNumber("nexus-ebics-fetch", 
"frequency").apply {
-                    if (this < 0) throw Exception("section 'nexus-ebics-fetch' 
has negative frequency")
+                cfg.config.requireString("nexus-ebics-fetch", 
"frequency").apply {
+                    checkFrequency(this)
                 }
-                cfg.config.requirePath("nexus-ebics-fetch", 
"statement-log-directory")
+                cfg.config.requirePath("nexus-ebics-fetch", 
"statement_log_directory")
                 cfg.config.requireNumber("nexus-httpd", "port")
                 cfg.config.requirePath("nexus-httpd", "unixpath")
                 cfg.config.requireString("nexus-httpd", "serve")
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt
index 3eb1e6a4..c25622d1 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt
@@ -22,9 +22,13 @@ package tech.libeufin.nexus
 import com.github.ajalt.clikt.core.CliktCommand
 import com.github.ajalt.clikt.parameters.options.option
 import io.ktor.client.*
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.time.delay
 import tech.libeufin.nexus.ebics.submitPayment
-import tech.libeufin.util.IbanPayto
+import tech.libeufin.util.getDatabaseName
 import tech.libeufin.util.parsePayto
+import java.util.*
+import kotlin.concurrent.fixedRateTimer
 import kotlin.system.exitProcess
 
 /**
@@ -46,14 +50,14 @@ private suspend fun submitInitiatedPayment(
     cfg: EbicsSetupConfig,
     clientPrivateKeysFile: ClientPrivateKeysFile,
     bankPublicKeysFile: BankPublicKeysFile,
-    initiatedPayment: InitiatedPayment,
-    debtor: IbanPayto
+    initiatedPayment: InitiatedPayment
 ): Boolean {
     val creditor = parsePayto(initiatedPayment.creditPaytoUri)
     if (creditor?.receiverName == null) {
         logger.error("Won't create pain.001 without the receiver name")
         return false
     }
+    val debtor = cfg.accountNumber
     if (debtor.bic == null || debtor.receiverName == null) {
         logger.error("Won't create pain.001 without the debtor BIC and name")
         return false
@@ -74,6 +78,98 @@ private suspend fun submitInitiatedPayment(
     return true
 }
 
+/**
+ * Converts human-readable duration in how many seconds.  Supports
+ * the suffixes 's' (seconds), 'm' (minute), 'h' (hours).  A valid
+ * duration is therefore, for example, Nm, where N is the number of
+ * minutes.
+ *
+ * @param trimmed duration
+ * @return how many seconds is the duration input, or null if the input
+ *         is not valid.
+ */
+fun getFrequencyInSeconds(humanFormat: String): Int? {
+    val trimmed = humanFormat.trim()
+    if (trimmed.isEmpty()) {
+        logger.error("Input was empty")
+        return null
+    }
+    val lastChar = trimmed.last()
+    val howManySeconds: Int = when (lastChar) {
+        's' -> {1}
+        'm' -> {60}
+        'h' -> {60 * 60}
+        else -> {
+            logger.error("Duration symbol not one of s, m, h.  '$lastChar' was 
found instead")
+            return null
+        }
+    }
+    val maybeNumber = trimmed.dropLast(1)
+    val howMany = try {
+        maybeNumber.toInt()
+    } catch (e: Exception) {
+        logger.error("Prefix was not a valid input: '$maybeNumber'")
+        return null
+    }
+    if (howMany == 0) return 0
+    val ret = howMany * howManySeconds
+    if (howMany != ret / howManySeconds) {
+        logger.error("Result overflew")
+        return null
+    }
+    return ret
+}
+
+/**
+ * Sanity-checks the frequency found in the configuration and
+ * either returns it or fails the process.  Note: the returned
+ * value is also guaranteed to be non-negative.
+ *
+ * @param foundInConfig frequency value as found in the configuration.
+ * @return the duration in seconds of the value found in the configuration.
+ */
+fun checkFrequency(foundInConfig: String): Int {
+    val frequencySeconds = getFrequencyInSeconds(foundInConfig)
+    if (frequencySeconds == null) {
+        throw Exception("Invalid frequency value")
+    }
+    if (frequencySeconds < 0) {
+        throw Exception("Configuration error: cannot operate with a negative 
submit frequency ($foundInConfig)")
+    }
+    return frequencySeconds
+}
+
+private fun submitBatch(
+    cfg: EbicsSetupConfig,
+    db: Database,
+    httpClient: HttpClient,
+    clientKeys: ClientPrivateKeysFile,
+    bankKeys: BankPublicKeysFile
+) {
+    runBlocking {
+        db.initiatedPaymentsUnsubmittedGet(cfg.currency).forEach {
+            val submitted = submitInitiatedPayment(
+                httpClient,
+                cfg,
+                clientKeys,
+                bankKeys,
+                it.value
+            )
+            /**
+             * The following block tries to flag the initiated payment as 
submitted,
+             * but it does NOT fail the process if the flagging fails.  This 
way, we
+             * do NOT block other payments to be submitted.
+             */
+            if (submitted) {
+                val flagged = db.initiatedPaymentSetSubmitted(it.key)
+                if (!flagged) {
+                    logger.warn("Initiated payment with row ID ${it.key} could 
not be flagged as submitted")
+                }
+            }
+        }
+    }
+}
+
 class EbicsSubmit : CliktCommand("Submits any initiated payment found in the 
database") {
     private val configFile by option(
         "--config", "-c",
@@ -83,23 +179,43 @@ class EbicsSubmit : CliktCommand("Submits any initiated 
payment found in the dat
     /**
      * Submits any initiated payment that was not submitted
      * so far and -- according to the configuration -- returns
-     * or long-polls for new payments.
+     * or long-polls (currently not implemented) for new payments.
      */
     override fun run() {
-        val cfg = loadConfigOrFail(configFile)
+        val cfg: EbicsSetupConfig = doOrFail { extractEbicsConfig(configFile) }
         val frequency: Int = doOrFail {
-            cfg.requireNumber("nexus-ebics-submit", "frequency")
+            val configValue = cfg.config.requireString("nexus-ebics-submit", 
"frequency")
+            return@doOrFail checkFrequency(configValue)
+
         }
-        if (frequency < 0) {
-            logger.error("Configuration error: cannot operate with a negative 
submit frequency ($frequency)")
+        val dbCfg = cfg.config.extractDbConfigOrFail()
+        val db = Database(dbCfg.dbConnStr)
+        val httpClient = HttpClient()
+        val bankKeys = loadBankKeys(cfg.bankPublicKeysFilename)
+        if (bankKeys == null) {
+            logger.error("Could not find the bank keys at: 
${cfg.bankPublicKeysFilename}")
             exitProcess(1)
         }
-        if (frequency == 0) {
-            logger.error("Long-polling not implemented, set frequency > 0")
+        if (!bankKeys.accepted) {
+            logger.error("Bank keys are not accepted, yet.  Won't submit any 
payment.")
             exitProcess(1)
         }
-        val dbCfg = cfg.extractDbConfigOrFail()
-        val db = Database(dbCfg.dbConnStr)
-        throw NotImplementedError("to be done")
+        val clientKeys = loadPrivateKeysFromDisk(cfg.clientPrivateKeysFilename)
+        if (clientKeys == null) {
+            logger.error("Client private keys not found at: 
${cfg.clientPrivateKeysFilename}")
+            exitProcess(1)
+        }
+        if (frequency == 0) {
+            logger.warn("Long-polling not implemented, submitting what is 
found and exit")
+            submitBatch(cfg, db, httpClient, clientKeys, bankKeys)
+            return
+        }
+        fixedRateTimer(
+            name = "ebics submit period",
+            period = (frequency * 1000).toLong(),
+            action = {
+                submitBatch(cfg, db, httpClient, clientKeys, bankKeys)
+            }
+        )
     }
 }
\ No newline at end of file
diff --git a/nexus/src/test/kotlin/ConfigLoading.kt 
b/nexus/src/test/kotlin/ConfigLoading.kt
index 1216a13a..e281399a 100644
--- a/nexus/src/test/kotlin/ConfigLoading.kt
+++ b/nexus/src/test/kotlin/ConfigLoading.kt
@@ -2,6 +2,9 @@ import org.junit.Test
 import org.junit.jupiter.api.assertThrows
 import tech.libeufin.nexus.EbicsSetupConfig
 import tech.libeufin.nexus.NEXUS_CONFIG_SOURCE
+import tech.libeufin.nexus.getFrequencyInSeconds
+import kotlin.test.assertEquals
+import kotlin.test.assertNull
 
 class ConfigLoading {
     /**
@@ -16,6 +19,15 @@ class ConfigLoading {
         cfg._dump()
     }
 
+    @Test
+    fun loadPath() {
+        val handle = TalerConfig(NEXUS_CONFIG_SOURCE)
+        handle.load()
+        val cfg = EbicsSetupConfig(handle)
+        cfg.config.requirePath("nexus-ebics-fetch", "statement_log_directory")
+    }
+
+
     /**
      * Tests that if the configuration lacks at least one option, then
      * the config loader throws exception.
@@ -32,4 +44,18 @@ class ConfigLoading {
             EbicsSetupConfig(handle)
         }
     }
+
+    // Checks converting human-readable durations to seconds.
+    @Test
+    fun timeParsing() {
+        assertEquals(1, getFrequencyInSeconds("1s"))
+        assertEquals(10*60, getFrequencyInSeconds("10m"))
+        assertEquals(24*60*60, getFrequencyInSeconds("24h"))
+        assertEquals(60*60, getFrequencyInSeconds("      1h      "))
+        assertEquals(60*60, getFrequencyInSeconds("01h"))
+        assertNull(getFrequencyInSeconds("1.1s"))
+        assertNull(getFrequencyInSeconds("         "))
+        assertNull(getFrequencyInSeconds("m"))
+        assertNull(getFrequencyInSeconds(""))
+    }
 }
\ No newline at end of file

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