merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

taler-merchant-depositcheck.c (30089B)


      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 "taler/platform.h"
     22 #include "microhttpd.h"
     23 #include <gnunet/gnunet_util_lib.h>
     24 #include <jansson.h>
     25 #include <pthread.h>
     26 #include "taler/taler_merchant_util.h"
     27 #include "taler/taler_merchantdb_lib.h"
     28 #include "taler/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_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_GetDepositsHandle *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 shutdown_task (void *cls)
    265 {
    266   struct Child *c;
    267   struct ExchangeInteraction *w;
    268 
    269   (void) cls;
    270   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    271               "Running shutdown\n");
    272   if (NULL != eh)
    273   {
    274     db_plugin->event_listen_cancel (eh);
    275     eh = NULL;
    276   }
    277   if (NULL != keys_eh)
    278   {
    279     db_plugin->event_listen_cancel (keys_eh);
    280     keys_eh = NULL;
    281   }
    282   if (NULL != task)
    283   {
    284     GNUNET_SCHEDULER_cancel (task);
    285     task = NULL;
    286   }
    287   while (NULL != (w = w_head))
    288   {
    289     GNUNET_CONTAINER_DLL_remove (w_head,
    290                                  w_tail,
    291                                  w);
    292     if (NULL != w->dgh)
    293     {
    294       TALER_EXCHANGE_get_deposits_cancel (w->dgh);
    295       w->dgh = NULL;
    296     }
    297     w_count--;
    298     GNUNET_free (w->instance_id);
    299     GNUNET_free (w);
    300   }
    301   while (NULL != (c = c_head))
    302   {
    303     GNUNET_CONTAINER_DLL_remove (c_head,
    304                                  c_tail,
    305                                  c);
    306     if (NULL != c->rt)
    307     {
    308       GNUNET_SCHEDULER_cancel (c->rt);
    309       c->rt = NULL;
    310     }
    311     if (NULL != c->cwh)
    312     {
    313       GNUNET_wait_child_cancel (c->cwh);
    314       c->cwh = NULL;
    315     }
    316     if (NULL != c->process)
    317     {
    318       enum GNUNET_OS_ProcessStatusType type
    319         = GNUNET_OS_PROCESS_UNKNOWN;
    320       unsigned long code = 0;
    321 
    322       GNUNET_break (GNUNET_OK ==
    323                     GNUNET_process_kill (c->process,
    324                                          SIGTERM));
    325       GNUNET_break (GNUNET_OK ==
    326                     GNUNET_process_wait (c->process,
    327                                          true,
    328                                          &type,
    329                                          &code));
    330       if ( (GNUNET_OS_PROCESS_EXITED != type) ||
    331            (0 != code) )
    332         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    333                     "Process for exchange %s had trouble (%d/%d)\n",
    334                     c->base_url,
    335                     (int) type,
    336                     (int) code);
    337       GNUNET_process_destroy (c->process);
    338     }
    339     GNUNET_free (c->base_url);
    340     GNUNET_free (c);
    341   }
    342   if (NULL != db_plugin)
    343   {
    344     db_plugin->rollback (db_plugin->cls); /* just in case */
    345     TALER_MERCHANTDB_plugin_unload (db_plugin);
    346     db_plugin = NULL;
    347   }
    348   cfg = NULL;
    349   if (NULL != ctx)
    350   {
    351     GNUNET_CURL_fini (ctx);
    352     ctx = NULL;
    353   }
    354   if (NULL != rc)
    355   {
    356     GNUNET_CURL_gnunet_rc_destroy (rc);
    357     rc = NULL;
    358   }
    359 }
    360 
    361 
    362 /**
    363  * Task to get more deposits to work on from the database.
    364  *
    365  * @param cls NULL
    366  */
    367 static void
    368 select_work (void *cls);
    369 
    370 
    371 /**
    372  * Make sure to run the select_work() task at
    373  * the @a next_deadline.
    374  *
    375  * @param deadline time when work becomes ready
    376  */
    377 static void
    378 run_at (struct GNUNET_TIME_Absolute deadline)
    379 {
    380   if ( (NULL != task) &&
    381        (GNUNET_TIME_absolute_cmp (deadline,
    382                                   >,
    383                                   next_deadline)) )
    384   {
    385     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    386                 "Not scheduling for %s yet, already have earlier task pending\n",
    387                 GNUNET_TIME_absolute2s (deadline));
    388     return;
    389   }
    390   if (NULL == keys)
    391   {
    392     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    393                 "Not scheduling for %s yet, no /keys available\n",
    394                 GNUNET_TIME_absolute2s (deadline));
    395     return; /* too early */
    396   }
    397   next_deadline = deadline;
    398   if (NULL != task)
    399     GNUNET_SCHEDULER_cancel (task);
    400   task = GNUNET_SCHEDULER_add_at (deadline,
    401                                   &select_work,
    402                                   NULL);
    403 }
    404 
    405 
    406 /**
    407  * Function called with detailed wire transfer data.
    408  *
    409  * @param cls closure with a `struct ExchangeInteraction *`
    410  * @param dr HTTP response data
    411  */
    412 static void
    413 deposit_get_cb (
    414   void *cls,
    415   const struct TALER_EXCHANGE_GetDepositsResponse *dr)
    416 {
    417   struct ExchangeInteraction *w = cls;
    418   struct GNUNET_TIME_Absolute future_retry;
    419 
    420   w->dgh = NULL;
    421   future_retry
    422     = GNUNET_TIME_relative_to_absolute (w->retry_backoff);
    423   switch (dr->hr.http_status)
    424   {
    425   case MHD_HTTP_OK:
    426     {
    427       enum GNUNET_DB_QueryStatus qs;
    428 
    429       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    430                   "Exchange returned wire transfer over %s for deposited coin %s\n",
    431                   TALER_amount2s (&dr->details.ok.coin_contribution),
    432                   TALER_B2S (&w->coin_pub));
    433       qs = db_plugin->insert_deposit_to_transfer (
    434         db_plugin->cls,
    435         w->deposit_serial,
    436         &w->h_wire,
    437         exchange_url,
    438         &dr->details.ok);
    439       if (qs <= 0)
    440       {
    441         GNUNET_break (0);
    442         GNUNET_SCHEDULER_shutdown ();
    443         return;
    444       }
    445       break;
    446     }
    447   case MHD_HTTP_ACCEPTED:
    448     {
    449       /* got a 'preliminary' reply from the exchange,
    450          remember our target UUID */
    451       enum GNUNET_DB_QueryStatus qs;
    452       struct GNUNET_TIME_Timestamp now;
    453 
    454       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    455                   "Exchange returned KYC requirement (%d) for deposited coin %s\n",
    456                   dr->details.accepted.kyc_ok,
    457                   TALER_B2S (&w->coin_pub));
    458       now = GNUNET_TIME_timestamp_get ();
    459       qs = db_plugin->account_kyc_set_failed (
    460         db_plugin->cls,
    461         w->instance_id,
    462         &w->h_wire,
    463         exchange_url,
    464         now,
    465         MHD_HTTP_ACCEPTED,
    466         dr->details.accepted.kyc_ok);
    467       if (qs < 0)
    468       {
    469         GNUNET_break (0);
    470         GNUNET_SCHEDULER_shutdown ();
    471         return;
    472       }
    473       if (dr->details.accepted.kyc_ok)
    474       {
    475         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    476                     "Bumping wire transfer deadline in DB to %s as that is when we will retry\n",
    477                     GNUNET_TIME_absolute2s (future_retry));
    478         qs = db_plugin->update_deposit_confirmation_status (
    479           db_plugin->cls,
    480           w->deposit_serial,
    481           true, /* need to try again in the future! */
    482           GNUNET_TIME_absolute_to_timestamp (future_retry),
    483           MHD_HTTP_ACCEPTED,
    484           TALER_EC_NONE,
    485           "Exchange reported 202 Accepted but no KYC block");
    486         if (qs < 0)
    487         {
    488           GNUNET_break (0);
    489           GNUNET_SCHEDULER_shutdown ();
    490           return;
    491         }
    492       }
    493       else
    494       {
    495         future_retry
    496           = GNUNET_TIME_absolute_max (
    497               future_retry,
    498               GNUNET_TIME_relative_to_absolute (
    499                 KYC_RETRY_DELAY));
    500         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    501                     "Bumping wire transfer deadline in DB to %s as that is when we will retry\n",
    502                     GNUNET_TIME_absolute2s (future_retry));
    503         qs = db_plugin->update_deposit_confirmation_status (
    504           db_plugin->cls,
    505           w->deposit_serial,
    506           true /* need to try again in the future */,
    507           GNUNET_TIME_absolute_to_timestamp (future_retry),
    508           MHD_HTTP_ACCEPTED,
    509           TALER_EC_NONE,
    510           "Exchange reported 202 Accepted due to KYC/AML block");
    511         if (qs < 0)
    512         {
    513           GNUNET_break (0);
    514           GNUNET_SCHEDULER_shutdown ();
    515           return;
    516         }
    517       }
    518       break;
    519     }
    520   default:
    521     {
    522       enum GNUNET_DB_QueryStatus qs;
    523       bool retry_needed = false;
    524 
    525       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    526                   "Exchange %s returned tracking failure for deposited coin %s: %u\n",
    527                   exchange_url,
    528                   TALER_B2S (&w->coin_pub),
    529                   dr->hr.http_status);
    530       /* rough classification by HTTP status group */
    531       switch (dr->hr.http_status / 100)
    532       {
    533       case 0:
    534         /* timeout */
    535         retry_needed = true;
    536         break;
    537       case 1:
    538       case 2:
    539       case 3:
    540         /* very strange */
    541         retry_needed = false;
    542         break;
    543       case 4:
    544         /* likely fatal */
    545         retry_needed = false;
    546         break;
    547       case 5:
    548         /* likely transient */
    549         retry_needed = true;
    550         break;
    551       }
    552       qs = db_plugin->update_deposit_confirmation_status (
    553         db_plugin->cls,
    554         w->deposit_serial,
    555         retry_needed,
    556         GNUNET_TIME_absolute_to_timestamp (future_retry),
    557         (uint32_t) dr->hr.http_status,
    558         dr->hr.ec,
    559         dr->hr.hint);
    560       if (qs < 0)
    561       {
    562         GNUNET_break (0);
    563         GNUNET_SCHEDULER_shutdown ();
    564         return;
    565       }
    566       break;
    567     }
    568   } /* end switch */
    569 
    570   GNUNET_CONTAINER_DLL_remove (w_head,
    571                                w_tail,
    572                                w);
    573   w_count--;
    574   GNUNET_free (w->instance_id);
    575   GNUNET_free (w);
    576   GNUNET_assert (NULL != keys);
    577   if (0 == w_count)
    578   {
    579     /* We only SELECT() again after having finished
    580        all requests, as otherwise we'll most like
    581        just SELECT() those again that are already
    582        being requested; alternatively, we could
    583        update the retry_time already on SELECT(),
    584        but this should be easier on the DB. */
    585     if (NULL != task)
    586       GNUNET_SCHEDULER_cancel (task);
    587     task = GNUNET_SCHEDULER_add_now (&select_work,
    588                                      NULL);
    589   }
    590 }
    591 
    592 
    593 /**
    594  * Typically called by `select_work`.
    595  *
    596  * @param cls NULL
    597  * @param deposit_serial identifies the deposit operation
    598  * @param wire_deadline when is the wire due
    599  * @param retry_time current value for the retry backoff
    600  * @param h_contract_terms hash of the contract terms
    601  * @param merchant_priv private key of the merchant
    602  * @param instance_id row ID of the instance
    603  * @param h_wire hash of the merchant's wire account into
    604  * @param amount_with_fee amount the exchange will deposit for this coin
    605  * @param deposit_fee fee the exchange will charge for this coin which the deposit was made
    606  * @param coin_pub public key of the deposited coin
    607  */
    608 static void
    609 pending_deposits_cb (
    610   void *cls,
    611   uint64_t deposit_serial,
    612   struct GNUNET_TIME_Absolute wire_deadline,
    613   struct GNUNET_TIME_Absolute retry_time,
    614   const struct TALER_PrivateContractHashP *h_contract_terms,
    615   const struct TALER_MerchantPrivateKeyP *merchant_priv,
    616   const char *instance_id,
    617   const struct TALER_MerchantWireHashP *h_wire,
    618   const struct TALER_Amount *amount_with_fee,
    619   const struct TALER_Amount *deposit_fee,
    620   const struct TALER_CoinSpendPublicKeyP *coin_pub)
    621 {
    622   struct ExchangeInteraction *w;
    623   struct GNUNET_TIME_Absolute mx
    624     = GNUNET_TIME_absolute_max (wire_deadline,
    625                                 retry_time);
    626   struct GNUNET_TIME_Relative retry_backoff;
    627 
    628   (void) cls;
    629   if (GNUNET_TIME_absolute_is_future (mx))
    630   {
    631     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    632                 "Pending deposit should be checked next at %s\n",
    633                 GNUNET_TIME_absolute2s (mx));
    634     run_at (mx);
    635     return;
    636   }
    637   if (GNUNET_TIME_absolute_is_zero (retry_time))
    638     retry_backoff = GNUNET_TIME_absolute_get_duration (wire_deadline);
    639   else
    640     retry_backoff = GNUNET_TIME_absolute_get_difference (wire_deadline,
    641                                                          retry_time);
    642   w = GNUNET_new (struct ExchangeInteraction);
    643   w->deposit_serial = deposit_serial;
    644   w->wire_deadline = wire_deadline;
    645   w->retry_backoff = GNUNET_TIME_randomized_backoff (retry_backoff,
    646                                                      GNUNET_TIME_UNIT_DAYS);
    647   w->h_contract_terms = *h_contract_terms;
    648   w->merchant_priv = *merchant_priv;
    649   w->h_wire = *h_wire;
    650   w->amount_with_fee = *amount_with_fee;
    651   w->deposit_fee = *deposit_fee;
    652   w->coin_pub = *coin_pub;
    653   w->instance_id = GNUNET_strdup (instance_id);
    654   GNUNET_CONTAINER_DLL_insert (w_head,
    655                                w_tail,
    656                                w);
    657   w_count++;
    658   GNUNET_assert (NULL != keys);
    659   if (GNUNET_TIME_absolute_is_past (
    660         keys->key_data_expiration.abs_time))
    661   {
    662     /* Parent should re-start us, then we will re-fetch /keys */
    663     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    664                 "/keys expired, shutting down\n");
    665     GNUNET_SCHEDULER_shutdown ();
    666     return;
    667   }
    668   GNUNET_assert (NULL == w->dgh);
    669   w->dgh = TALER_EXCHANGE_get_deposits_create (
    670     ctx,
    671     exchange_url,
    672     keys,
    673     &w->merchant_priv,
    674     &w->h_wire,
    675     &w->h_contract_terms,
    676     &w->coin_pub);
    677   if (NULL == w->dgh)
    678   {
    679     GNUNET_break (0);
    680     GNUNET_SCHEDULER_shutdown ();
    681     return;
    682   }
    683   if (TALER_EC_NONE !=
    684       TALER_EXCHANGE_get_deposits_start (w->dgh,
    685                                          &deposit_get_cb,
    686                                          w))
    687   {
    688     GNUNET_break (0);
    689     TALER_EXCHANGE_get_deposits_cancel (w->dgh);
    690     w->dgh = NULL;
    691     GNUNET_SCHEDULER_shutdown ();
    692     return;
    693   }
    694 }
    695 
    696 
    697 /**
    698  * Function called on events received from Postgres.
    699  *
    700  * @param cls closure, NULL
    701  * @param extra additional event data provided, timestamp with wire deadline
    702  * @param extra_size number of bytes in @a extra
    703  */
    704 static void
    705 db_notify (void *cls,
    706            const void *extra,
    707            size_t extra_size)
    708 {
    709   struct GNUNET_TIME_Absolute deadline;
    710   struct GNUNET_TIME_AbsoluteNBO nbo_deadline;
    711 
    712   (void) cls;
    713   if (sizeof (nbo_deadline) != extra_size)
    714   {
    715     GNUNET_break (0);
    716     return;
    717   }
    718   if (0 != w_count)
    719     return; /* already at work! */
    720   memcpy (&nbo_deadline,
    721           extra,
    722           extra_size);
    723   deadline = GNUNET_TIME_absolute_ntoh (nbo_deadline);
    724   run_at (deadline);
    725 }
    726 
    727 
    728 static void
    729 select_work (void *cls)
    730 {
    731   bool retry = false;
    732   uint64_t limit = CONCURRENCY_LIMIT - w_count;
    733 
    734   (void) cls;
    735   task = NULL;
    736   GNUNET_assert (w_count <= CONCURRENCY_LIMIT);
    737   GNUNET_assert (NULL != keys);
    738   if (0 == limit)
    739   {
    740     GNUNET_break (0);
    741     return;
    742   }
    743   if (GNUNET_TIME_absolute_is_past (
    744         keys->key_data_expiration.abs_time))
    745   {
    746     /* Parent should re-start us, then we will re-fetch /keys */
    747     GNUNET_SCHEDULER_shutdown ();
    748     return;
    749   }
    750   while (1)
    751   {
    752     enum GNUNET_DB_QueryStatus qs;
    753 
    754     db_plugin->preflight (db_plugin->cls);
    755     if (retry)
    756       limit = 1;
    757     qs = db_plugin->lookup_pending_deposits (
    758       db_plugin->cls,
    759       exchange_url,
    760       limit,
    761       retry,
    762       &pending_deposits_cb,
    763       NULL);
    764     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    765                 "Looking up pending deposits query status was %d\n",
    766                 (int) qs);
    767     switch (qs)
    768     {
    769     case GNUNET_DB_STATUS_HARD_ERROR:
    770     case GNUNET_DB_STATUS_SOFT_ERROR:
    771       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    772                   "Transaction failed!\n");
    773       global_ret = EXIT_FAILURE;
    774       GNUNET_SCHEDULER_shutdown ();
    775       return;
    776     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    777       if (test_mode)
    778       {
    779         GNUNET_SCHEDULER_shutdown ();
    780         return;
    781       }
    782       if (retry)
    783         return; /* nothing left */
    784       retry = true;
    785       continue;
    786     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    787     default:
    788       /* wait for async completion, then select more work. */
    789       return;
    790     }
    791   }
    792 }
    793 
    794 
    795 /**
    796  * Start a copy of this process with the exchange URL
    797  * set to the given @a base_url
    798  *
    799  * @param base_url base URL to run with
    800  */
    801 static struct GNUNET_Process *
    802 start_worker (const char *base_url)
    803 {
    804   struct GNUNET_Process *p;
    805   char toff[30];
    806   long long zo;
    807   enum GNUNET_GenericReturnValue ret;
    808 
    809   zo = GNUNET_TIME_get_offset ();
    810   GNUNET_snprintf (toff,
    811                    sizeof (toff),
    812                    "%lld",
    813                    zo);
    814   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    815               "Launching worker for exchange `%s' using `%s`\n",
    816               base_url,
    817               NULL == cfg_filename
    818               ? "<default>"
    819               : cfg_filename);
    820   p = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR);
    821 
    822   if (NULL == cfg_filename)
    823     ret = GNUNET_process_run_command_va (
    824       p,
    825       "taler-merchant-depositcheck",
    826       "taler-merchant-depositcheck",
    827       "-e", base_url,
    828       "-L", "INFO",
    829       "-T", toff,
    830       test_mode ? "-t" : NULL,
    831       NULL);
    832   else
    833     ret = GNUNET_process_run_command_va (
    834       p,
    835       "taler-merchant-depositcheck",
    836       "taler-merchant-depositcheck",
    837       "-c", cfg_filename,
    838       "-e", base_url,
    839       "-L", "INFO",
    840       "-T", toff,
    841       test_mode ? "-t" : NULL,
    842       NULL);
    843   if (GNUNET_OK != ret)
    844   {
    845     GNUNET_process_destroy (p);
    846     return NULL;
    847   }
    848   return p;
    849 }
    850 
    851 
    852 /**
    853  * Restart worker process for the given child.
    854  *
    855  * @param cls a `struct Child *` that needs a worker.
    856  */
    857 static void
    858 restart_child (void *cls);
    859 
    860 
    861 /**
    862  * Function called upon death or completion of a child process.
    863  *
    864  * @param cls a `struct Child *`
    865  * @param type type of the process
    866  * @param exit_code status code of the process
    867  */
    868 static void
    869 child_done_cb (void *cls,
    870                enum GNUNET_OS_ProcessStatusType type,
    871                long unsigned int exit_code)
    872 {
    873   struct Child *c = cls;
    874 
    875   c->cwh = NULL;
    876   GNUNET_process_destroy (c->process);
    877   c->process = NULL;
    878   if ( (GNUNET_OS_PROCESS_EXITED != type) ||
    879        (0 != exit_code) )
    880   {
    881     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    882                 "Process for exchange %s had trouble (%d/%d)\n",
    883                 c->base_url,
    884                 (int) type,
    885                 (int) exit_code);
    886     GNUNET_SCHEDULER_shutdown ();
    887     global_ret = EXIT_NOTINSTALLED;
    888     return;
    889   }
    890   if (test_mode &&
    891       (! GNUNET_TIME_relative_is_zero (c->rd)) )
    892   {
    893     return;
    894   }
    895   if (GNUNET_TIME_absolute_is_future (c->next_start))
    896     c->rd = GNUNET_TIME_STD_BACKOFF (c->rd);
    897   else
    898     c->rd = GNUNET_TIME_UNIT_SECONDS;
    899   c->rt = GNUNET_SCHEDULER_add_at (c->next_start,
    900                                    &restart_child,
    901                                    c);
    902 }
    903 
    904 
    905 static void
    906 restart_child (void *cls)
    907 {
    908   struct Child *c = cls;
    909 
    910   c->rt = NULL;
    911   c->next_start = GNUNET_TIME_relative_to_absolute (c->rd);
    912   c->process = start_worker (c->base_url);
    913   if (NULL == c->process)
    914   {
    915     GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
    916                          "exec");
    917     global_ret = EXIT_NO_RESTART;
    918     GNUNET_SCHEDULER_shutdown ();
    919     return;
    920   }
    921   c->cwh = GNUNET_wait_child (c->process,
    922                               &child_done_cb,
    923                               c);
    924 }
    925 
    926 
    927 /**
    928  * Function to iterate over section.
    929  *
    930  * @param cls closure
    931  * @param section name of the section
    932  */
    933 static void
    934 cfg_iter_cb (void *cls,
    935              const char *section)
    936 {
    937   char *base_url;
    938   struct Child *c;
    939 
    940   if (0 !=
    941       strncasecmp (section,
    942                    "merchant-exchange-",
    943                    strlen ("merchant-exchange-")))
    944     return;
    945   if (GNUNET_YES ==
    946       GNUNET_CONFIGURATION_get_value_yesno (cfg,
    947                                             section,
    948                                             "DISABLED"))
    949     return;
    950   if (GNUNET_OK !=
    951       GNUNET_CONFIGURATION_get_value_string (cfg,
    952                                              section,
    953                                              "EXCHANGE_BASE_URL",
    954                                              &base_url))
    955   {
    956     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
    957                                section,
    958                                "EXCHANGE_BASE_URL");
    959     return;
    960   }
    961   c = GNUNET_new (struct Child);
    962   c->rd = GNUNET_TIME_UNIT_SECONDS;
    963   c->base_url = base_url;
    964   GNUNET_CONTAINER_DLL_insert (c_head,
    965                                c_tail,
    966                                c);
    967   c->rt = GNUNET_SCHEDULER_add_now (&restart_child,
    968                                     c);
    969 }
    970 
    971 
    972 /**
    973  * Trigger (re)loading of keys from DB.
    974  *
    975  * @param cls NULL
    976  * @param extra base URL of the exchange that changed
    977  * @param extra_len number of bytes in @a extra
    978  */
    979 static void
    980 update_exchange_keys (void *cls,
    981                       const void *extra,
    982                       size_t extra_len)
    983 {
    984   const char *url = extra;
    985 
    986   if ( (NULL == extra) ||
    987        (0 == extra_len) )
    988   {
    989     GNUNET_break (0);
    990     return;
    991   }
    992   if ('\0' != url[extra_len - 1])
    993   {
    994     GNUNET_break (0);
    995     return;
    996   }
    997   if (0 != strcmp (url,
    998                    exchange_url))
    999     return; /* not relevant for us */
   1000 
   1001   {
   1002     enum GNUNET_DB_QueryStatus qs;
   1003     struct GNUNET_TIME_Absolute earliest_retry;
   1004 
   1005     if (NULL != keys)
   1006     {
   1007       TALER_EXCHANGE_keys_decref (keys);
   1008       keys = NULL;
   1009     }
   1010     qs = db_plugin->select_exchange_keys (db_plugin->cls,
   1011                                           exchange_url,
   1012                                           &earliest_retry,
   1013                                           &keys);
   1014     if (qs < 0)
   1015     {
   1016       GNUNET_break (0);
   1017       GNUNET_SCHEDULER_shutdown ();
   1018       return;
   1019     }
   1020     if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) ||
   1021          (NULL == keys) )
   1022     {
   1023       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1024                   "No keys yet for `%s'\n",
   1025                   exchange_url);
   1026     }
   1027   }
   1028   if (NULL == keys)
   1029   {
   1030     if (NULL != task)
   1031     {
   1032       GNUNET_SCHEDULER_cancel (task);
   1033       task = NULL;
   1034     }
   1035   }
   1036   else
   1037   {
   1038     if (NULL == task)
   1039       task = GNUNET_SCHEDULER_add_now (&select_work,
   1040                                        NULL);
   1041   }
   1042 }
   1043 
   1044 
   1045 /**
   1046  * First task.
   1047  *
   1048  * @param cls closure, NULL
   1049  * @param args remaining command-line arguments
   1050  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
   1051  * @param c configuration
   1052  */
   1053 static void
   1054 run (void *cls,
   1055      char *const *args,
   1056      const char *cfgfile,
   1057      const struct GNUNET_CONFIGURATION_Handle *c)
   1058 {
   1059   (void) args;
   1060 
   1061   cfg = c;
   1062   if (NULL != cfgfile)
   1063     cfg_filename = GNUNET_strdup (cfgfile);
   1064   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1065               "Running with configuration %s\n",
   1066               cfgfile);
   1067   GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
   1068                                  NULL);
   1069   if (NULL == exchange_url)
   1070   {
   1071     GNUNET_CONFIGURATION_iterate_sections (c,
   1072                                            &cfg_iter_cb,
   1073                                            NULL);
   1074     if (NULL == c_head)
   1075     {
   1076       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1077                   "No exchanges found in configuration\n");
   1078       global_ret = EXIT_NOTCONFIGURED;
   1079       GNUNET_SCHEDULER_shutdown ();
   1080       return;
   1081     }
   1082     return;
   1083   }
   1084 
   1085   ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
   1086                           &rc);
   1087   rc = GNUNET_CURL_gnunet_rc_create (ctx);
   1088   if (NULL == ctx)
   1089   {
   1090     GNUNET_break (0);
   1091     GNUNET_SCHEDULER_shutdown ();
   1092     global_ret = EXIT_NO_RESTART;
   1093     return;
   1094   }
   1095   if (NULL ==
   1096       (db_plugin = TALER_MERCHANTDB_plugin_load (cfg)))
   1097   {
   1098     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1099                 "Failed to initialize DB subsystem\n");
   1100     GNUNET_SCHEDULER_shutdown ();
   1101     global_ret = EXIT_NOTCONFIGURED;
   1102     return;
   1103   }
   1104   if (GNUNET_OK !=
   1105       db_plugin->connect (db_plugin->cls))
   1106   {
   1107     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1108                 "Failed to connect to database. Consider running taler-merchant-dbinit!\n");
   1109     GNUNET_SCHEDULER_shutdown ();
   1110     global_ret = EXIT_NO_RESTART;
   1111     return;
   1112   }
   1113   {
   1114     struct GNUNET_DB_EventHeaderP es = {
   1115       .size = htons (sizeof (es)),
   1116       .type = htons (TALER_DBEVENT_MERCHANT_NEW_WIRE_DEADLINE)
   1117     };
   1118 
   1119     eh = db_plugin->event_listen (db_plugin->cls,
   1120                                   &es,
   1121                                   GNUNET_TIME_UNIT_FOREVER_REL,
   1122                                   &db_notify,
   1123                                   NULL);
   1124   }
   1125   {
   1126     struct GNUNET_DB_EventHeaderP es = {
   1127       .size = htons (sizeof (es)),
   1128       .type = htons (TALER_DBEVENT_MERCHANT_EXCHANGE_KEYS)
   1129     };
   1130 
   1131     keys_eh = db_plugin->event_listen (db_plugin->cls,
   1132                                        &es,
   1133                                        GNUNET_TIME_UNIT_FOREVER_REL,
   1134                                        &update_exchange_keys,
   1135                                        NULL);
   1136   }
   1137 
   1138   update_exchange_keys (NULL,
   1139                         exchange_url,
   1140                         strlen (exchange_url) + 1);
   1141 }
   1142 
   1143 
   1144 /**
   1145  * The main function of the taler-merchant-depositcheck
   1146  *
   1147  * @param argc number of arguments from the command line
   1148  * @param argv command line arguments
   1149  * @return 0 ok, 1 on error
   1150  */
   1151 int
   1152 main (int argc,
   1153       char *const *argv)
   1154 {
   1155   struct GNUNET_GETOPT_CommandLineOption options[] = {
   1156     GNUNET_GETOPT_option_string ('e',
   1157                                  "exchange",
   1158                                  "BASE_URL",
   1159                                  "limit us to checking deposits of this exchange",
   1160                                  &exchange_url),
   1161     GNUNET_GETOPT_option_timetravel ('T',
   1162                                      "timetravel"),
   1163     GNUNET_GETOPT_option_flag ('t',
   1164                                "test",
   1165                                "run in test mode and exit when idle",
   1166                                &test_mode),
   1167     GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION),
   1168     GNUNET_GETOPT_OPTION_END
   1169   };
   1170   enum GNUNET_GenericReturnValue ret;
   1171 
   1172   ret = GNUNET_PROGRAM_run (
   1173     TALER_MERCHANT_project_data (),
   1174     argc, argv,
   1175     "taler-merchant-depositcheck",
   1176     gettext_noop (
   1177       "background process that checks with the exchange on deposits that are past the wire deadline"),
   1178     options,
   1179     &run, NULL);
   1180   if (GNUNET_SYSERR == ret)
   1181     return EXIT_INVALIDARGUMENT;
   1182   if (GNUNET_NO == ret)
   1183     return EXIT_SUCCESS;
   1184   return global_ret;
   1185 }
   1186 
   1187 
   1188 /* end of taler-merchant-depositcheck.c */