gnunet-svn
[Top][All Lists]
Advanced

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

[exchange] branch master updated: add test and fix bugs for sanction lis


From: Admin
Subject: [exchange] branch master updated: add test and fix bugs for sanction list check (for #9053)
Date: Mon, 09 Jun 2025 00:06:34 +0200

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

grothoff pushed a commit to branch master
in repository exchange.

The following commit(s) were added to refs/heads/master by this push:
     new 8ad5457d5 add test and fix bugs for sanction list check (for #9053)
8ad5457d5 is described below

commit 8ad5457d51d5089f4f6c7dfe5a81b7210dd28110
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Mon Jun 9 00:06:21 2025 +0200

    add test and fix bugs for sanction list check (for #9053)
---
 src/exchange/exchange.conf                         |   8 +-
 src/exchange/taler-exchange-sanctionscheck.c       |  58 ++++++++--
 src/exchange/test_taler_exchange_httpd.sh          |   5 +-
 .../exchange_do_insert_sanction_list_hit.sql       |   5 +
 src/exchangedb/pg_insert_sanction_list_hit.c       |   4 +-
 src/kyclogic/kyclogic_sanctions.c                  | 119 ++++++++++++---------
 .../taler-exchange-helper-measure-test-form        |  10 +-
 src/testing/Makefile.am                            |   7 +-
 src/testing/sanction-list.json                     |   3 +
 src/testing/test-sanctions.sh                      |  38 +++----
 src/testing/test_kyc_api.c                         |   2 +-
 src/testing/test_sanctions.conf                    |   2 +-
 12 files changed, 163 insertions(+), 98 deletions(-)

diff --git a/src/exchange/exchange.conf b/src/exchange/exchange.conf
index 4f3f43008..8624eb17c 100644
--- a/src/exchange/exchange.conf
+++ b/src/exchange/exchange.conf
@@ -138,17 +138,17 @@ MIN_ROW_FILENAME = 
${TALER_CACHE_HOME}sanctionscheck-offset.bin
 # Sanction list match rating that must be exceeded for an automated
 # freeze of the account (without manual investigation first).
 # Both this and the FREEZE_CONFIDENCE_LIMIT must be met.
-FREEZE_RATING_LIMIT = 0.95
+FREEZE_RATING_LIMIT = 0.9
 
 # Sanction list confidence that must be exceeded for an automated
 # freeze of the account (without manual investigation first).
 # Both this and the FREEZE_RATING_LIMIT must be met.
-FREEZE_CONFIDENCE_LIMIT = 0.95
+FREEZE_CONFIDENCE_LIMIT = 0.9
 
 # Value that the rating divided by the confidence must exceed to
-# trigger a (manual) investigation. At 0.95 (default), a modest
+# trigger a (manual) investigation. At 0.95, a modest
 # 0.8 match with low 0.5 confidence will trigger (1.6), but a
 # good 0.9 match with a super-high 1.0 confidence would not (0.9 < 0.95).
 # OTOH, a bad 0.2 match with super-low 0.1 confidence would again
 # trigger (2.0 > 0.95).
-INVESTIGATION_LIMIT = 0.95
\ No newline at end of file
+INVESTIGATION_LIMIT = 0.9
\ No newline at end of file
diff --git a/src/exchange/taler-exchange-sanctionscheck.c 
b/src/exchange/taler-exchange-sanctionscheck.c
index 2a7bdeaca..431a9fac5 100644
--- a/src/exchange/taler-exchange-sanctionscheck.c
+++ b/src/exchange/taler-exchange-sanctionscheck.c
@@ -157,18 +157,18 @@ static bool in_transaction;
 /**
  * Match quality needed for instantly freezing an account.
  */
-static float freeze_rating_limit = 0.95;
+static float freeze_rating_limit = 0.9;
 
 /**
  * Match confidence needed for instantly freezing an account.
  */
-static float freeze_confidence_limit = 0.95;
+static float freeze_confidence_limit = 0.9;
 
 /**
  * Rating/confidence threshold that must be passed to begin
  * an investigation.
  */
-static float investigation_limit = 0.95;
+static float investigation_limit = 0.9;
 
 /**
  * Write @a min_row_id to @a min_row_fd.
@@ -208,9 +208,9 @@ shutdown_task (void *cls)
   struct Account *acc;
 
   (void) cls;
-  sync_row ();
   if (-1 != min_row_fd)
   {
+    sync_row ();
     GNUNET_break (0 == close (min_row_fd));
     min_row_fd = -1;
   }
@@ -284,6 +284,17 @@ sanction_cb (void *cls,
   bool freeze = false;
   bool investigate = false;
 
+  if (TALER_EC_NONE != ec)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Error %s (%d) when analyzing record\n",
+                TALER_ErrorCode_get_hint (ec),
+                (int) ec);
+    db_plugin->rollback (db_plugin->cls);
+    global_ret = 1;
+    GNUNET_SCHEDULER_shutdown ();
+    return;
+  }
   if ( (rating > (double) freeze_rating_limit) &&
        (confidence > (double) freeze_confidence_limit) )
   {
@@ -294,6 +305,16 @@ sanction_cb (void *cls,
   {
     investigate = true;
   }
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Best match is %f/%f at `%s' will %s\n",
+              rating,
+              confidence,
+              best_match,
+              freeze || investigate
+              ? (freeze
+                 ? "freeze"
+                 : "investigate")
+              : "do nothing");
   if (freeze || investigate)
   {
     static const char *freeze_event[] = {
@@ -320,9 +341,10 @@ sanction_cb (void *cls,
                                "INVESTIGATION_PENDING"),
       GNUNET_JSON_pack_string ("AML_INVESTIGATION_TIGGER",
                                "SANCTION_LIST_MATCH"));
-    GNUNET_assert (0 ==
-                   json_object_update_missing (properties,
-                                               acc->properties));
+    if (NULL != acc->properties)
+      GNUNET_assert (0 ==
+                     json_object_update_missing (properties,
+                                                 acc->properties));
     qs = db_plugin->insert_sanction_list_hit (db_plugin->cls,
                                               &acc->h_payto,
                                               investigate,
@@ -420,6 +442,14 @@ account_cb (void *cls,
                 (unsigned long long) row_id);
     return true;
   }
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Found KYC data %llu\n",
+              (unsigned long long) row_id);
+#if DEBUG
+  json_dumpf (attributes,
+              stderr,
+              JSON_INDENT (2));
+#endif
   acc = GNUNET_new (struct Account);
   acc->row_id = row_id;
   acc->h_payto = *h_payto;
@@ -697,6 +727,7 @@ run (void *cls,
                                  "exchange-sanctionscheck",
                                  "MIN_ROW_FILENAME");
       global_ret = EXIT_NOTCONFIGURED;
+      GNUNET_SCHEDULER_shutdown ();
       return;
     }
     if (reset &&
@@ -708,6 +739,18 @@ run (void *cls,
                                 min_row_fn);
       GNUNET_free (min_row_fn);
       global_ret = EXIT_NOPERMISSION;
+      GNUNET_SCHEDULER_shutdown ();
+      return;
+    }
+    if (GNUNET_OK !=
+        GNUNET_DISK_directory_create_for_file (min_row_fn))
+    {
+      GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+                                "open",
+                                min_row_fn);
+      GNUNET_free (min_row_fn);
+      global_ret = EXIT_NOPERMISSION;
+      GNUNET_SCHEDULER_shutdown ();
       return;
     }
     min_row_fd = open (min_row_fn,
@@ -720,6 +763,7 @@ run (void *cls,
                                 min_row_fn);
       GNUNET_free (min_row_fn);
       global_ret = EXIT_NOTCONFIGURED;
+      GNUNET_SCHEDULER_shutdown ();
       return;
     }
     if (sizeof (r) !=
diff --git a/src/exchange/test_taler_exchange_httpd.sh 
b/src/exchange/test_taler_exchange_httpd.sh
index 0fe71f3ad..988b7c54f 100755
--- a/src/exchange/test_taler_exchange_httpd.sh
+++ b/src/exchange/test_taler_exchange_httpd.sh
@@ -35,7 +35,7 @@ PREFIX=
 taler-exchange-dbinit -c test_taler_exchange_httpd.conf &> /dev/null || exit 77
 # Run Exchange HTTPD (in background)
 $PREFIX taler-exchange-httpd -c test_taler_exchange_httpd.conf 2> 
test-exchange.log &
-
+EPID=$!
 # Give HTTP time to start
 
 for n in `seq 1 100`
@@ -74,8 +74,7 @@ echo -n .
 cat test_taler_exchange_httpd.get | grep -v ^\# | awk '{ print "curl -H 
\"Accept: */plain\" http://localhost:8081"; $1 }' | bash &> /dev/null
 
 echo " DONE"
-# $! is the last backgrounded process, hence the exchange
-kill -TERM $!
+kill -TERM "${EPID}"
 wait $!
 # Return status code from exchange for this script
 exit $?
diff --git a/src/exchangedb/exchange_do_insert_sanction_list_hit.sql 
b/src/exchangedb/exchange_do_insert_sanction_list_hit.sql
index 265c25973..e86210b18 100644
--- a/src/exchangedb/exchange_do_insert_sanction_list_hit.sql
+++ b/src/exchangedb/exchange_do_insert_sanction_list_hit.sql
@@ -32,6 +32,11 @@ DECLARE
   ini_event TEXT;
 BEGIN
 
+-- Disable all previous legitimization outcomes.
+UPDATE legitimization_outcomes
+   SET is_active=FALSE
+ WHERE h_payto=in_h_normalized_payto;
+
 INSERT INTO legitimization_outcomes
   (h_payto
   ,decision_time
diff --git a/src/exchangedb/pg_insert_sanction_list_hit.c 
b/src/exchangedb/pg_insert_sanction_list_hit.c
index 3a59cc3ab..ee5e4d6c1 100644
--- a/src/exchangedb/pg_insert_sanction_list_hit.c
+++ b/src/exchangedb/pg_insert_sanction_list_hit.c
@@ -76,8 +76,8 @@ TEH_PG_insert_sanction_list_hit (
   PREPARE (pg,
            "do_insert_sanction_list_hit",
            "SELECT"
-           " out_outcome_serial_id"
-           " FROM exchange_insert_sanction_list_hit"
+           " out_outcome_serial_id AS outcome_serial_id"
+           " FROM exchange_do_insert_sanction_list_hit"
            "($1,$2,$3,$4,$5,$6,$7,$8);");
   qs = GNUNET_PQ_eval_prepared_singleton_select (
     pg->conn,
diff --git a/src/kyclogic/kyclogic_sanctions.c 
b/src/kyclogic/kyclogic_sanctions.c
index 3ce169076..ab591bdb4 100644
--- a/src/kyclogic/kyclogic_sanctions.c
+++ b/src/kyclogic/kyclogic_sanctions.c
@@ -51,7 +51,7 @@ struct TALER_KYCLOGIC_EvaluationEntry
   /**
    * Buffer with data we need to send to the helper.
    */
-  void *write_buf;
+  char *write_buf;
 
   /**
    * Total length of @e write_buf.
@@ -167,7 +167,10 @@ fail_hard (struct TALER_KYCLOGIC_SanctionRater *sr)
             NULL,
             1.0,
             0.0);
-    free (ee->write_buf);
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Failed to send %u bytes to child\n",
+                (unsigned int) (ee->write_size - ee->write_pos));
+    GNUNET_free (ee->write_buf);
     GNUNET_free (ee);
   }
 }
@@ -192,6 +195,7 @@ process_buffer (struct TALER_KYCLOGIC_SanctionRater *sr)
   if ( (NULL == end) &&
        (sr->read_pos < 2048) )
     return true;
+  end++;
   buf_len = end - sr->read_buf;
   while (0 != buf_len)
   {
@@ -204,9 +208,14 @@ process_buffer (struct TALER_KYCLOGIC_SanctionRater *sr)
     nl = memchr (buf,
                  '\n',
                  buf_len);
-    GNUNET_assert (NULL != nl);
+    if (NULL == nl)
+    {
+      /* no newline in 2048 bytes? not allowed */
+      GNUNET_break (0);
+      return false;
+    }
     *nl = '\0';
-    line_len = nl - buf;
+    line_len = nl - buf + 1;
     if (3 !=
         sscanf (buf,
                 "%lf %lf %1023s",
@@ -231,7 +240,7 @@ process_buffer (struct TALER_KYCLOGIC_SanctionRater *sr)
               best_match,
               rating,
               confidence);
-      free (ee->write_buf);
+      GNUNET_free (ee->write_buf);
       GNUNET_free (ee);
     }
     buf += line_len;
@@ -347,54 +356,56 @@ write_cb (void *cls)
   ssize_t ret;
 
   sr->write_task = NULL;
-  while (ee->write_size > ee->write_pos)
+  while ( (NULL != ee) &&
+          (ee->write_size == ee->write_pos) )
+    ee = ee->prev;
+  while (NULL != ee)
   {
-    ret = GNUNET_DISK_file_write (sr->chld_stdin,
-                                  ee->write_buf + ee->write_pos,
-                                  ee->write_size - ee->write_pos);
-    if (ret < 0)
+    while (ee->write_size > ee->write_pos)
     {
-      if ( (EAGAIN != errno) &&
-           (EINTR != errno) )
-        GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
-                             "write");
-      break;
+      ret = GNUNET_DISK_file_write (sr->chld_stdin,
+                                    ee->write_buf + ee->write_pos,
+                                    ee->write_size - ee->write_pos);
+      if (ret < 0)
+      {
+        if ( (EAGAIN != errno) &&
+             (EINTR != errno) )
+        {
+          GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+                               "write");
+          /* helper must have died */
+          fail_hard (sr);
+          return;
+        }
+        break;
+      }
+      if (0 == ret)
+      {
+        GNUNET_break (0);
+        break;
+      }
+      GNUNET_assert (ee->write_size >= ee->write_pos + ret);
+      ee->write_pos += ret;
     }
-    if (0 == ret)
+    if ( (ee->write_size > ee->write_pos) &&
+         ( (EAGAIN == errno) ||
+           (EWOULDBLOCK == errno) ||
+           (EINTR == errno) ) )
     {
-      GNUNET_break (0);
-      break;
-    }
-    GNUNET_assert (ee->write_size >= ee->write_pos + ret);
-    ee->write_pos += ret;
-  }
-  if (ee->write_size == ee->write_pos)
-  {
-    free (ee->write_buf);
-    GNUNET_CONTAINER_DLL_remove (sr->ee_head,
-                                 sr->ee_tail,
-                                 ee);
-    GNUNET_free (ee);
-    ee = sr->ee_tail;
-    if (NULL == ee)
+      sr->write_task
+        = GNUNET_SCHEDULER_add_write_file (
+            GNUNET_TIME_UNIT_FOREVER_REL,
+            sr->chld_stdin,
+            &write_cb,
+            sr);
       return;
-  }
-  if ( (ee->write_size > ee->write_pos) &&
-       ( (EAGAIN == errno) ||
-         (EWOULDBLOCK == errno) ||
-         (EINTR == errno) ) )
-  {
-    sr->write_task
-      = GNUNET_SCHEDULER_add_write_file (
-          GNUNET_TIME_UNIT_FOREVER_REL,
-          sr->chld_stdin,
-          &write_cb,
-          sr);
-    return;
-  }
-  /* helper must have died */
-  GNUNET_break (0);
-  fail_hard (sr);
+    }
+    if (ee->write_size == ee->write_pos)
+    {
+      GNUNET_free (ee->write_buf);
+      ee = ee->prev;
+    }
+  } /* while (NULL != ee) */
 }
 
 
@@ -485,6 +496,7 @@ TALER_KYCLOGIC_sanction_rater_eval (struct 
TALER_KYCLOGIC_SanctionRater *sr,
                                     void *cb_cls)
 {
   struct TALER_KYCLOGIC_EvaluationEntry *ee;
+  char *js;
 
   if (NULL == sr->read_task)
     return NULL;
@@ -494,12 +506,13 @@ TALER_KYCLOGIC_sanction_rater_eval (struct 
TALER_KYCLOGIC_SanctionRater *sr,
   GNUNET_CONTAINER_DLL_insert (sr->ee_head,
                                sr->ee_tail,
                                ee);
-  ee->write_buf = json_dumps (attributes,
-                              JSON_COMPACT);
+  js = json_dumps (attributes,
+                   JSON_COMPACT);
+  GNUNET_asprintf (&ee->write_buf,
+                   "%s\n",
+                   js);
+  free (js);
   ee->write_size = strlen (ee->write_buf);
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "Passing %llu bytes to JSON conversion tool\n",
-              (unsigned long long) ee->write_size);
   if (NULL == sr->write_task)
     sr->write_task
       = GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL,
diff --git a/src/kyclogic/taler-exchange-helper-measure-test-form 
b/src/kyclogic/taler-exchange-helper-measure-test-form
index 349e8d661..aada52ab5 100755
--- a/src/kyclogic/taler-exchange-helper-measure-test-form
+++ b/src/kyclogic/taler-exchange-helper-measure-test-form
@@ -34,9 +34,9 @@ do
     case "$OPTION" in
         a)
             # This AML program expects as inputs a full_name
-            # and a birthdate.
-            echo "full_name"
-            echo "birthdate"
+            # and a date of birth.
+            echo "FULL_NAME"
+            echo "DATE_OF_BIRTH"
             exit 0
             ;;
         c)
@@ -90,8 +90,8 @@ J=$(echo "$A" | jq -r 'def get($k):
                then .[$k]
                else error("attribute missing")
            end;
-           {"full_name":get("full_name"),
-            "birthdate":get("birthdate")}')
+           {"FULL_NAME":get("FULL_NAME"),
+            "DATE_OF_BIRTH":get("DATE_OF_BIRTH")}')
 
 # Here we could use those values...
 echo "$J" >> /dev/null
diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am
index d1c970adf..d653ab276 100644
--- a/src/testing/Makefile.am
+++ b/src/testing/Makefile.am
@@ -185,9 +185,12 @@ endif
 #  test_exchange_api_revocation_cs
 #  test_exchange_api_revocation_rsa
 
+check_SCRIPTS = \
+  test-sanctions.sh
 
 TESTS = \
-  $(check_PROGRAMS)
+  $(check_PROGRAMS) \
+  $(check_SCRIPTS)
 
 test_auditor_api_cs_SOURCES = \
   test_auditor_api.c
@@ -559,9 +562,11 @@ test_kyc_api_LDADD = \
 
 EXTRA_DIST = \
   $(bin_SCRIPTS) \
+  $(check_SCRIPTS) \
   valgrind.h \
   coins-cs.conf \
   coins-rsa.conf \
+  sanction-list.json \
   test_exchange_api_home/.local/share/taler-auditor/offline-keys/auditor.priv \
   test_exchange_api_home/.local/share/taler-exchange/offline/master.priv \
   test_auditor_api-cs.conf \
diff --git a/src/testing/sanction-list.json b/src/testing/sanction-list.json
new file mode 100644
index 000000000..665c37625
--- /dev/null
+++ b/src/testing/sanction-list.json
@@ -0,0 +1,3 @@
+[{"ssid":"1","FULL_NAME":["Bob"],"DATE_OF_BIRTH":["6.7.1980"]},
+ {"ssid":"2","FULL_NAME":["Alice"],"DATE_OF_BIRTH":["5.7.1980"]},
+ {"ssid":"3","FULL_NAME":["Carol"],"DATE_OF_BIRTH":["5.7.1980"]}]
diff --git a/src/testing/test-sanctions.sh b/src/testing/test-sanctions.sh
index 699018ae1..2bc59e149 100755
--- a/src/testing/test-sanctions.sh
+++ b/src/testing/test-sanctions.sh
@@ -32,6 +32,10 @@ function my_cleanup()
     fi
 }
 
+echo -n "Testing for robocop"
+robocop -h > /dev/null || exit_skip " robocop required"
+echo " FOUND"
+
 . setup.sh
 
 
@@ -67,7 +71,7 @@ ID=$(jq -r .requirements[0].id < "$LAST_RESPONSE")
 echo -n "Submitting KYC form..." >&2
 STATUS=$(curl -H "Content-Type: application/json" -X POST \
     "http://localhost:8081/kyc-upload/$ID"; \
-    -d '{"full_name":"Bob","birthdate":"5.7.1980"}' \
+    -d '{"FULL_NAME":"Bob","DATE_OF_BIRTH":"5.7.1980"}' \
     -w "%{http_code}" -s -o "$LAST_RESPONSE")
 echo $STATUS
 
@@ -77,30 +81,22 @@ then
     exit_fail "Expected 204, KYC data submitted. got: $STATUS"
 fi
 
+taler-exchange-sanctionscheck \
+    -L INFO \
+    -c test_sanctions.conf.edited \
+    --reset \
+    --test \
+    robocop sanction-list.json
 
+PROP=$(echo 'SELECT jproperties FROM exchange.legitimization_outcomes WHERE 
is_active;' | psql talercheck -Aqt)
 
-bash
-
-# => begin KYC process
-# => submit KYC data
-# => run sanction list tool!
-
+MATCH=$(echo "$PROP" | jq -r .SANCTION_LIST_BEST_MATCH)
 
+if [ "$MATCH" != "1" ]
+then
+    exit_fail "Sanction checker failed to find Bob"
+fi
 
 echo "Test PASSED"
 
 exit 0
-
-
-echo -n "Creating order to test auth is ok..." >&2
-STATUS=$(curl -H "Content-Type: application/json" -X POST \
-    'http://localhost:9966/private/orders' \
-    -H 'Authorization: Bearer '"$NEW_SECRET" \
-    -d '{"order":{"amount":"TESTKUDOS:1","summary":"payme"}}' \
-    -w "%{http_code}" -s -o "$LAST_RESPONSE")
-
-if [ "$STATUS" != "200" ]
-then
-    cat "$LAST_RESPONSE" >&2
-    exit_fail "Expected 200, order created. got: $STATUS"
-fi
diff --git a/src/testing/test_kyc_api.c b/src/testing/test_kyc_api.c
index 5d0eff9c4..dbb5ffb43 100644
--- a/src/testing/test_kyc_api.c
+++ b/src/testing/test_kyc_api.c
@@ -771,7 +771,7 @@ run (void *cls,
       "get-kyc-info-form",
       0,  /* requirement index */
       "application/json",
-      
"{\"form_id\":\"test\",\"full_name\":\"Bob\",\"birthdate\":\"1990-00-00\"}",
+      
"{\"form_id\":\"test\",\"FULL_NAME\":\"Bob\",\"DATE_OF_BIRTH\":\"1990-00-00\"}",
       MHD_HTTP_NO_CONTENT),
     /* now this should be allowed */
     TALER_TESTING_cmd_wallet_kyc_get (
diff --git a/src/testing/test_sanctions.conf b/src/testing/test_sanctions.conf
index 2c37f2da9..12db30d56 100644
--- a/src/testing/test_sanctions.conf
+++ b/src/testing/test_sanctions.conf
@@ -40,7 +40,7 @@ FALLBACK = manual-freeze
 # This check runs on oauth2
 FORM_NAME = full_name_and_birthdate
 # Outputs from this check
-OUTPUTS = full_name birthdate
+OUTPUTS = FULL_NAME DATE_OF_BIRTH
 
 # This is the "default" setting for an account if
 # it has not yet triggered anything.

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