merchant

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

taler-merchant-depositcheck.c (30661B)


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