merchant

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

taler-merchant-depositcheck.c (30184B)


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