gnunet-svn
[Top][All Lists]
Advanced

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

[taler-exchange] branch master updated: -more general KYC logic


From: gnunet
Subject: [taler-exchange] branch master updated: -more general KYC logic
Date: Thu, 04 Aug 2022 11:36:09 +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 61f39f09 -more general KYC logic
61f39f09 is described below

commit 61f39f0941340f9f7f4a60ad123cda4bccbefbfe
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Thu Aug 4 11:36:05 2022 +0200

    -more general KYC logic
---
 src/exchange/taler-exchange-httpd_kyc.c | 326 ++++++++++++++++++++++++++++++--
 src/exchange/taler-exchange-httpd_kyc.h |   3 +-
 src/include/taler_exchangedb_plugin.h   |  32 ++++
 3 files changed, 347 insertions(+), 14 deletions(-)

diff --git a/src/exchange/taler-exchange-httpd_kyc.c 
b/src/exchange/taler-exchange-httpd_kyc.c
index cb7f73c6..aa1911d2 100644
--- a/src/exchange/taler-exchange-httpd_kyc.c
+++ b/src/exchange/taler-exchange-httpd_kyc.c
@@ -73,6 +73,11 @@ struct TEH_KycProvider
    */
   struct TEH_KYCLOGIC_ProviderDetails *pd;
 
+  /**
+   * Cost of running this provider's KYC.
+   */
+  unsigned long long cost;
+
   /**
    * Length of the @e checks array.
    */
@@ -156,7 +161,7 @@ static unsigned int num_kyc_triggers;
 /**
  * Array of configured providers.
  */
-static struct TEH_KycProvider *kyc_providers;
+static struct TEH_KycProvider **kyc_providers;
 
 /**
  * Length of the #kyc_providers array.
@@ -419,6 +424,7 @@ add_provider (const char *section)
     kp->provider_section_name = section;
     kp->user_type = ut;
     kp->logic = lp;
+    kp->cost = cost;
     add_checks (checks,
                 &kp->provided_checks,
                 &kp->num_checks);
@@ -574,6 +580,33 @@ handle_section (void *cls,
 }
 
 
+/**
+ * Comparator for qsort. Compares two triggers
+ * by timeframe to sort triggers by time.
+ *
+ * @param p1 first trigger to compare
+ * @param p2 second trigger to compare
+ * @return -1 if p1 < p2, 0 if p1==p2, 1 if p1 > p2.
+ */
+static int
+sort_by_timeframe (void *p1,
+                   void *p2)
+{
+  struct TEH_KycTrigger **t1 = p1;
+  struct TEH_KycTrigger **t2 = p2;
+
+  if (GNUNET_TIME_relative_cmp ((*t1)->timeframe,
+                                <,
+                                (*t2)->timeframe))
+    return -1;
+  if (GNUNET_TIME_relative_cmp ((*t1)->timeframe,
+                                >,
+                                (*t2)->timeframe))
+    return 1;
+  return 0;
+}
+
+
 enum GNUNET_GenericReturnValue
 TEH_kyc_init (void)
 {
@@ -599,7 +632,10 @@ TEH_kyc_init (void)
       TEH_kyc_done ();
       return GNUNET_SYSERR;
     }
-
+  qsort (kyc_triggers,
+         num_kyc_triggers,
+         sizeof (struct TEH_KycTrigger *),
+         &sort_by_timeframe);
   return GNUNET_OK;
 }
 
@@ -654,22 +690,288 @@ TEH_kyc_done (void)
 }
 
 
+/**
+ * Closure for the #eval_trigger().
+ */
+struct ThresholdTestContext
+{
+  /**
+   * Total amount so far.
+   */
+  struct TALER_Amount total;
+
+  /**
+   * Trigger event to evaluate triggers of.
+   */
+  enum TEH_KycTriggerEvent event;
+
+  /**
+   * Offset in the triggers array where we need to start
+   * checking for triggers. All trigges below this
+   * offset were already hit.
+   */
+  unsigned int start;
+
+  /**
+   * Array of checks needed so far.
+   */
+  struct TEH_KycCheck **needed;
+
+  /**
+   * Pointer to number of entries used in @a needed.
+   */
+  unsigned int *needed_cnt;
+
+};
+
+
+/**
+ * Function called on each @a amount that was found to
+ * be relevant for a KYC check.
+ *
+ * @param cls closure to allow the KYC module to
+ *        total up amounts and evaluate rules
+ * @param amount encountered transaction amount
+ * @param date when was the amount encountered
+ * @return #GNUNET_OK to continue to iterate,
+ *         #GNUNET_NO to abort iteration
+ *         #GNUNET_SYSERR on internal error (also abort itaration)
+ */
+static enum GNUNET_GenericReturnValue
+eval_trigger (void *cls,
+              const struct TALER_Amount *amount,
+              struct GNUNET_TIME_Absolute date)
+{
+  struct ThresholdTestContext *ttc = cls;
+  struct GNUNET_TIME_Relative duration;
+  bool bump = true;
+
+  duration = GNUNET_TIME_absolute_get_duration (date);
+  if (0 >
+      TALER_amount_add (&ttc->total,
+                        &ttc->total,
+                        amount))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  for (unsigned int i = ttc->start; i<num_kyc_triggers; i++)
+  {
+    const struct TEH_KycTrigger *kt = kyc_triggers[i];
+
+    if (event != kt->trigger)
+      continue;
+    timeframe = GNUNET_TIME_relative_max (timeframe,
+                                          kt->timeframe);
+    if (GNUNET_TIME_relative_cmp (kt->timeframe,
+                                  >,
+                                  duration))
+    {
+      if (bump)
+        ttc->start = i;
+      return;
+    }
+    if (-1 ==
+        TALER_amount_cmp (&ttc->total,
+                          &kt->threshold))
+    {
+      if (bump)
+        ttc->start = i;
+      bump = false;
+      continue; /* amount too low to trigger */
+    }
+    /* add check to list of required checks, unless
+       already present... */
+    for (unsigned int j = 0; j<kt->num_checks; j++)
+    {
+      struct TEH_KycCheck *rc = kt->required_checks[j];
+      bool found = false;
+
+      for (unsigned int k = 0; k<*ttc->needed_cnt; k++)
+        if (ttc->needed[k] == rc)
+        {
+          found = true;
+          break;
+        }
+      if (! found)
+      {
+        ttc->needed[*ttc->needed_cnt] = rc;
+        (*ttc->needed_cnt)++;
+      }
+    }
+  }
+  if (bump)
+    return GNUNET_NO; /* we hit all possible triggers! */
+  return GNUNET_OK;
+}
+
+
+/**
+ * Closure for the #remove_satisfied().
+ */
+struct RemoveContext
+{
+
+  /**
+   * Array of checks needed so far.
+   */
+  struct TEH_KycCheck **needed;
+
+  /**
+   * Pointer to number of entries used in @a needed.
+   */
+  unsigned int *needed_cnt;
+
+};
+
+
+/**
+ * Remove all checks satisfied by @a provider_name from
+ * our list of checks.
+ *
+ * @param cls a `struct RemoveContext`
+ * @param provider_name section name of provider that was already run 
previously
+ */
+static void
+remove_satisfied (void *cls,
+                  const char *provider_name)
+{
+  struct RemoveContext *rc = cls;
+
+  for (unsigned int i = 0; i<num_kyc_providers; i++)
+  {
+    const struct TEH_KycProvider *kp = kyc_providers[i];
+
+    if (0 != strcasecmp (provider_name,
+                         kp->provider_section_name))
+      continue;
+    for (unsigned int j = 0; j<kp->num_checks; j++)
+    {
+      const struct TEH_KycCheck *kc = kp->provided_checks[j];
+
+      for (unsigned int k = 0; k<*rc->needed_cnt; k++)
+        if (kc == rc->needed[k])
+        {
+          rc->needed[k] = rc->needed[*rc->needed_cnt - 1];
+          (*rc->needed_cnt)--;
+          if (0 == *rc->needed_cnt)
+            return; /* for sure finished */
+          break;
+        }
+    }
+    break;
+  }
+}
+
+
 const char *
 TEH_kyc_test_required (enum TEH_KycTriggerEvent event,
                        const struct TALER_PaytoHashP *h_payto,
                        TEH_KycAmountIterator ai,
                        void *cls)
 {
-  // Check if event(s) may at all require KYC.
-  // If so, check what provider checks are
-  // already satisfied for h_payto (with database)
-  // If unsatisfied checks are left, use 'ai'
-  // to check if amount is high enough to trigger them.
-  // If it is, find cheapest provider that satisfies
-  // all of them (or, if multiple providers would be
-  // needed, return one of them).
-  GNUNET_break (0);
-  return NULL;
+  struct TEH_KycCheck *needed[num_kyc_checks];
+  unsigned int needed_cnt = 0;
+  struct GNUNET_TIME_Relative timeframe;
+  unsigned long long min_cost = ULONG_LONG_MAX;
+  unsigned int max_checks = 0;
+  const struct TEH_KycProvider *kp_best = NULL;
+
+  timeframe = GNUNET_TIME_UNIT_ZERO;
+  for (unsigned int i = 0; i<num_kyc_triggers; i++)
+  {
+    const struct TEH_KycTrigger *kt = kyc_triggers[i];
+
+    if (event != kt->trigger)
+      continue;
+    timeframe = GNUNET_TIME_relative_max (timeframe,
+                                          kt->timeframe);
+  }
+  {
+    struct GNUNET_TIME_Absolute now;
+    struct ThresholdTestContext ttc = {
+      .event = event,
+      .needed = needed,
+      .needed_cnt = &needed_cnt
+    };
+
+    TALER_amount_set_zero (TEH_currency,
+                           &ttc.total);
+    now = GNUNET_TIME_absolute_get ();
+    ai (ai_cls,
+        GNUNET_TIME_absolute_subtract (now,
+                                       timeframe),
+        &eval_trigger,
+        &ttc);
+  }
+  if (0 == needed_cnt)
+    return NULL;
+  {
+    struct RemoveContext rc = {
+      .needed = needed,
+      .needed_cnt = &needed_cnt
+    };
+    enum GNUNET_DB_QueryStatus qs;
+
+    /* Check what provider checks are already satisfied for h_payto (with
+       database), remove those from the 'needed' array. */
+    GNUNET_break (0);
+    qs = TEH_plugin->select_satisfied_kyc_processes (TEH_plugin->cls,
+                                                     h_payto,
+                                                     &remove_satisfied,
+                                                     &rc);
+    GNUNET_break (qs >= 0);  // FIXME: handle DB failure more nicely?
+  }
+
+  /* Count maximum number of remaining checks covered by any
+     provider */
+  for (unsigned int i = 0; i<num_kyc_providers; i++)
+  {
+    const struct TEH_KycProvider *kp = kyc_providers[i];
+    unsigned int matched = 0;
+
+    for (unsigned int j = 0; j<kp->num_checks; j++)
+    {
+      const struct TEH_KycCheck *kc = kp->provided_checks[j];
+
+      for (unsigned int k = 0; k<needed_cnt; k++)
+        if (kc == needed[k])
+        {
+          matched++;
+          break;
+        }
+    }
+    max_checks = GNUNET_MAX (max_checks,
+                             matched);
+  }
+
+  /* Find min-cost provider covering max_checks. */
+  for (unsigned int i = 0; i<num_kyc_providers; i++)
+  {
+    const struct TEH_KycProvider *kp = kyc_providers[i];
+    unsigned int matched = 0;
+
+    for (unsigned int j = 0; j<kp->num_checks; j++)
+    {
+      const struct TEH_KycCheck *kc = kp->provided_checks[j];
+
+      for (unsigned int k = 0; k<needed_cnt; k++)
+        if (kc == needed[k])
+        {
+          matched++;
+          break;
+        }
+    }
+    if ( (max_checks == matched) &&
+         (kp->cost < min_cost) )
+    {
+      min_cost = kp->cost;
+      kp_best = kp;
+    }
+  }
+
+  GNUNET_assert (NULL != kp_best);
+  return kp_best->provider_section_name;
 }
 
 
diff --git a/src/exchange/taler-exchange-httpd_kyc.h 
b/src/exchange/taler-exchange-httpd_kyc.h
index 0061f658..e64f9c34 100644
--- a/src/exchange/taler-exchange-httpd_kyc.h
+++ b/src/exchange/taler-exchange-httpd_kyc.h
@@ -203,8 +203,7 @@ TEH_kyc_test_required (enum TEH_KycTriggerEvent event,
 
 
 /**
- * Obtain the provider logic for a given
- * @a provider_section_name.
+ * Obtain the provider logic for a given @a provider_section_name.
  *
  * @param provider_section_name identifies a KYC provider process
  * @param[out] plugin set to the KYC logic API
diff --git a/src/include/taler_exchangedb_plugin.h 
b/src/include/taler_exchangedb_plugin.h
index 5a5e8cab..26636d44 100644
--- a/src/include/taler_exchangedb_plugin.h
+++ b/src/include/taler_exchangedb_plugin.h
@@ -865,6 +865,20 @@ typedef void
   const struct TALER_MasterSignatureP *master_sig);
 
 
+/**
+ * Function called on all KYC process names that the given
+ * account has already passed.
+ *
+ * @param cls closure
+ * @param kyc_provider_section_name configuration section
+ *        of the respective KYC process
+ */
+typedef void
+(*TALER_EXCHANGEDB_SatisfiedProviderCallback)(
+  void *cls,
+  const char *kyc_provider_section_name);
+
+
 /**
  * Function called with information about the exchange's auditors.
  *
@@ -5607,6 +5621,24 @@ struct TALER_EXCHANGEDB_Plugin
     uint64_t serial);
 
 
+  /**
+   * Call us on KYC processes satisfied for the given
+   * account.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param h_payto account identifier
+   * @param spc function to call for each satisfied KYC process
+   * @param spc_cls closure for @a spc
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_satisfied_kyc_processes)(
+    void *cls,
+    const struct TALER_PaytoHashP *h_payto,
+    TALER_EXCHANGEDB_SatisfiedProviderCallback spc,
+    void *spc_cls);
+
+
 };
 
 #endif /* _TALER_EXCHANGE_DB_H */

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