merchant

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

taler-merchant-depositcheck.c (29516B)


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