merchant

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

taler-merchant-depositcheck.c (30271B)


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