LCOV - code coverage report
Current view: top level - backend - taler-merchant-depositcheck.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 186 357 52.1 %
Date: 2025-08-28 06:06:54 Functions: 11 13 84.6 %

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   Copyright (C) 2024, 2025 Taler Systems SA
       4             : 
       5             :   TALER is free software; you can redistribute it and/or modify it under the
       6             :   terms of the GNU Affero General Public License as published by the Free Software
       7             :   Foundation; either version 3, or (at your option) any later version.
       8             : 
       9             :   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
      10             :   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
      11             :   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
      12             : 
      13             :   You should have received a copy of the GNU Affero General Public License along with
      14             :   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
      15             : */
      16             : /**
      17             :  * @file taler-merchant-depositcheck.c
      18             :  * @brief Process that inquires with the exchange for deposits that should have been wired
      19             :  * @author Christian Grothoff
      20             :  */
      21             : #include "platform.h"
      22             : #include "microhttpd.h"
      23             : #include <gnunet/gnunet_util_lib.h>
      24             : #include <jansson.h>
      25             : #include <pthread.h>
      26             : #include "taler_merchant_util.h"
      27             : #include "taler_merchantdb_lib.h"
      28             : #include "taler_merchantdb_plugin.h"
      29             : #include <taler/taler_dbevents.h>
      30             : 
      31             : /**
      32             :  * How many requests do we make at most in parallel to the same exchange?
      33             :  */
      34             : #define CONCURRENCY_LIMIT 32
      35             : 
      36             : /**
      37             :  * How long do we not try a deposit check if the deposit
      38             :  * was put on hold due to a KYC/AML block?
      39             :  */
      40             : #define KYC_RETRY_DELAY GNUNET_TIME_UNIT_HOURS
      41             : 
      42             : /**
      43             :  * Information we keep per exchange.
      44             :  */
      45             : struct Child
      46             : {
      47             : 
      48             :   /**
      49             :    * Kept in a DLL.
      50             :    */
      51             :   struct Child *next;
      52             : 
      53             :   /**
      54             :    * Kept in a DLL.
      55             :    */
      56             :   struct Child *prev;
      57             : 
      58             :   /**
      59             :    * The child process.
      60             :    */
      61             :   struct GNUNET_OS_Process *process;
      62             : 
      63             :   /**
      64             :    * Wait handle.
      65             :    */
      66             :   struct GNUNET_ChildWaitHandle *cwh;
      67             : 
      68             :   /**
      69             :    * Which exchange is this state for?
      70             :    */
      71             :   char *base_url;
      72             : 
      73             :   /**
      74             :    * Task to restart the child.
      75             :    */
      76             :   struct GNUNET_SCHEDULER_Task *rt;
      77             : 
      78             :   /**
      79             :    * When should the child be restarted at the earliest?
      80             :    */
      81             :   struct GNUNET_TIME_Absolute next_start;
      82             : 
      83             :   /**
      84             :    * Current minimum delay between restarts, grows
      85             :    * exponentially if child exits before this time.
      86             :    */
      87             :   struct GNUNET_TIME_Relative rd;
      88             : 
      89             : };
      90             : 
      91             : 
      92             : /**
      93             :  * Information we keep per exchange interaction.
      94             :  */
      95             : struct ExchangeInteraction
      96             : {
      97             :   /**
      98             :    * Kept in a DLL.
      99             :    */
     100             :   struct ExchangeInteraction *next;
     101             : 
     102             :   /**
     103             :    * Kept in a DLL.
     104             :    */
     105             :   struct ExchangeInteraction *prev;
     106             : 
     107             :   /**
     108             :    * Handle for exchange interaction.
     109             :    */
     110             :   struct TALER_EXCHANGE_DepositGetHandle *dgh;
     111             : 
     112             :   /**
     113             :    * Wire deadline for the deposit.
     114             :    */
     115             :   struct GNUNET_TIME_Absolute wire_deadline;
     116             : 
     117             :   /**
     118             :    * Current value for the retry backoff
     119             :    */
     120             :   struct GNUNET_TIME_Relative retry_backoff;
     121             : 
     122             :   /**
     123             :    * Target account hash of the deposit.
     124             :    */
     125             :   struct TALER_MerchantWireHashP h_wire;
     126             : 
     127             :   /**
     128             :    * Deposited amount.
     129             :    */
     130             :   struct TALER_Amount amount_with_fee;
     131             : 
     132             :   /**
     133             :    * Deposit fee paid.
     134             :    */
     135             :   struct TALER_Amount deposit_fee;
     136             : 
     137             :   /**
     138             :    * Public key of the deposited coin.
     139             :    */
     140             :   struct TALER_CoinSpendPublicKeyP coin_pub;
     141             : 
     142             :   /**
     143             :    * Hash over the @e contract_terms.
     144             :    */
     145             :   struct TALER_PrivateContractHashP h_contract_terms;
     146             : 
     147             :   /**
     148             :    * Merchant instance's private key.
     149             :    */
     150             :   struct TALER_MerchantPrivateKeyP merchant_priv;
     151             : 
     152             :   /**
     153             :    * Serial number of the row in the deposits table
     154             :    * that we are processing.
     155             :    */
     156             :   uint64_t deposit_serial;
     157             : 
     158             :   /**
     159             :    * The instance the deposit belongs to.
     160             :    */
     161             :   char *instance_id;
     162             : 
     163             : };
     164             : 
     165             : 
     166             : /**
     167             :  * Head of list of children we forked.
     168             :  */
     169             : static struct Child *c_head;
     170             : 
     171             : /**
     172             :  * Tail of list of children we forked.
     173             :  */
     174             : static struct Child *c_tail;
     175             : 
     176             : /**
     177             :  * Key material of the exchange.
     178             :  */
     179             : static struct TALER_EXCHANGE_Keys *keys;
     180             : 
     181             : /**
     182             :  * Head of list of active exchange interactions.
     183             :  */
     184             : static struct ExchangeInteraction *w_head;
     185             : 
     186             : /**
     187             :  * Tail of list of active exchange interactions.
     188             :  */
     189             : static struct ExchangeInteraction *w_tail;
     190             : 
     191             : /**
     192             :  * Number of active entries in the @e w_head list.
     193             :  */
     194             : static uint64_t w_count;
     195             : 
     196             : /**
     197             :  * Notification handler from database on new work.
     198             :  */
     199             : static struct GNUNET_DB_EventHandler *eh;
     200             : 
     201             : /**
     202             :  * Notification handler from database on new keys.
     203             :  */
     204             : static struct GNUNET_DB_EventHandler *keys_eh;
     205             : 
     206             : /**
     207             :  * The merchant's configuration.
     208             :  */
     209             : static const struct GNUNET_CONFIGURATION_Handle *cfg;
     210             : 
     211             : /**
     212             :  * Name of the configuration file we use.
     213             :  */
     214             : static char *cfg_filename;
     215             : 
     216             : /**
     217             :  * Our database plugin.
     218             :  */
     219             : static struct TALER_MERCHANTDB_Plugin *db_plugin;
     220             : 
     221             : /**
     222             :  * Next wire deadline that @e task is scheduled for.
     223             :  */
     224             : static struct GNUNET_TIME_Absolute next_deadline;
     225             : 
     226             : /**
     227             :  * Next task to run, if any.
     228             :  */
     229             : static struct GNUNET_SCHEDULER_Task *task;
     230             : 
     231             : /**
     232             :  * Handle to the context for interacting with the exchange.
     233             :  */
     234             : static struct GNUNET_CURL_Context *ctx;
     235             : 
     236             : /**
     237             :  * Scheduler context for running the @e ctx.
     238             :  */
     239             : static struct GNUNET_CURL_RescheduleContext *rc;
     240             : 
     241             : /**
     242             :  * Which exchange are we monitoring? NULL if we
     243             :  * are the parent of the workers.
     244             :  */
     245             : static char *exchange_url;
     246             : 
     247             : /**
     248             :  * Value to return from main(). 0 on success, non-zero on errors.
     249             :  */
     250             : static int global_ret;
     251             : 
     252             : /**
     253             :  * #GNUNET_YES if we are in test mode and should exit when idle.
     254             :  */
     255             : static int test_mode;
     256             : 
     257             : 
     258             : /**
     259             :  * We're being aborted with CTRL-C (or SIGTERM). Shut down.
     260             :  *
     261             :  * @param cls closure
     262             :  */
     263             : static void
     264          15 : shutdown_task (void *cls)
     265             : {
     266             :   struct Child *c;
     267             :   struct ExchangeInteraction *w;
     268             : 
     269             :   (void) cls;
     270          15 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     271             :               "Running shutdown\n");
     272          15 :   if (NULL != eh)
     273             :   {
     274          10 :     db_plugin->event_listen_cancel (eh);
     275          10 :     eh = NULL;
     276             :   }
     277          15 :   if (NULL != keys_eh)
     278             :   {
     279          10 :     db_plugin->event_listen_cancel (keys_eh);
     280          10 :     keys_eh = NULL;
     281             :   }
     282          15 :   if (NULL != task)
     283             :   {
     284           0 :     GNUNET_SCHEDULER_cancel (task);
     285           0 :     task = NULL;
     286             :   }
     287          15 :   while (NULL != (w = w_head))
     288             :   {
     289           0 :     GNUNET_CONTAINER_DLL_remove (w_head,
     290             :                                  w_tail,
     291             :                                  w);
     292           0 :     if (NULL != w->dgh)
     293             :     {
     294           0 :       TALER_EXCHANGE_deposits_get_cancel (w->dgh);
     295           0 :       w->dgh = NULL;
     296             :     }
     297           0 :     w_count--;
     298           0 :     GNUNET_free (w->instance_id);
     299           0 :     GNUNET_free (w);
     300             :   }
     301          20 :   while (NULL != (c = c_head))
     302             :   {
     303           5 :     GNUNET_CONTAINER_DLL_remove (c_head,
     304             :                                  c_tail,
     305             :                                  c);
     306           5 :     if (NULL != c->rt)
     307             :     {
     308           0 :       GNUNET_SCHEDULER_cancel (c->rt);
     309           0 :       c->rt = NULL;
     310             :     }
     311           5 :     if (NULL != c->cwh)
     312             :     {
     313           0 :       GNUNET_wait_child_cancel (c->cwh);
     314           0 :       c->cwh = NULL;
     315             :     }
     316           5 :     if (NULL != c->process)
     317             :     {
     318           0 :       enum GNUNET_OS_ProcessStatusType type
     319             :         = GNUNET_OS_PROCESS_UNKNOWN;
     320           0 :       unsigned long code = 0;
     321             : 
     322           0 :       GNUNET_break (0 ==
     323             :                     GNUNET_OS_process_kill (c->process,
     324             :                                             SIGTERM));
     325           0 :       GNUNET_break (GNUNET_OK ==
     326             :                     GNUNET_OS_process_wait_status (c->process,
     327             :                                                    &type,
     328             :                                                    &code));
     329           0 :       if ( (GNUNET_OS_PROCESS_EXITED != type) ||
     330           0 :            (0 != code) )
     331           0 :         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     332             :                     "Process for exchange %s had trouble (%d/%d)\n",
     333             :                     c->base_url,
     334             :                     (int) type,
     335             :                     (int) code);
     336           0 :       GNUNET_OS_process_destroy (c->process);
     337             :     }
     338           5 :     GNUNET_free (c->base_url);
     339           5 :     GNUNET_free (c);
     340             :   }
     341          15 :   if (NULL != db_plugin)
     342             :   {
     343          10 :     db_plugin->rollback (db_plugin->cls); /* just in case */
     344          10 :     TALER_MERCHANTDB_plugin_unload (db_plugin);
     345          10 :     db_plugin = NULL;
     346             :   }
     347          15 :   cfg = NULL;
     348          15 :   if (NULL != ctx)
     349             :   {
     350          10 :     GNUNET_CURL_fini (ctx);
     351          10 :     ctx = NULL;
     352             :   }
     353          15 :   if (NULL != rc)
     354             :   {
     355          10 :     GNUNET_CURL_gnunet_rc_destroy (rc);
     356          10 :     rc = NULL;
     357             :   }
     358          15 : }
     359             : 
     360             : 
     361             : /**
     362             :  * Task to get more deposits to work on from the database.
     363             :  *
     364             :  * @param cls NULL
     365             :  */
     366             : static void
     367             : select_work (void *cls);
     368             : 
     369             : 
     370             : /**
     371             :  * Make sure to run the select_work() task at
     372             :  * the @a next_deadline.
     373             :  *
     374             :  * @param deadline time when work becomes ready
     375             :  */
     376             : static void
     377           0 : run_at (struct GNUNET_TIME_Absolute deadline)
     378             : {
     379           0 :   if ( (NULL != task) &&
     380           0 :        (GNUNET_TIME_absolute_cmp (deadline,
     381             :                                   >,
     382             :                                   next_deadline)) )
     383             :   {
     384           0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     385             :                 "Not scheduling for %s yet, already have earlier task pending\n",
     386             :                 GNUNET_TIME_absolute2s (deadline));
     387           0 :     return;
     388             :   }
     389           0 :   if (NULL == keys)
     390             :   {
     391           0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     392             :                 "Not scheduling for %s yet, no /keys available\n",
     393             :                 GNUNET_TIME_absolute2s (deadline));
     394           0 :     return; /* too early */
     395             :   }
     396           0 :   next_deadline = deadline;
     397           0 :   if (NULL != task)
     398           0 :     GNUNET_SCHEDULER_cancel (task);
     399           0 :   task = GNUNET_SCHEDULER_add_at (deadline,
     400             :                                   &select_work,
     401             :                                   NULL);
     402             : }
     403             : 
     404             : 
     405             : /**
     406             :  * Function called with detailed wire transfer data.
     407             :  *
     408             :  * @param cls closure with a `struct ExchangeInteraction *`
     409             :  * @param dr HTTP response data
     410             :  */
     411             : static void
     412           8 : deposit_get_cb (
     413             :   void *cls,
     414             :   const struct TALER_EXCHANGE_GetDepositResponse *dr)
     415             : {
     416           8 :   struct ExchangeInteraction *w = cls;
     417             :   struct GNUNET_TIME_Absolute future_retry;
     418             : 
     419           8 :   w->dgh = NULL;
     420             :   future_retry
     421           8 :     = GNUNET_TIME_relative_to_absolute (w->retry_backoff);
     422           8 :   switch (dr->hr.http_status)
     423             :   {
     424           8 :   case MHD_HTTP_OK:
     425             :     {
     426             :       enum GNUNET_DB_QueryStatus qs;
     427             : 
     428           8 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     429             :                   "Exchange returned wire transfer over %s for deposited coin %s\n",
     430             :                   TALER_amount2s (&dr->details.ok.coin_contribution),
     431             :                   TALER_B2S (&w->coin_pub));
     432           8 :       qs = db_plugin->insert_deposit_to_transfer (
     433           8 :         db_plugin->cls,
     434             :         w->deposit_serial,
     435           8 :         &w->h_wire,
     436             :         exchange_url,
     437             :         &dr->details.ok);
     438           8 :       if (qs <= 0)
     439             :       {
     440           0 :         GNUNET_break (0);
     441           0 :         GNUNET_SCHEDULER_shutdown ();
     442           0 :         return;
     443             :       }
     444           8 :       break;
     445             :     }
     446           0 :   case MHD_HTTP_ACCEPTED:
     447             :     {
     448             :       /* got a 'preliminary' reply from the exchange,
     449             :          remember our target UUID */
     450             :       enum GNUNET_DB_QueryStatus qs;
     451             :       struct GNUNET_TIME_Timestamp now;
     452             : 
     453           0 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     454             :                   "Exchange returned KYC requirement (%d) for deposited coin %s\n",
     455             :                   dr->details.accepted.kyc_ok,
     456             :                   TALER_B2S (&w->coin_pub));
     457           0 :       now = GNUNET_TIME_timestamp_get ();
     458           0 :       qs = db_plugin->account_kyc_set_failed (
     459           0 :         db_plugin->cls,
     460           0 :         w->instance_id,
     461           0 :         &w->h_wire,
     462             :         exchange_url,
     463             :         now,
     464             :         MHD_HTTP_ACCEPTED,
     465           0 :         dr->details.accepted.kyc_ok);
     466           0 :       if (qs < 0)
     467             :       {
     468           0 :         GNUNET_break (0);
     469           0 :         GNUNET_SCHEDULER_shutdown ();
     470           0 :         return;
     471             :       }
     472           0 :       if (dr->details.accepted.kyc_ok)
     473             :       {
     474           0 :         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     475             :                     "Bumping wire transfer deadline in DB to %s as that is when we will retry\n",
     476             :                     GNUNET_TIME_absolute2s (future_retry));
     477           0 :         qs = db_plugin->update_deposit_confirmation_status (
     478           0 :           db_plugin->cls,
     479             :           w->deposit_serial,
     480             :           true, /* need to try again in the future! */
     481             :           GNUNET_TIME_absolute_to_timestamp (future_retry),
     482             :           MHD_HTTP_ACCEPTED,
     483             :           TALER_EC_NONE,
     484             :           "Exchange reported 202 Accepted but no KYC block");
     485           0 :         if (qs < 0)
     486             :         {
     487           0 :           GNUNET_break (0);
     488           0 :           GNUNET_SCHEDULER_shutdown ();
     489           0 :           return;
     490             :         }
     491             :       }
     492             :       else
     493             :       {
     494             :         future_retry
     495           0 :           = GNUNET_TIME_absolute_max (
     496             :               future_retry,
     497             :               GNUNET_TIME_relative_to_absolute (
     498             :                 KYC_RETRY_DELAY));
     499           0 :         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     500             :                     "Bumping wire transfer deadline in DB to %s as that is when we will retry\n",
     501             :                     GNUNET_TIME_absolute2s (future_retry));
     502           0 :         qs = db_plugin->update_deposit_confirmation_status (
     503           0 :           db_plugin->cls,
     504             :           w->deposit_serial,
     505             :           true /* need to try again in the future */,
     506             :           GNUNET_TIME_absolute_to_timestamp (future_retry),
     507             :           MHD_HTTP_ACCEPTED,
     508             :           TALER_EC_NONE,
     509             :           "Exchange reported 202 Accepted due to KYC/AML block");
     510           0 :         if (qs < 0)
     511             :         {
     512           0 :           GNUNET_break (0);
     513           0 :           GNUNET_SCHEDULER_shutdown ();
     514           0 :           return;
     515             :         }
     516             :       }
     517           0 :       break;
     518             :     }
     519           0 :   default:
     520             :     {
     521             :       enum GNUNET_DB_QueryStatus qs;
     522           0 :       bool retry_needed = false;
     523             : 
     524           0 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     525             :                   "Exchange %s returned tracking failure for deposited coin %s: %u\n",
     526             :                   exchange_url,
     527             :                   TALER_B2S (&w->coin_pub),
     528             :                   dr->hr.http_status);
     529             :       /* rough classification by HTTP status group */
     530           0 :       switch (dr->hr.http_status / 100)
     531             :       {
     532           0 :       case 0:
     533             :         /* timeout */
     534           0 :         retry_needed = true;
     535           0 :         break;
     536           0 :       case 1:
     537             :       case 2:
     538             :       case 3:
     539             :         /* very strange */
     540           0 :         retry_needed = false;
     541           0 :         break;
     542           0 :       case 4:
     543             :         /* likely fatal */
     544           0 :         retry_needed = false;
     545           0 :         break;
     546           0 :       case 5:
     547             :         /* likely transient */
     548           0 :         retry_needed = true;
     549           0 :         break;
     550             :       }
     551           0 :       qs = db_plugin->update_deposit_confirmation_status (
     552           0 :         db_plugin->cls,
     553             :         w->deposit_serial,
     554             :         retry_needed,
     555             :         GNUNET_TIME_absolute_to_timestamp (future_retry),
     556           0 :         (uint32_t) dr->hr.http_status,
     557           0 :         dr->hr.ec,
     558           0 :         dr->hr.hint);
     559           0 :       if (qs < 0)
     560             :       {
     561           0 :         GNUNET_break (0);
     562           0 :         GNUNET_SCHEDULER_shutdown ();
     563           0 :         return;
     564             :       }
     565           0 :       break;
     566             :     }
     567             :   } /* end switch */
     568             : 
     569           8 :   GNUNET_CONTAINER_DLL_remove (w_head,
     570             :                                w_tail,
     571             :                                w);
     572           8 :   w_count--;
     573           8 :   GNUNET_free (w->instance_id);
     574           8 :   GNUNET_free (w);
     575           8 :   GNUNET_assert (NULL != keys);
     576           8 :   if (0 == w_count)
     577             :   {
     578             :     /* We only SELECT() again after having finished
     579             :        all requests, as otherwise we'll most like
     580             :        just SELECT() those again that are already
     581             :        being requested; alternatively, we could
     582             :        update the retry_time already on SELECT(),
     583             :        but this should be easier on the DB. */
     584           8 :     if (NULL != task)
     585           0 :       GNUNET_SCHEDULER_cancel (task);
     586           8 :     task = GNUNET_SCHEDULER_add_now (&select_work,
     587             :                                      NULL);
     588             :   }
     589             : }
     590             : 
     591             : 
     592             : /**
     593             :  * Typically called by `select_work`.
     594             :  *
     595             :  * @param cls NULL
     596             :  * @param deposit_serial identifies the deposit operation
     597             :  * @param wire_deadline when is the wire due
     598             :  * @param retry_time current value for the retry backoff
     599             :  * @param h_contract_terms hash of the contract terms
     600             :  * @param merchant_priv private key of the merchant
     601             :  * @param instance_id row ID of the instance
     602             :  * @param h_wire hash of the merchant's wire account into
     603             :  * @param amount_with_fee amount the exchange will deposit for this coin
     604             :  * @param deposit_fee fee the exchange will charge for this coin which the deposit was made
     605             :  * @param coin_pub public key of the deposited coin
     606             :  */
     607             : static void
     608           8 : pending_deposits_cb (
     609             :   void *cls,
     610             :   uint64_t deposit_serial,
     611             :   struct GNUNET_TIME_Absolute wire_deadline,
     612             :   struct GNUNET_TIME_Absolute retry_time,
     613             :   const struct TALER_PrivateContractHashP *h_contract_terms,
     614             :   const struct TALER_MerchantPrivateKeyP *merchant_priv,
     615             :   const char *instance_id,
     616             :   const struct TALER_MerchantWireHashP *h_wire,
     617             :   const struct TALER_Amount *amount_with_fee,
     618             :   const struct TALER_Amount *deposit_fee,
     619             :   const struct TALER_CoinSpendPublicKeyP *coin_pub)
     620             : {
     621             :   struct ExchangeInteraction *w;
     622             :   struct GNUNET_TIME_Absolute mx
     623           8 :     = GNUNET_TIME_absolute_max (wire_deadline,
     624             :                                 retry_time);
     625             :   struct GNUNET_TIME_Relative retry_backoff;
     626             : 
     627             :   (void) cls;
     628           8 :   if (GNUNET_TIME_absolute_is_future (mx))
     629             :   {
     630           0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     631             :                 "Pending deposit should be checked next at %s\n",
     632             :                 GNUNET_TIME_absolute2s (mx));
     633           0 :     run_at (mx);
     634           0 :     return;
     635             :   }
     636           8 :   if (GNUNET_TIME_absolute_is_zero (retry_time))
     637           0 :     retry_backoff = GNUNET_TIME_absolute_get_duration (wire_deadline);
     638             :   else
     639           8 :     retry_backoff = GNUNET_TIME_absolute_get_difference (wire_deadline,
     640             :                                                          retry_time);
     641           8 :   w = GNUNET_new (struct ExchangeInteraction);
     642           8 :   w->deposit_serial = deposit_serial;
     643           8 :   w->wire_deadline = wire_deadline;
     644           8 :   w->retry_backoff = GNUNET_TIME_randomized_backoff (retry_backoff,
     645             :                                                      GNUNET_TIME_UNIT_DAYS);
     646           8 :   w->h_contract_terms = *h_contract_terms;
     647           8 :   w->merchant_priv = *merchant_priv;
     648           8 :   w->h_wire = *h_wire;
     649           8 :   w->amount_with_fee = *amount_with_fee;
     650           8 :   w->deposit_fee = *deposit_fee;
     651           8 :   w->coin_pub = *coin_pub;
     652           8 :   w->instance_id = GNUNET_strdup (instance_id);
     653           8 :   GNUNET_CONTAINER_DLL_insert (w_head,
     654             :                                w_tail,
     655             :                                w);
     656           8 :   w_count++;
     657           8 :   GNUNET_assert (NULL != keys);
     658           8 :   if (GNUNET_TIME_absolute_is_past (
     659           8 :         keys->key_data_expiration.abs_time))
     660             :   {
     661             :     /* Parent should re-start us, then we will re-fetch /keys */
     662           0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     663             :                 "/keys expired, shutting down\n");
     664           0 :     GNUNET_SCHEDULER_shutdown ();
     665           0 :     return;
     666             :   }
     667           8 :   GNUNET_assert (NULL == w->dgh);
     668           8 :   w->dgh = TALER_EXCHANGE_deposits_get (
     669             :     ctx,
     670             :     exchange_url,
     671             :     keys,
     672           8 :     &w->merchant_priv,
     673           8 :     &w->h_wire,
     674           8 :     &w->h_contract_terms,
     675           8 :     &w->coin_pub,
     676           8 :     GNUNET_TIME_UNIT_ZERO,
     677             :     &deposit_get_cb,
     678             :     w);
     679             : }
     680             : 
     681             : 
     682             : /**
     683             :  * Function called on events received from Postgres.
     684             :  *
     685             :  * @param cls closure, NULL
     686             :  * @param extra additional event data provided, timestamp with wire deadline
     687             :  * @param extra_size number of bytes in @a extra
     688             :  */
     689             : static void
     690           0 : db_notify (void *cls,
     691             :            const void *extra,
     692             :            size_t extra_size)
     693             : {
     694             :   struct GNUNET_TIME_Absolute deadline;
     695             :   struct GNUNET_TIME_AbsoluteNBO nbo_deadline;
     696             : 
     697             :   (void) cls;
     698           0 :   if (sizeof (nbo_deadline) != extra_size)
     699             :   {
     700           0 :     GNUNET_break (0);
     701           0 :     return;
     702             :   }
     703           0 :   if (0 != w_count)
     704           0 :     return; /* already at work! */
     705           0 :   memcpy (&nbo_deadline,
     706             :           extra,
     707             :           extra_size);
     708           0 :   deadline = GNUNET_TIME_absolute_ntoh (nbo_deadline);
     709           0 :   run_at (deadline);
     710             : }
     711             : 
     712             : 
     713             : static void
     714          18 : select_work (void *cls)
     715             : {
     716          18 :   bool retry = false;
     717          18 :   uint64_t limit = CONCURRENCY_LIMIT - w_count;
     718             : 
     719             :   (void) cls;
     720          18 :   task = NULL;
     721          18 :   GNUNET_assert (w_count <= CONCURRENCY_LIMIT);
     722          18 :   GNUNET_assert (NULL != keys);
     723          18 :   if (0 == limit)
     724             :   {
     725           0 :     GNUNET_break (0);
     726           0 :     return;
     727             :   }
     728          18 :   if (GNUNET_TIME_absolute_is_past (
     729          18 :         keys->key_data_expiration.abs_time))
     730             :   {
     731             :     /* Parent should re-start us, then we will re-fetch /keys */
     732           0 :     GNUNET_SCHEDULER_shutdown ();
     733           0 :     return;
     734             :   }
     735             :   while (1)
     736           0 :   {
     737             :     enum GNUNET_DB_QueryStatus qs;
     738             : 
     739          18 :     db_plugin->preflight (db_plugin->cls);
     740          18 :     if (retry)
     741           0 :       limit = 1;
     742          18 :     qs = db_plugin->lookup_pending_deposits (
     743          18 :       db_plugin->cls,
     744             :       exchange_url,
     745             :       limit,
     746             :       retry,
     747             :       &pending_deposits_cb,
     748             :       NULL);
     749          18 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     750             :                 "Looking up pending deposits query status was %d\n",
     751             :                 (int) qs);
     752          18 :     switch (qs)
     753             :     {
     754           0 :     case GNUNET_DB_STATUS_HARD_ERROR:
     755             :     case GNUNET_DB_STATUS_SOFT_ERROR:
     756           0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     757             :                   "Transaction failed!\n");
     758           0 :       global_ret = EXIT_FAILURE;
     759           0 :       GNUNET_SCHEDULER_shutdown ();
     760           0 :       return;
     761          10 :     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
     762          10 :       if (test_mode)
     763             :       {
     764          10 :         GNUNET_SCHEDULER_shutdown ();
     765          10 :         return;
     766             :       }
     767           0 :       if (retry)
     768           0 :         return; /* nothing left */
     769           0 :       retry = true;
     770           0 :       continue;
     771           8 :     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
     772             :     default:
     773             :       /* wait for async completion, then select more work. */
     774           8 :       return;
     775             :     }
     776             :   }
     777             : }
     778             : 
     779             : 
     780             : /**
     781             :  * Start a copy of this process with the exchange URL
     782             :  * set to the given @a base_url
     783             :  *
     784             :  * @param base_url base URL to run with
     785             :  */
     786             : static struct GNUNET_OS_Process *
     787           5 : start_worker (const char *base_url)
     788             : {
     789             :   char toff[30];
     790             :   long long zo;
     791             : 
     792           5 :   zo = GNUNET_TIME_get_offset ();
     793           5 :   GNUNET_snprintf (toff,
     794             :                    sizeof (toff),
     795             :                    "%lld",
     796             :                    zo);
     797           5 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     798             :               "Launching worker for exchange `%s' using `%s`\n",
     799             :               base_url,
     800             :               NULL == cfg_filename
     801             :               ? "<default>"
     802             :               : cfg_filename);
     803           5 :   if (NULL == cfg_filename)
     804           0 :     return GNUNET_OS_start_process (
     805             :       GNUNET_OS_INHERIT_STD_ALL,
     806             :       NULL,
     807             :       NULL,
     808             :       NULL,
     809             :       "taler-merchant-depositcheck",
     810             :       "taler-merchant-depositcheck",
     811             :       "-e", base_url,
     812             :       "-L", "INFO",
     813             :       "-T", toff,
     814           0 :       test_mode ? "-t" : NULL,
     815             :       NULL);
     816           5 :   return GNUNET_OS_start_process (
     817             :     GNUNET_OS_INHERIT_STD_ALL,
     818             :     NULL,
     819             :     NULL,
     820             :     NULL,
     821             :     "taler-merchant-depositcheck",
     822             :     "taler-merchant-depositcheck",
     823             :     "-c", cfg_filename,
     824             :     "-e", base_url,
     825             :     "-L", "INFO",
     826             :     "-T", toff,
     827           5 :     test_mode ? "-t" : NULL,
     828             :     NULL);
     829             : }
     830             : 
     831             : 
     832             : /**
     833             :  * Restart worker process for the given child.
     834             :  *
     835             :  * @param cls a `struct Child *` that needs a worker.
     836             :  */
     837             : static void
     838             : restart_child (void *cls);
     839             : 
     840             : 
     841             : /**
     842             :  * Function called upon death or completion of a child process.
     843             :  *
     844             :  * @param cls a `struct Child *`
     845             :  * @param type type of the process
     846             :  * @param exit_code status code of the process
     847             :  */
     848             : static void
     849           5 : child_done_cb (void *cls,
     850             :                enum GNUNET_OS_ProcessStatusType type,
     851             :                long unsigned int exit_code)
     852             : {
     853           5 :   struct Child *c = cls;
     854             : 
     855           5 :   c->cwh = NULL;
     856           5 :   GNUNET_OS_process_destroy (c->process);
     857           5 :   c->process = NULL;
     858           5 :   if ( (GNUNET_OS_PROCESS_EXITED != type) ||
     859             :        (0 != exit_code) )
     860             :   {
     861           0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     862             :                 "Process for exchange %s had trouble (%d/%d)\n",
     863             :                 c->base_url,
     864             :                 (int) type,
     865             :                 (int) exit_code);
     866           0 :     GNUNET_SCHEDULER_shutdown ();
     867           0 :     global_ret = EXIT_NOTINSTALLED;
     868           0 :     return;
     869             :   }
     870           5 :   if (test_mode &&
     871           5 :       (! GNUNET_TIME_relative_is_zero (c->rd)) )
     872             :   {
     873           5 :     return;
     874             :   }
     875           0 :   if (GNUNET_TIME_absolute_is_future (c->next_start))
     876           0 :     c->rd = GNUNET_TIME_STD_BACKOFF (c->rd);
     877             :   else
     878           0 :     c->rd = GNUNET_TIME_UNIT_SECONDS;
     879           0 :   c->rt = GNUNET_SCHEDULER_add_at (c->next_start,
     880             :                                    &restart_child,
     881             :                                    c);
     882             : }
     883             : 
     884             : 
     885             : static void
     886           5 : restart_child (void *cls)
     887             : {
     888           5 :   struct Child *c = cls;
     889             : 
     890           5 :   c->rt = NULL;
     891           5 :   c->next_start = GNUNET_TIME_relative_to_absolute (c->rd);
     892           5 :   c->process = start_worker (c->base_url);
     893           5 :   if (NULL == c->process)
     894             :   {
     895           0 :     GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
     896             :                          "exec");
     897           0 :     global_ret = EXIT_NO_RESTART;
     898           0 :     GNUNET_SCHEDULER_shutdown ();
     899           0 :     return;
     900             :   }
     901           5 :   c->cwh = GNUNET_wait_child (c->process,
     902             :                               &child_done_cb,
     903             :                               c);
     904             : }
     905             : 
     906             : 
     907             : /**
     908             :  * Function to iterate over section.
     909             :  *
     910             :  * @param cls closure
     911             :  * @param section name of the section
     912             :  */
     913             : static void
     914         209 : cfg_iter_cb (void *cls,
     915             :              const char *section)
     916             : {
     917             :   char *base_url;
     918             :   struct Child *c;
     919             : 
     920         209 :   if (0 !=
     921         209 :       strncasecmp (section,
     922             :                    "merchant-exchange-",
     923             :                    strlen ("merchant-exchange-")))
     924         204 :     return;
     925          15 :   if (GNUNET_YES ==
     926          15 :       GNUNET_CONFIGURATION_get_value_yesno (cfg,
     927             :                                             section,
     928             :                                             "DISABLED"))
     929          10 :     return;
     930           5 :   if (GNUNET_OK !=
     931           5 :       GNUNET_CONFIGURATION_get_value_string (cfg,
     932             :                                              section,
     933             :                                              "EXCHANGE_BASE_URL",
     934             :                                              &base_url))
     935             :   {
     936           0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
     937             :                                section,
     938             :                                "EXCHANGE_BASE_URL");
     939           0 :     return;
     940             :   }
     941           5 :   c = GNUNET_new (struct Child);
     942           5 :   c->rd = GNUNET_TIME_UNIT_SECONDS;
     943           5 :   c->base_url = base_url;
     944           5 :   GNUNET_CONTAINER_DLL_insert (c_head,
     945             :                                c_tail,
     946             :                                c);
     947           5 :   c->rt = GNUNET_SCHEDULER_add_now (&restart_child,
     948             :                                     c);
     949             : }
     950             : 
     951             : 
     952             : /**
     953             :  * Trigger (re)loading of keys from DB.
     954             :  *
     955             :  * @param cls NULL
     956             :  * @param extra base URL of the exchange that changed
     957             :  * @param extra_len number of bytes in @a extra
     958             :  */
     959             : static void
     960          10 : update_exchange_keys (void *cls,
     961             :                       const void *extra,
     962             :                       size_t extra_len)
     963             : {
     964          10 :   const char *url = extra;
     965             : 
     966          10 :   if ( (NULL == extra) ||
     967             :        (0 == extra_len) )
     968             :   {
     969           0 :     GNUNET_break (0);
     970           0 :     return;
     971             :   }
     972          10 :   if ('\0' != url[extra_len - 1])
     973             :   {
     974           0 :     GNUNET_break (0);
     975           0 :     return;
     976             :   }
     977          10 :   if (0 != strcmp (url,
     978             :                    exchange_url))
     979           0 :     return; /* not relevant for us */
     980             : 
     981             :   {
     982             :     enum GNUNET_DB_QueryStatus qs;
     983             :     struct GNUNET_TIME_Absolute earliest_retry;
     984             : 
     985          10 :     if (NULL != keys)
     986             :     {
     987           0 :       TALER_EXCHANGE_keys_decref (keys);
     988           0 :       keys = NULL;
     989             :     }
     990          10 :     qs = db_plugin->select_exchange_keys (db_plugin->cls,
     991             :                                           exchange_url,
     992             :                                           &earliest_retry,
     993             :                                           &keys);
     994          10 :     if (qs < 0)
     995             :     {
     996           0 :       GNUNET_break (0);
     997           0 :       GNUNET_SCHEDULER_shutdown ();
     998           0 :       return;
     999             :     }
    1000          10 :     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    1001             :     {
    1002           0 :       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    1003             :                   "No keys yet for `%s'\n",
    1004             :                   exchange_url);
    1005             :     }
    1006             :   }
    1007          10 :   if (NULL == keys)
    1008             :   {
    1009           0 :     if (NULL != task)
    1010             :     {
    1011           0 :       GNUNET_SCHEDULER_cancel (task);
    1012           0 :       task = NULL;
    1013             :     }
    1014             :   }
    1015             :   else
    1016             :   {
    1017          10 :     if (NULL == task)
    1018          10 :       task = GNUNET_SCHEDULER_add_now (&select_work,
    1019             :                                        NULL);
    1020             :   }
    1021             : }
    1022             : 
    1023             : 
    1024             : /**
    1025             :  * First task.
    1026             :  *
    1027             :  * @param cls closure, NULL
    1028             :  * @param args remaining command-line arguments
    1029             :  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
    1030             :  * @param c configuration
    1031             :  */
    1032             : static void
    1033          15 : run (void *cls,
    1034             :      char *const *args,
    1035             :      const char *cfgfile,
    1036             :      const struct GNUNET_CONFIGURATION_Handle *c)
    1037             : {
    1038             :   (void) args;
    1039             : 
    1040          15 :   cfg = c;
    1041          15 :   if (NULL != cfgfile)
    1042          15 :     cfg_filename = GNUNET_strdup (cfgfile);
    1043          15 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1044             :               "Running with configuration %s\n",
    1045             :               cfgfile);
    1046          15 :   GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
    1047             :                                  NULL);
    1048          15 :   if (NULL == exchange_url)
    1049             :   {
    1050           5 :     GNUNET_CONFIGURATION_iterate_sections (c,
    1051             :                                            &cfg_iter_cb,
    1052             :                                            NULL);
    1053           5 :     if (NULL == c_head)
    1054             :     {
    1055           0 :       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    1056             :                   "No exchanges found in configuration\n");
    1057           0 :       global_ret = EXIT_NOTCONFIGURED;
    1058           0 :       GNUNET_SCHEDULER_shutdown ();
    1059           0 :       return;
    1060             :     }
    1061           5 :     return;
    1062             :   }
    1063             : 
    1064          10 :   ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
    1065             :                           &rc);
    1066          10 :   rc = GNUNET_CURL_gnunet_rc_create (ctx);
    1067          10 :   if (NULL == ctx)
    1068             :   {
    1069           0 :     GNUNET_break (0);
    1070           0 :     GNUNET_SCHEDULER_shutdown ();
    1071           0 :     global_ret = EXIT_NO_RESTART;
    1072           0 :     return;
    1073             :   }
    1074          10 :   if (NULL ==
    1075          10 :       (db_plugin = TALER_MERCHANTDB_plugin_load (cfg)))
    1076             :   {
    1077           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1078             :                 "Failed to initialize DB subsystem\n");
    1079           0 :     GNUNET_SCHEDULER_shutdown ();
    1080           0 :     global_ret = EXIT_NOTCONFIGURED;
    1081           0 :     return;
    1082             :   }
    1083          10 :   if (GNUNET_OK !=
    1084          10 :       db_plugin->connect (db_plugin->cls))
    1085             :   {
    1086           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1087             :                 "Failed to connect to database. Consider running taler-merchant-dbinit!\n");
    1088           0 :     GNUNET_SCHEDULER_shutdown ();
    1089           0 :     global_ret = EXIT_NO_RESTART;
    1090           0 :     return;
    1091             :   }
    1092             :   {
    1093          10 :     struct GNUNET_DB_EventHeaderP es = {
    1094          10 :       .size = htons (sizeof (es)),
    1095          10 :       .type = htons (TALER_DBEVENT_MERCHANT_NEW_WIRE_DEADLINE)
    1096             :     };
    1097             : 
    1098          20 :     eh = db_plugin->event_listen (db_plugin->cls,
    1099             :                                   &es,
    1100          10 :                                   GNUNET_TIME_UNIT_FOREVER_REL,
    1101             :                                   &db_notify,
    1102             :                                   NULL);
    1103             :   }
    1104             :   {
    1105          10 :     struct GNUNET_DB_EventHeaderP es = {
    1106          10 :       .size = ntohs (sizeof (es)),
    1107          10 :       .type = ntohs (TALER_DBEVENT_MERCHANT_EXCHANGE_KEYS)
    1108             :     };
    1109             : 
    1110          20 :     keys_eh = db_plugin->event_listen (db_plugin->cls,
    1111             :                                        &es,
    1112          10 :                                        GNUNET_TIME_UNIT_FOREVER_REL,
    1113             :                                        &update_exchange_keys,
    1114             :                                        NULL);
    1115             :   }
    1116             : 
    1117          10 :   update_exchange_keys (NULL,
    1118             :                         exchange_url,
    1119          10 :                         strlen (exchange_url) + 1);
    1120             : }
    1121             : 
    1122             : 
    1123             : /**
    1124             :  * The main function of the taler-merchant-depositcheck
    1125             :  *
    1126             :  * @param argc number of arguments from the command line
    1127             :  * @param argv command line arguments
    1128             :  * @return 0 ok, 1 on error
    1129             :  */
    1130             : int
    1131          15 : main (int argc,
    1132             :       char *const *argv)
    1133             : {
    1134          15 :   struct GNUNET_GETOPT_CommandLineOption options[] = {
    1135          15 :     GNUNET_GETOPT_option_string ('e',
    1136             :                                  "exchange",
    1137             :                                  "BASE_URL",
    1138             :                                  "limit us to checking deposits of this exchange",
    1139             :                                  &exchange_url),
    1140          15 :     GNUNET_GETOPT_option_timetravel ('T',
    1141             :                                      "timetravel"),
    1142          15 :     GNUNET_GETOPT_option_flag ('t',
    1143             :                                "test",
    1144             :                                "run in test mode and exit when idle",
    1145             :                                &test_mode),
    1146          15 :     GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION),
    1147             :     GNUNET_GETOPT_OPTION_END
    1148             :   };
    1149             :   enum GNUNET_GenericReturnValue ret;
    1150             : 
    1151          15 :   ret = GNUNET_PROGRAM_run (
    1152             :     TALER_MERCHANT_project_data (),
    1153             :     argc, argv,
    1154             :     "taler-merchant-depositcheck",
    1155             :     gettext_noop (
    1156             :       "background process that checks with the exchange on deposits that are past the wire deadline"),
    1157             :     options,
    1158             :     &run, NULL);
    1159          15 :   if (GNUNET_SYSERR == ret)
    1160           0 :     return EXIT_INVALIDARGUMENT;
    1161          15 :   if (GNUNET_NO == ret)
    1162           0 :     return EXIT_SUCCESS;
    1163          15 :   return global_ret;
    1164             : }
    1165             : 
    1166             : 
    1167             : /* end of taler-merchant-depositcheck.c */

Generated by: LCOV version 1.16