merchant

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

taler-merchant-depositcheck.c (32203B)


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