merchant

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

taler-merchant-depositcheck.c (30772B)


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