gnunet-svn
[Top][All Lists]
Advanced

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

[taler-exchange] branch master updated: -more work on AML triggers for P


From: gnunet
Subject: [taler-exchange] branch master updated: -more work on AML triggers for P2P transfers
Date: Sun, 12 Feb 2023 22:02:54 +0100

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 6db4bdbe -more work on AML triggers for P2P transfers
6db4bdbe is described below

commit 6db4bdbe6e39b84b995b11ab132a7c6706686677
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Sun Feb 12 22:02:45 2023 +0100

    -more work on AML triggers for P2P transfers
---
 contrib/gana                                       |   2 +-
 src/exchange/taler-exchange-httpd.c                |  17 +++
 src/exchange/taler-exchange-httpd.h                |   7 +
 src/exchange/taler-exchange-httpd_batch-withdraw.c | 132 +++++++++++++++++-
 src/exchange/taler-exchange-httpd_responses.c      |  25 ++++
 src/exchange/taler-exchange-httpd_responses.h      |  13 ++
 src/exchange/taler-exchange-httpd_withdraw.c       | 153 +++++++++++++++++++--
 src/exchange/test_taler_exchange_httpd.conf        |   1 +
 src/exchangedb/pg_reserves_in_insert.c             |  19 ---
 src/exchangedb/pg_select_aml_threshold.c           |   2 +-
 10 files changed, 338 insertions(+), 33 deletions(-)

diff --git a/contrib/gana b/contrib/gana
index 3a616a04..a7abaa85 160000
--- a/contrib/gana
+++ b/contrib/gana
@@ -1 +1 @@
-Subproject commit 3a616a04f1cd946bf0641b54cd71f1b858174f74
+Subproject commit a7abaa856abbd16994132c5596ce04f442b9f4b9
diff --git a/src/exchange/taler-exchange-httpd.c 
b/src/exchange/taler-exchange-httpd.c
index 62bd9a9d..0c5d36e0 100644
--- a/src/exchange/taler-exchange-httpd.c
+++ b/src/exchange/taler-exchange-httpd.c
@@ -147,6 +147,13 @@ struct TALER_EXCHANGEDB_Plugin *TEH_plugin;
  */
 char *TEH_currency;
 
+/**
+ * What is the largest amount we allow a peer to
+ * merge into a reserve before always triggering
+ * an AML check?
+ */
+struct TALER_Amount TEH_aml_threshold;
+
 /**
  * Our base URL.
  */
@@ -1860,6 +1867,16 @@ exchange_serve_process_config (void)
                                "CURRENCY");
     return GNUNET_SYSERR;
   }
+  if (GNUNET_OK !=
+      TALER_config_get_amount (TEH_cfg,
+                               "taler",
+                               "AML_THRESHOLD",
+                               &TEH_aml_threshold))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Need amount in section `TALER' under `AML_THRESHOLD'\n");
+    return GNUNET_SYSERR;
+  }
   if (GNUNET_OK !=
       GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
                                              "exchange",
diff --git a/src/exchange/taler-exchange-httpd.h 
b/src/exchange/taler-exchange-httpd.h
index 0c2bd9e8..96003626 100644
--- a/src/exchange/taler-exchange-httpd.h
+++ b/src/exchange/taler-exchange-httpd.h
@@ -97,6 +97,13 @@ extern struct TALER_EXCHANGEDB_Plugin *TEH_plugin;
  */
 extern char *TEH_currency;
 
+/**
+ * What is the largest amount we allow a peer to
+ * merge into a reserve before always triggering
+ * an AML check?
+ */
+extern struct TALER_Amount TEH_aml_threshold;
+
 /**
  * Our (externally visible) base URL.
  */
diff --git a/src/exchange/taler-exchange-httpd_batch-withdraw.c 
b/src/exchange/taler-exchange-httpd_batch-withdraw.c
index b2f35b56..e6be54a5 100644
--- a/src/exchange/taler-exchange-httpd_batch-withdraw.c
+++ b/src/exchange/taler-exchange-httpd_batch-withdraw.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014-2022 Taler Systems SA
+  Copyright (C) 2014-2023 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as
@@ -108,6 +108,11 @@ struct BatchWithdrawContext
    */
   unsigned int planchets_length;
 
+  /**
+   * AML decision, #TALER_AML_NORMAL if we may proceed.
+   */
+  enum TALER_AmlDecisionState aml_decision;
+
 };
 
 
@@ -150,6 +155,34 @@ batch_withdraw_amount_cb (void *cls,
 }
 
 
+/**
+ * Function called on each @a amount that was found to
+ * be relevant for the AML check as it was merged into
+ * the reserve.
+ *
+ * @param cls `struct TALER_Amount *` to total up the amounts
+ * @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
+aml_amount_cb (
+  void *cls,
+  const struct TALER_Amount *amount,
+  struct GNUNET_TIME_Absolute date)
+{
+  struct TALER_Amount *total = cls;
+
+  GNUNET_assert (0 <=
+                 TALER_amount_add (total,
+                                   total,
+                                   amount));
+  return GNUNET_OK;
+}
+
+
 /**
  * Function implementing withdraw transaction.  Runs the
  * transaction logic; IF it returns a non-error code, the transaction
@@ -178,8 +211,102 @@ batch_withdraw_transaction (void *cls,
   bool balance_ok = false;
   bool found = false;
   const char *kyc_required;
+  struct TALER_PaytoHashP reserve_h_payto;
 
   wc->now = GNUNET_TIME_timestamp_get ();
+  /* Do AML check: compute total merged amount and check
+     against applicable AML threshold */
+  {
+    char *reserve_payto;
+
+    reserve_payto = TALER_reserve_make_payto (TEH_base_url,
+                                              wc->reserve_pub);
+    TALER_payto_hash (reserve_payto,
+                      &reserve_h_payto);
+    GNUNET_free (reserve_payto);
+  }
+  {
+    struct TALER_Amount merge_amount;
+    struct TALER_Amount threshold;
+    struct GNUNET_TIME_Absolute now_minus_one_month;
+
+    now_minus_one_month
+      = GNUNET_TIME_absolute_subtract (wc->now.abs_time,
+                                       GNUNET_TIME_UNIT_MONTHS);
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_amount_set_zero (TEH_currency,
+                                          &merge_amount));
+    qs = TEH_plugin->select_merge_amounts_for_kyc_check (TEH_plugin->cls,
+                                                         &reserve_h_payto,
+                                                         now_minus_one_month,
+                                                         &aml_amount_cb,
+                                                         &merge_amount);
+    if (qs < 0)
+    {
+      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+      if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+        *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                               MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                               
TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                               
"select_merge_amounts_for_kyc_check");
+      return qs;
+    }
+    qs = TEH_plugin->select_aml_threshold (TEH_plugin->cls,
+                                           &reserve_h_payto,
+                                           &wc->aml_decision,
+                                           &threshold);
+    if (qs < 0)
+    {
+      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+      if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+        *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                               MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                               
TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                               "select_aml_threshold");
+      return qs;
+    }
+    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+    {
+      threshold = TEH_aml_threshold; /* use default */
+      wc->aml_decision = TALER_AML_NORMAL;
+    }
+
+    switch (wc->aml_decision)
+    {
+    case TALER_AML_NORMAL:
+      if (0 >= TALER_amount_cmp (&merge_amount,
+                                 &threshold))
+      {
+        /* merge_amount <= threshold, continue withdraw below */
+        break;
+      }
+      wc->aml_decision = TALER_AML_PENDING;
+      qs = TEH_plugin->trigger_aml_process (TEH_plugin->cls,
+                                            &reserve_h_payto,
+                                            &merge_amount);
+      if (qs <= 0)
+      {
+        GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+        if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+          *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                                 
MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                                 
TALER_EC_GENERIC_DB_STORE_FAILED,
+                                                 "trigger_aml_process");
+        return qs;
+      }
+      return qs;
+    case TALER_AML_PENDING:
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "AML already pending, doing nothing\n");
+      return qs;
+    case TALER_AML_FROZEN:
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Account frozen, doing nothing\n");
+      return qs;
+    }
+  }
+
+  /* Check if the money came from a wire transfer */
   qs = TEH_plugin->reserves_get_origin (TEH_plugin->cls,
                                         wc->reserve_pub,
                                         &wc->h_payto);
@@ -352,6 +479,9 @@ generate_reply_success (const struct TEH_RequestContext *rc,
                                             &wc->h_payto,
                                             &wc->kyc);
   }
+  if (TALER_AML_NORMAL != wc->aml_decision)
+    return TEH_RESPONSE_reply_aml_blocked (rc->connection,
+                                           wc->aml_decision);
 
   sigs = json_array ();
   GNUNET_assert (NULL != sigs);
diff --git a/src/exchange/taler-exchange-httpd_responses.c 
b/src/exchange/taler-exchange-httpd_responses.c
index 33bc1398..5d9dfc3a 100644
--- a/src/exchange/taler-exchange-httpd_responses.c
+++ b/src/exchange/taler-exchange-httpd_responses.c
@@ -1142,4 +1142,29 @@ TEH_RESPONSE_reply_kyc_required (struct MHD_Connection 
*connection,
 }
 
 
+MHD_RESULT
+TEH_RESPONSE_reply_aml_blocked (struct MHD_Connection *connection,
+                                enum TALER_AmlDecisionState status)
+{
+  enum TALER_ErrorCode ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+
+  switch (status)
+  {
+  case TALER_AML_NORMAL:
+    GNUNET_break (0);
+    return MHD_NO;
+  case TALER_AML_PENDING:
+    ec = TALER_EC_EXCHANGE_GENERIC_AML_PENDING;
+    break;
+  case TALER_AML_FROZEN:
+    ec = TALER_EC_EXCHANGE_GENERIC_AML_FROZEN;
+    break;
+  }
+  return TALER_MHD_REPLY_JSON_PACK (
+    connection,
+    MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
+    TALER_JSON_pack_ec (ec));
+}
+
+
 /* end of taler-exchange-httpd_responses.c */
diff --git a/src/exchange/taler-exchange-httpd_responses.h 
b/src/exchange/taler-exchange-httpd_responses.h
index ba6577b2..0db6968f 100644
--- a/src/exchange/taler-exchange-httpd_responses.h
+++ b/src/exchange/taler-exchange-httpd_responses.h
@@ -91,6 +91,19 @@ TEH_RESPONSE_reply_kyc_required (struct MHD_Connection 
*connection,
                                  const struct TALER_EXCHANGEDB_KycStatus *kyc);
 
 
+/**
+ * Send information that an AML process is blocking
+ * the operation right now.
+ *
+ * @param connection connection to the client
+ * @param status current AML status
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_RESPONSE_reply_aml_blocked (struct MHD_Connection *connection,
+                                enum TALER_AmlDecisionState status);
+
+
 /**
  * Send assertion that the given denomination key hash
  * is not usable (typically expired) at this time.
diff --git a/src/exchange/taler-exchange-httpd_withdraw.c 
b/src/exchange/taler-exchange-httpd_withdraw.c
index 567cad5a..40cefc7d 100644
--- a/src/exchange/taler-exchange-httpd_withdraw.c
+++ b/src/exchange/taler-exchange-httpd_withdraw.c
@@ -61,16 +61,21 @@ struct WithdrawContext
   struct TALER_EXCHANGEDB_KycStatus kyc;
 
   /**
-   * Hash of the payto-URI representing the reserve
-   * from which we are withdrawing.
+   * Hash of the payto-URI representing the account
+   * from which the money was put into the reserve.
    */
-  struct TALER_PaytoHashP h_payto;
+  struct TALER_PaytoHashP h_account_payto;
 
   /**
    * Current time for the DB transaction.
    */
   struct GNUNET_TIME_Timestamp now;
 
+  /**
+   * AML decision, #TALER_AML_NORMAL if we may proceed.
+   */
+  enum TALER_AmlDecisionState aml_decision;
+
 };
 
 
@@ -108,7 +113,7 @@ withdraw_amount_cb (void *cls,
     return;
   qs = TEH_plugin->select_withdraw_amounts_for_kyc_check (
     TEH_plugin->cls,
-    &wc->h_payto,
+    &wc->h_account_payto,
     limit,
     cb,
     cb_cls);
@@ -120,6 +125,34 @@ withdraw_amount_cb (void *cls,
 }
 
 
+/**
+ * Function called on each @a amount that was found to
+ * be relevant for the AML check as it was merged into
+ * the reserve.
+ *
+ * @param cls `struct TALER_Amount *` to total up the amounts
+ * @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
+aml_amount_cb (
+  void *cls,
+  const struct TALER_Amount *amount,
+  struct GNUNET_TIME_Absolute date)
+{
+  struct TALER_Amount *total = cls;
+
+  GNUNET_assert (0 <=
+                 TALER_amount_add (total,
+                                   total,
+                                   amount));
+  return GNUNET_OK;
+}
+
+
 /**
  * Function implementing withdraw transaction.  Runs the
  * transaction logic; IF it returns a non-error code, the transaction
@@ -150,23 +183,116 @@ withdraw_transaction (void *cls,
   uint64_t ruuid;
   const struct TALER_CsNonce *nonce;
   const struct TALER_BlindedPlanchet *bp;
+  struct TALER_PaytoHashP reserve_h_payto;
 
   wc->now = GNUNET_TIME_timestamp_get ();
+  /* Do AML check: compute total merged amount and check
+     against applicable AML threshold */
+  {
+    char *reserve_payto;
+
+    reserve_payto = TALER_reserve_make_payto (TEH_base_url,
+                                              &wc->collectable.reserve_pub);
+    TALER_payto_hash (reserve_payto,
+                      &reserve_h_payto);
+    GNUNET_free (reserve_payto);
+  }
+  {
+    struct TALER_Amount merge_amount;
+    struct TALER_Amount threshold;
+    struct GNUNET_TIME_Absolute now_minus_one_month;
+
+    now_minus_one_month
+      = GNUNET_TIME_absolute_subtract (wc->now.abs_time,
+                                       GNUNET_TIME_UNIT_MONTHS);
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_amount_set_zero (TEH_currency,
+                                          &merge_amount));
+    qs = TEH_plugin->select_merge_amounts_for_kyc_check (TEH_plugin->cls,
+                                                         &reserve_h_payto,
+                                                         now_minus_one_month,
+                                                         &aml_amount_cb,
+                                                         &merge_amount);
+    if (qs < 0)
+    {
+      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+      if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+        *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                               MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                               
TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                               
"select_merge_amounts_for_kyc_check");
+      return qs;
+    }
+    qs = TEH_plugin->select_aml_threshold (TEH_plugin->cls,
+                                           &reserve_h_payto,
+                                           &wc->aml_decision,
+                                           &threshold);
+    if (qs < 0)
+    {
+      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+      if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+        *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                               MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                               
TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                               "select_aml_threshold");
+      return qs;
+    }
+    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+    {
+      threshold = TEH_aml_threshold; /* use default */
+      wc->aml_decision = TALER_AML_NORMAL;
+    }
+
+    switch (wc->aml_decision)
+    {
+    case TALER_AML_NORMAL:
+      if (0 >= TALER_amount_cmp (&merge_amount,
+                                 &threshold))
+      {
+        /* merge_amount <= threshold, continue withdraw below */
+        break;
+      }
+      wc->aml_decision = TALER_AML_PENDING;
+      qs = TEH_plugin->trigger_aml_process (TEH_plugin->cls,
+                                            &reserve_h_payto,
+                                            &merge_amount);
+      if (qs <= 0)
+      {
+        GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+        if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+          *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                                 
MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                                 
TALER_EC_GENERIC_DB_STORE_FAILED,
+                                                 "trigger_aml_process");
+        return qs;
+      }
+      return qs;
+    case TALER_AML_PENDING:
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "AML already pending, doing nothing\n");
+      return qs;
+    case TALER_AML_FROZEN:
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Account frozen, doing nothing\n");
+      return qs;
+    }
+  }
+
+  /* Check if the money came from a wire transfer */
   qs = TEH_plugin->reserves_get_origin (TEH_plugin->cls,
                                         &wc->collectable.reserve_pub,
-                                        &wc->h_payto);
+                                        &wc->h_account_payto);
   if (qs < 0)
     return qs;
-  /* If no results, reserve was created by merge,
-     in which case no KYC check is required as the
-     merge already did that. */
+  /* If no results, reserve was created by merge, in which case no KYC check
+     is required as the merge already did that. */
   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
   {
     const char *kyc_required;
 
     qs = TALER_KYCLOGIC_kyc_test_required (
       TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW,
-      &wc->h_payto,
+      &wc->h_account_payto,
       TEH_plugin->select_satisfied_kyc_processes,
       TEH_plugin->cls,
       &withdraw_amount_cb,
@@ -191,7 +317,7 @@ withdraw_transaction (void *cls,
       return TEH_plugin->insert_kyc_requirement_for_account (
         TEH_plugin->cls,
         kyc_required,
-        &wc->h_payto,
+        &wc->h_account_payto,
         &wc->kyc.requirement_row);
     }
   }
@@ -515,8 +641,13 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
 
   if (! wc.kyc.ok)
     return TEH_RESPONSE_reply_kyc_required (rc->connection,
-                                            &wc.h_payto,
+                                            &wc.h_account_payto,
                                             &wc.kyc);
+
+  if (TALER_AML_NORMAL != wc.aml_decision)
+    return TEH_RESPONSE_reply_aml_blocked (rc->connection,
+                                           wc.aml_decision);
+
   {
     MHD_RESULT ret;
 
diff --git a/src/exchange/test_taler_exchange_httpd.conf 
b/src/exchange/test_taler_exchange_httpd.conf
index 9bd4851f..0af23b9d 100644
--- a/src/exchange/test_taler_exchange_httpd.conf
+++ b/src/exchange/test_taler_exchange_httpd.conf
@@ -7,6 +7,7 @@ TALER_RUNTIME_DIR = 
${TMPDIR:-${TMP:-/tmp}}/${USER:-}/taler-system-runtime/
 # Currency supported by the exchange (can only be one)
 CURRENCY = EUR
 CURRENCY_ROUND_UNIT = EUR:0.01
+AML_THRESHOLD = EUR:1000000
 
 [auditor]
 TINY_AMOUNT = EUR:0.01
diff --git a/src/exchangedb/pg_reserves_in_insert.c 
b/src/exchangedb/pg_reserves_in_insert.c
index ac14cb56..da21a906 100644
--- a/src/exchangedb/pg_reserves_in_insert.c
+++ b/src/exchangedb/pg_reserves_in_insert.c
@@ -54,25 +54,6 @@ compute_notify_on_reserve (const struct 
TALER_ReservePublicKeyP *reserve_pub)
 }
 
 
-static void
-notify_on_reserve (struct PostgresClosure *pg,
-                   const struct TALER_ReservePublicKeyP *reserve_pub)
-{
-  struct TALER_ReserveEventP rep = {
-    .header.size = htons (sizeof (rep)),
-    .header.type = htons (TALER_DBEVENT_EXCHANGE_RESERVE_INCOMING),
-    .reserve_pub = *reserve_pub
-  };
-
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "Notifying on reserve!\n");
-  TEH_PG_event_notify (pg,
-                       &rep.header,
-                       NULL,
-                       0);
-}
-
-
 static enum GNUNET_DB_QueryStatus
 insert1 (struct PostgresClosure *pg,
          const struct TALER_EXCHANGEDB_ReserveInInfo reserves[1],
diff --git a/src/exchangedb/pg_select_aml_threshold.c 
b/src/exchangedb/pg_select_aml_threshold.c
index 723524ad..e67a57a3 100644
--- a/src/exchangedb/pg_select_aml_threshold.c
+++ b/src/exchangedb/pg_select_aml_threshold.c
@@ -41,7 +41,7 @@ TEH_PG_select_aml_threshold (
   uint32_t status32 = TALER_AML_NORMAL;
   struct GNUNET_PQ_ResultSpec rs[] = {
     TALER_PQ_RESULT_SPEC_AMOUNT ("threshold",
-                                 &threshold),
+                                 threshold),
     GNUNET_PQ_result_spec_uint32 ("status",
                                   &status32),
     GNUNET_PQ_result_spec_end

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