merchant

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

taler-merchant-donaukeyupdate.c (29645B)


      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-donaukeyupdate.c
     18  * @brief Process that ensures our /keys data for all Donau instances is current
     19  * @author Bohdan Potuzhnyi
     20  * @author Christian Grothoff
     21  */
     22 #include "platform.h"
     23 #include "microhttpd.h"
     24 #include <gnunet/gnunet_util_lib.h>
     25 #include <jansson.h>
     26 #include <pthread.h>
     27 #include <taler/taler_dbevents.h>
     28 #include "donau/donau_service.h"
     29 #include "taler_merchant_util.h"
     30 #include "taler_merchantdb_lib.h"
     31 #include "taler_merchantdb_plugin.h"
     32 #include "taler_merchant_bank_lib.h"
     33 
     34 /**
     35  * Maximum frequency for the Donau interaction.
     36  */
     37 #define DONAU_MAXFREQ GNUNET_TIME_relative_multiply ( \
     38           GNUNET_TIME_UNIT_MINUTES, \
     39           5)
     40 
     41 /**
     42  * How many inquiries do we process concurrently at most.
     43  */
     44 #define OPEN_INQUIRY_LIMIT 1024
     45 
     46 /**
     47  * How often do we retry after DB serialization errors (at most)?
     48  */
     49 #define MAX_RETRIES 3
     50 
     51 /**
     52  * Information about a Donau instance.
     53  */
     54 struct Donau
     55 {
     56   /**
     57    * Pointer to the next Donau instance in the doubly linked list.
     58    */
     59   struct Donau *next;
     60 
     61   /**
     62    * Pointer to the previous Donau instance in the doubly linked list.
     63    */
     64   struct Donau *prev;
     65 
     66   /**
     67    * Base URL of the Donau instance being tracked.
     68    * This URL is used to query the Donau service for keys and other resources.
     69    */
     70   char *donau_url;
     71 
     72   /**
     73    * Expected currency of the donau.
     74    */
     75   char *currency;
     76 
     77   /**
     78    * Pointer to the keys obtained from the Donau instance.
     79    * This structure holds the cryptographic keys for the Donau instance.
     80    */
     81   struct DONAU_Keys *keys;
     82 
     83   /**
     84    * A handle for an ongoing /keys request to the Donau instance.
     85    * This is NULL when there is no active request.
     86    */
     87   struct DONAU_GetKeysHandle *conn;
     88 
     89   /**
     90    * Scheduler task for retrying a failed /keys request.
     91    * This task will trigger the next attempt to download the Donau keys if the previous request failed or needs to be retried.
     92    */
     93   struct GNUNET_SCHEDULER_Task *retry_task;
     94 
     95   /**
     96    * The earliest time at which the Donau instance can attempt another /keys request.
     97    * This is used to manage the timing between requests and ensure compliance with rate-limiting rules.
     98    */
     99   struct GNUNET_TIME_Absolute first_retry;
    100 
    101   /**
    102    * The delay between the next retry for fetching /keys.
    103    * Used to implement exponential backoff strategies for retries in case of failures.
    104    */
    105   struct GNUNET_TIME_Relative retry_delay;
    106 
    107   /**
    108    * A flag indicating whether this Donau instance is currently rate-limited.
    109    * If true, the instance is temporarily paused from making further requests due to reaching a limit.
    110    */
    111   bool limited;
    112 
    113   /**
    114    * Are we force-retrying a /keys download because some keys
    115    * were missing?
    116    */
    117   bool force_retry;
    118 };
    119 
    120 
    121 /**
    122  * Head of known Donau instances.
    123  */
    124 static struct Donau *d_head;
    125 
    126 /**
    127  * Tail of known Donau instances.
    128  */
    129 static struct Donau *d_tail;
    130 
    131 /**
    132  * Context for the charity force download.
    133  */
    134 struct ForceCharityCtx
    135 {
    136   /**
    137    * Pointer to the next ForceCharityCtx in the doubly linked list.
    138    */
    139   struct ForceCharityCtx *next;
    140 
    141   /**
    142    * Pointer to the previous ForceCharityCtx in the doubly linked list.
    143    */
    144   struct ForceCharityCtx *prev;
    145 
    146   /**
    147    * Serial of the Donau instance in our DB for which we running the force update.
    148    */
    149   uint64_t di_serial;
    150 
    151   /**
    152    * Base URL of the Donau instance for which we are running the force update.
    153    */
    154   char *donau_url;
    155 
    156   /**
    157    * ID of the charity for which we are running the force update.
    158    */
    159   uint64_t charity_id;
    160 
    161   /**
    162    * Handle to the charity update request.
    163    */
    164   struct DONAU_CharityGetHandle *h;
    165 };
    166 
    167 /**
    168  * Head of the list of charity force updates.
    169  */
    170 static struct ForceCharityCtx *fcc_head;
    171 
    172 /**
    173  * Tail of the list of charity force updates.
    174  */
    175 static struct ForceCharityCtx *fcc_tail;
    176 
    177 /**
    178  * The merchant's configuration.
    179  */
    180 static const struct GNUNET_CONFIGURATION_Handle *cfg;
    181 
    182 /**
    183  * Our database plugin.
    184  */
    185 static struct TALER_MERCHANTDB_Plugin *db_plugin;
    186 
    187 /**
    188  * Our event handler listening for /keys forced downloads.
    189  */
    190 static struct GNUNET_DB_EventHandler *eh;
    191 
    192 /**
    193  * Our event handler listening for /charity_id forced downloads.
    194  */
    195 static struct GNUNET_DB_EventHandler *eh_charity;
    196 
    197 /**
    198  * Handle to the context for interacting with the Donau services.
    199  */
    200 static struct GNUNET_CURL_Context *ctx;
    201 
    202 /**
    203  * Scheduler context for running the @e ctx.
    204  */
    205 static struct GNUNET_CURL_RescheduleContext *rc;
    206 
    207 /**
    208  * How many active inquiries do we have right now.
    209  */
    210 static unsigned int active_inquiries;
    211 
    212 /**
    213  * Value to return from main(). 0 on success, non-zero on errors.
    214  */
    215 static int global_ret;
    216 
    217 /**
    218  * #GNUNET_YES if we are in test mode and should exit when idle.
    219  */
    220 static int test_mode;
    221 
    222 /**
    223  * True if the last DB query was limited by the
    224  * #OPEN_INQUIRY_LIMIT and we thus should check again
    225  * as soon as we are substantially below that limit,
    226  * and not only when we get a DB notification.
    227  */
    228 static bool at_limit;
    229 
    230 
    231 /**
    232  * Function that initiates a /keys download for a Donau instance.
    233  *
    234  * @param cls closure with a `struct Donau *`
    235  */
    236 static void
    237 download_keys (void *cls);
    238 
    239 
    240 /**
    241  * An inquiry finished, check if we need to start more.
    242  */
    243 static void
    244 end_inquiry (void)
    245 {
    246   GNUNET_assert (active_inquiries > 0);
    247   active_inquiries--;
    248   if ( (active_inquiries < OPEN_INQUIRY_LIMIT / 2) &&
    249        (at_limit) )
    250   {
    251     at_limit = false;
    252     for (struct Donau *d = d_head;
    253          NULL != d;
    254          d = d->next)
    255     {
    256       if (! d->limited)
    257         continue;
    258       d->limited = false;
    259       /* done synchronously so that the active_inquiries
    260          is updated immediately */
    261       download_keys (d);
    262       if (at_limit)
    263         break;
    264     }
    265   }
    266   if ( (! at_limit) &&
    267        (0 == active_inquiries) &&
    268        (test_mode) )
    269   {
    270     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    271                 "No more open inquiries and in test mode. Exiting.\n");
    272     GNUNET_SCHEDULER_shutdown ();
    273     return;
    274   }
    275 }
    276 
    277 
    278 /**
    279  * Update Donau keys in the database.
    280  *
    281  * @param keys Donau keys to persist
    282  * @param first_retry earliest we may retry fetching the keys
    283  * @return transaction status
    284  */
    285 static enum GNUNET_DB_QueryStatus
    286 insert_donau_keys_data (const struct DONAU_Keys *keys,
    287                         struct GNUNET_TIME_Absolute first_retry)
    288 {
    289   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    290               "Inserting Donau keys into the database %s\n",
    291               keys->donau_url);
    292   return db_plugin->upsert_donau_keys (db_plugin->cls,
    293                                        keys,
    294                                        first_retry);
    295 }
    296 
    297 
    298 /**
    299  * Store Donau keys in the database and handle retries.
    300  *
    301  * @param keys the keys to store
    302  * @param first_retry earliest time we may retry fetching the keys
    303  * @return true on success
    304  */
    305 static bool
    306 store_donau_keys (struct DONAU_Keys *keys,
    307                   struct GNUNET_TIME_Absolute first_retry)
    308 {
    309   enum GNUNET_DB_QueryStatus qs;
    310   db_plugin->preflight (db_plugin->cls);
    311   for (unsigned int r = 0; r < MAX_RETRIES; r++)
    312   {
    313     if (GNUNET_OK !=
    314         db_plugin->start (db_plugin->cls,
    315                           "update donau key data"))
    316     {
    317       db_plugin->rollback (db_plugin->cls);
    318       GNUNET_break (0);
    319       return false;
    320     }
    321 
    322     qs = insert_donau_keys_data (keys,
    323                                  first_retry);
    324     if (0 > qs)
    325     {
    326       db_plugin->rollback (db_plugin->cls);
    327       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    328                   "Error while inserting Donau keys into the database: status %d",
    329                   qs);
    330       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    331         continue;
    332       GNUNET_break (0);
    333       return false;
    334     }
    335 
    336     qs = db_plugin->commit (db_plugin->cls);
    337     if (0 > qs)
    338     {
    339       db_plugin->rollback (db_plugin->cls);
    340       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    341                   "Failed to commit Donau keys to the database: status %d",
    342                   qs);
    343       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    344         continue;
    345       GNUNET_break (0);
    346       return false;
    347     }
    348     break;
    349   }
    350   if (qs < 0)
    351   {
    352     GNUNET_break (0);
    353     return false;
    354   }
    355   return true;
    356 }
    357 
    358 
    359 /**
    360  * Store Donau charity in the database and handle retries.
    361  *
    362  * @param charity_id the charity ID to store
    363  * @param donau_url the base URL of the Donau instance
    364  * @param charity the charity structure to store
    365  */
    366 static bool
    367 store_donau_charity (uint64_t charity_id,
    368                      const char *donau_url,
    369                      const struct DONAU_Charity *charity)
    370 {
    371   enum GNUNET_DB_QueryStatus qs;
    372 
    373   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    374               "Inserting/updating charity %llu for Donau `%s'\n",
    375               (unsigned long long) charity_id,
    376               donau_url);
    377 
    378   db_plugin->preflight (db_plugin->cls);
    379 
    380   for (unsigned int r = 0; r < MAX_RETRIES; r++)
    381   {
    382     if (GNUNET_OK !=
    383         db_plugin->start (db_plugin->cls,
    384                           "update donau charity data"))
    385     {
    386       db_plugin->rollback (db_plugin->cls);
    387       GNUNET_break (0);
    388       return false;
    389     }
    390 
    391     qs = db_plugin->update_donau_instance (db_plugin->cls,
    392                                            donau_url,
    393                                            charity,
    394                                            charity_id);
    395     if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    396     {
    397       db_plugin->rollback (db_plugin->cls);
    398       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    399                   "Error while updating charity into the database: status %d",
    400                   qs);
    401       continue;
    402     }
    403     if (0 >= qs)
    404     {
    405       db_plugin->rollback (db_plugin->cls);
    406       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    407                   "Error while updating charity into the database: status %d",
    408                   qs);
    409       return false;
    410     }
    411 
    412     qs = db_plugin->commit (db_plugin->cls);
    413     if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    414     {
    415       db_plugin->rollback (db_plugin->cls);
    416       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    417                   "Failed to commit charity data to the database: status %d",
    418                   qs);
    419       continue;
    420     }
    421     if (0 > qs)
    422     {
    423       db_plugin->rollback (db_plugin->cls);
    424       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    425                   "Failed to commit charity data to the database: status %d",
    426                   qs);
    427       return false;
    428     }
    429     break;
    430   }
    431   if (0 >= qs)
    432   {
    433     GNUNET_break (0);
    434     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    435                 "Retries exhausted while inserting charity %llu for Donau `%s': last status %d",
    436                 (unsigned long long) charity_id,
    437                 donau_url,
    438                 qs);
    439     return false;
    440   }
    441   return true;
    442 }
    443 
    444 
    445 /**
    446  * Callback after Donau keys are fetched.
    447  *
    448  * @param cls closure with a `struct Donau *`
    449  * @param kr response data
    450  * @param keys the keys of the Donau instance
    451  */
    452 static void
    453 donau_cert_cb (
    454   void *cls,
    455   const struct DONAU_KeysResponse *kr,
    456   struct DONAU_Keys *keys)
    457 {
    458   struct Donau *d = cls;
    459   struct GNUNET_TIME_Absolute n;
    460   struct GNUNET_TIME_Absolute first_retry;
    461 
    462   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    463               "Starting donau cert with object \n");
    464 
    465   d->conn = NULL;
    466   switch (kr->hr.http_status)
    467   {
    468   case MHD_HTTP_OK:
    469     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    470                 "Got new keys for %s, updating database\n",
    471                 d->donau_url);
    472     first_retry = GNUNET_TIME_relative_to_absolute (DONAU_MAXFREQ);
    473     if (! store_donau_keys (keys,
    474                             first_retry))
    475     {
    476       GNUNET_break (0);
    477       DONAU_keys_decref (keys);
    478       break;
    479     }
    480 
    481     d->keys = keys;
    482     /* Reset back-off */
    483     d->retry_delay = DONAU_MAXFREQ;
    484     /* limit retry */
    485     d->first_retry = first_retry;
    486 
    487     /* FIXME: Might be good to reference some key_data_expiration and not first sign_key*/
    488     n = GNUNET_TIME_absolute_max (d->first_retry,
    489                                   keys->sign_keys[0].expire_sign.abs_time);
    490     if (NULL != d->retry_task)
    491       GNUNET_SCHEDULER_cancel (d->retry_task);
    492     d->retry_task = GNUNET_SCHEDULER_add_at (n,
    493                                              &download_keys,
    494                                              d);
    495     end_inquiry ();
    496     return;
    497   default:
    498     GNUNET_break (NULL == keys);
    499     break;
    500   }
    501 
    502   d->retry_delay
    503     = GNUNET_TIME_STD_BACKOFF (d->retry_delay);
    504   n = GNUNET_TIME_absolute_max (
    505     d->first_retry,
    506     GNUNET_TIME_relative_to_absolute (d->retry_delay));
    507 
    508   if (NULL != d->retry_task)
    509     GNUNET_SCHEDULER_cancel (d->retry_task);
    510   d->retry_task
    511     = GNUNET_SCHEDULER_add_at (n,
    512                                &download_keys,
    513                                d);
    514   end_inquiry ();
    515 }
    516 
    517 
    518 /**
    519  * Initiate the download of Donau keys.
    520  *
    521  * @param cls closure with a `struct Donau *`
    522  */
    523 static void
    524 download_keys (void *cls)
    525 {
    526   struct Donau *d = cls;
    527 
    528   d->retry_task = NULL;
    529   GNUNET_break (OPEN_INQUIRY_LIMIT >= active_inquiries);
    530   if (OPEN_INQUIRY_LIMIT <= active_inquiries)
    531   {
    532     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    533                 "Cannot start more donaukeys inquiries, already at limit\n");
    534     d->limited = true;
    535     at_limit = true;
    536     return;
    537   }
    538   d->retry_delay
    539     = GNUNET_TIME_STD_BACKOFF (d->retry_delay);
    540   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    541               "Downloading keys from %s (%s)\n",
    542               d->donau_url,
    543               d->force_retry ? "forced" : "regular");
    544   d->conn = DONAU_get_keys (ctx,
    545                             d->donau_url,
    546                             &donau_cert_cb,
    547                             d);
    548   d->force_retry = false;
    549   if (NULL != d->conn)
    550   {
    551     active_inquiries++;
    552   }
    553   else
    554   {
    555     struct GNUNET_TIME_Relative n;
    556 
    557     n = GNUNET_TIME_relative_max (d->retry_delay,
    558                                   DONAU_MAXFREQ);
    559 
    560     d->retry_task
    561       = GNUNET_SCHEDULER_add_delayed (n,
    562                                       &download_keys,
    563                                       d);
    564   }
    565 }
    566 
    567 
    568 /**
    569  * Callback for DONAU_charity_get() that stores the charity
    570  * information in the DB and finishes the inquiry.
    571  *
    572  * @param cls closure with `struct ForceCharityCtx *`
    573  * @param gcr response from DONAU
    574  */
    575 static void
    576 donau_charity_cb (void *cls,
    577                   const struct DONAU_GetCharityResponse *gcr)
    578 {
    579   struct ForceCharityCtx *fcc = cls;
    580   fcc->h = NULL;
    581 
    582   switch (gcr->hr.http_status)
    583   {
    584   case MHD_HTTP_OK:
    585     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    586                 "Got charity_id `%llu' details for donau `%s', updating DB\n",
    587                 (unsigned long long) fcc->charity_id,
    588                 fcc->donau_url);
    589 
    590     if (! store_donau_charity (fcc->charity_id,
    591                                fcc->donau_url,
    592                                &gcr->details.ok.charity))
    593     {
    594       GNUNET_break (0);
    595     }
    596     break;
    597 
    598   default:
    599     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    600                 "DONAU charity_get for `%s' failed with HTTP %u / ec %u\n",
    601                 fcc->donau_url,
    602                 gcr->hr.http_status,
    603                 gcr->hr.ec);
    604     break;
    605   }
    606 
    607   end_inquiry ();
    608 }
    609 
    610 
    611 /**
    612  * Download the charity_id for a Donau instance.
    613  *
    614  * @param cls closure with a `struct Donau *`
    615  */
    616 static void
    617 download_charity_id (void *cls)
    618 {
    619   struct ForceCharityCtx *fcc = cls;
    620 
    621   /* nothing to do if a request is already outstanding */
    622   if (NULL != fcc->h)
    623     return;
    624 
    625   /* respect global inquiry limit */
    626   GNUNET_break (OPEN_INQUIRY_LIMIT >= active_inquiries);
    627   if (OPEN_INQUIRY_LIMIT <= active_inquiries)
    628   {
    629     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    630                 "Cannot start more charity inquiries, already at limit\n");
    631     return;
    632   }
    633 
    634   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    635               "Downloading charity `%llu' from `%s'\n",
    636               (unsigned long long) fcc->charity_id,
    637               fcc->donau_url);
    638 
    639   fcc->h = DONAU_charity_get (ctx,
    640                               fcc->donau_url,
    641                               fcc->charity_id,
    642                               NULL,          /* bearer token -- not needed */
    643                               &donau_charity_cb,
    644                               fcc);
    645 
    646   if (NULL != fcc->h)
    647   {
    648     active_inquiries++;
    649   }
    650   else
    651   {
    652     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    653                 "Failed to initiate DONAU_charity_get() for `%s'\n",
    654                 fcc->donau_url);
    655     /* we do NOT retry here – simply finish the (failed) inquiry */
    656     end_inquiry ();
    657   }
    658 }
    659 
    660 
    661 /**
    662  * Lookup donau by @a donau_url. Create one
    663  * if it does not exist.
    664  *
    665  * @param donau_url base URL to match against
    666  * @return NULL if not found
    667  */
    668 static struct Donau *
    669 lookup_donau (const char *donau_url)
    670 {
    671   for (struct Donau *d = d_head;
    672        NULL != d;
    673        d = d->next)
    674     if (0 == strcmp (d->donau_url,
    675                      donau_url))
    676       return d;
    677   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    678               "Got notification about unknown Donau `%s'\n",
    679               donau_url);
    680   return NULL;
    681 }
    682 
    683 
    684 /**
    685  * Lookup a ForceCharityCtx by donau-instance serial.
    686  *
    687  * @param di_serial serial to search for
    688  * @return matching context or NULL
    689  */
    690 static struct ForceCharityCtx *
    691 lookup_donau_charity (uint64_t di_serial)
    692 {
    693   for (struct ForceCharityCtx *fcc = fcc_head;
    694        NULL != fcc;
    695        fcc = fcc->next)
    696     if (fcc->di_serial == di_serial)
    697       return fcc;
    698   return NULL;
    699 }
    700 
    701 
    702 /**
    703  * Force immediate (re)loading of /charity_id for an donau.
    704  *
    705  * @param cls NULL
    706  * @param extra base URL of the donau that changed
    707  * @param extra_len number of bytes in @a extra
    708  */
    709 static void
    710 force_donau_charity_id (void *cls,
    711                         const void *extra,
    712                         size_t extra_len)
    713 {
    714   uint64_t di_serial;
    715   char *donau_url = NULL;
    716   uint64_t charity_id = -1;
    717   enum GNUNET_DB_QueryStatus qs;
    718   struct ForceCharityCtx *fcc;
    719 
    720   if ( (sizeof(uint64_t) != extra_len) ||
    721        (NULL == extra) )
    722   {
    723     GNUNET_break (0);
    724     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    725                 "Incorrect extra for the force_donau_charity_id");
    726     return;
    727   }
    728   GNUNET_memcpy (&di_serial,
    729                  extra,
    730                  sizeof(uint64_t));
    731   di_serial = GNUNET_ntohll (di_serial);
    732   qs = db_plugin->select_donau_instance_by_serial (db_plugin->cls,
    733                                                    di_serial,
    734                                                    &donau_url,
    735                                                    &charity_id);
    736 
    737   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
    738   {
    739     GNUNET_break (0);
    740     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    741                 "force_donau_charity_id: instance serial %llu not found (status %d)\n",
    742                 (unsigned long long) di_serial,
    743                 qs);
    744     return;
    745   }
    746 
    747   fcc = lookup_donau_charity (di_serial);
    748   if (NULL == fcc)
    749   {
    750     fcc = GNUNET_new (struct ForceCharityCtx);
    751     fcc->di_serial   = di_serial;
    752     fcc->donau_url   = donau_url;      /* take ownership */
    753     fcc->charity_id  = charity_id;
    754     GNUNET_CONTAINER_DLL_insert (fcc_head, fcc_tail, fcc);
    755 
    756     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    757                 "Created new ForceCharityCtx for donau `%s' "
    758                 "(serial %llu, charity %llu)\n",
    759                 donau_url,
    760                 (unsigned long long) di_serial,
    761                 (unsigned long long) charity_id);
    762   }
    763   else
    764   {
    765     GNUNET_free (donau_url);
    766     if (NULL != fcc->h)
    767     {
    768       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    769                   "Already downloading charity_id for donau `%s'\n",
    770                   fcc->donau_url);
    771       return;
    772     }
    773   }
    774   download_charity_id (fcc);
    775 }
    776 
    777 
    778 /**
    779  * Force immediate (re)loading of /keys for an donau.
    780  *
    781  * @param cls NULL
    782  * @param extra base URL of the donau that changed
    783  * @param extra_len number of bytes in @a extra
    784  */
    785 static void
    786 force_donau_keys (void *cls,
    787                   const void *extra,
    788                   size_t extra_len)
    789 {
    790   const char *url = extra;
    791   struct Donau *d;
    792 
    793   if ( (NULL == extra) ||
    794        (0 == extra_len) )
    795   {
    796     GNUNET_break (0);
    797     return;
    798   }
    799   if ('\0' != url[extra_len - 1])
    800   {
    801     GNUNET_break (0);
    802     return;
    803   }
    804   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    805               "Received keys update notification: reload `%s'\n",
    806               url);
    807 
    808   d = lookup_donau (url);
    809   if (NULL == d)
    810   {
    811     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    812                 "Donau instance `%s' not found. Creating new instance.\n",
    813                 url);
    814 
    815     d = GNUNET_new (struct Donau);
    816     d->donau_url = GNUNET_strdup (url);
    817     d->retry_delay = DONAU_MAXFREQ;
    818     d->first_retry = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_ZERO);
    819 
    820     GNUNET_CONTAINER_DLL_insert (d_head,
    821                                  d_tail,
    822                                  d);
    823     download_keys (d);
    824   }
    825 
    826   if (NULL != d->conn)
    827   {
    828     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    829                 "Already downloading %skeys\n",
    830                 url);
    831     return;
    832   }
    833   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    834               "Will download %skeys in %s\n",
    835               url,
    836               GNUNET_TIME_relative2s (
    837                 GNUNET_TIME_absolute_get_remaining (
    838                   d->first_retry),
    839                 true));
    840   if (NULL != d->retry_task)
    841     GNUNET_SCHEDULER_cancel (d->retry_task);
    842   d->force_retry = true;
    843   d->retry_task
    844     = GNUNET_SCHEDULER_add_at (d->first_retry,
    845                                &download_keys,
    846                                d);
    847 }
    848 
    849 
    850 /**
    851  * We're being aborted with CTRL-C (or SIGTERM). Shut down.
    852  *
    853  * @param cls closure (NULL)
    854  */
    855 static void
    856 shutdown_task (void *cls)
    857 {
    858   (void) cls;
    859   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    860               "Running shutdown\n");
    861   while (NULL != d_head)
    862   {
    863     struct Donau *d = d_head;
    864 
    865     GNUNET_free (d->donau_url);
    866     GNUNET_free (d->currency);
    867     if (NULL != d->conn)
    868     {
    869       DONAU_get_keys_cancel (d->conn);
    870       d->conn = NULL;
    871     }
    872     if (NULL != d->keys)
    873     {
    874       DONAU_keys_decref (d->keys);
    875       d->keys = NULL;
    876     }
    877     if (NULL != d->retry_task)
    878     {
    879       GNUNET_SCHEDULER_cancel (d->retry_task);
    880       d->retry_task = NULL;
    881     }
    882     GNUNET_CONTAINER_DLL_remove (d_head,
    883                                  d_tail,
    884                                  d);
    885     GNUNET_free (d);
    886   }
    887   if (NULL != eh)
    888   {
    889     db_plugin->event_listen_cancel (eh);
    890     eh = NULL;
    891   }
    892   if (NULL != eh_charity)
    893   {
    894     db_plugin->event_listen_cancel (eh_charity);
    895     eh_charity = NULL;
    896   }
    897   TALER_MERCHANTDB_plugin_unload (db_plugin);
    898   db_plugin = NULL;
    899   cfg = NULL;
    900   if (NULL != ctx)
    901   {
    902     GNUNET_CURL_fini (ctx);
    903     ctx = NULL;
    904   }
    905   if (NULL != rc)
    906   {
    907     GNUNET_CURL_gnunet_rc_destroy (rc);
    908     rc = NULL;
    909   }
    910 }
    911 
    912 
    913 /**
    914  * Callback function typically used by `select_donau_instances` to handle
    915  * the details of each Donau instance retrieved from the database.
    916  *
    917  * @param cls Closure to pass additional context or data to the callback function.
    918  * @param donau_instance_serial Serial number of the Donau instance in the merchant database.
    919  * @param donau_url The URL of the Donau instance.
    920  * @param charity_name The name of the charity associated with the Donau instance.
    921  * @param charity_pub_key Pointer to the charity's public key used for cryptographic operations.
    922  * @param charity_id The unique identifier for the charity within the Donau instance.
    923  * @param charity_max_per_year Maximum allowed donations to the charity for the current year.
    924  * @param charity_receipts_to_date Total donations received by the charity so far in the current year.
    925  * @param current_year The year for which the donation data is being tracked.
    926  * @param donau_keys_json JSON object containing additional key-related information for the Donau instance.
    927  */
    928 static void
    929 accept_donau (
    930   void *cls,
    931   uint64_t donau_instance_serial,
    932   const char *donau_url,
    933   const char *charity_name,
    934   const struct DONAU_CharityPublicKeyP *charity_pub_key,
    935   uint64_t charity_id,
    936   const struct TALER_Amount *charity_max_per_year,
    937   const struct TALER_Amount *charity_receipts_to_date,
    938   int64_t current_year,
    939   const json_t *donau_keys_json
    940   )
    941 {
    942   struct Donau *d;
    943 
    944   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    945               "Donau instance `%s' not found. Creating new instance.\n",
    946               donau_url);
    947   d = GNUNET_new (struct Donau);
    948   d->donau_url = GNUNET_strdup (donau_url);
    949   d->retry_delay = DONAU_MAXFREQ;
    950   d->first_retry = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_ZERO);
    951   GNUNET_CONTAINER_DLL_insert (d_head,
    952                                d_tail,
    953                                d);
    954   if (NULL == donau_keys_json)
    955   {
    956     download_keys (d);
    957     return;
    958   }
    959   d->keys = DONAU_keys_from_json (donau_keys_json);
    960   if (NULL == d->keys)
    961   {
    962     GNUNET_break (0);
    963     download_keys (d);
    964     return;
    965   }
    966   d->retry_delay = DONAU_MAXFREQ;
    967   d->first_retry = GNUNET_TIME_relative_to_absolute (DONAU_MAXFREQ);
    968 
    969   {
    970     struct GNUNET_TIME_Absolute n;
    971 
    972     n = GNUNET_TIME_absolute_min (
    973       d->first_retry,
    974       d->keys->sign_keys[0].expire_sign.abs_time);
    975     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    976                 "Will download %skeys in %s\n",
    977                 donau_url,
    978                 GNUNET_TIME_relative2s (
    979                   GNUNET_TIME_absolute_get_remaining (n),
    980                   true));
    981     d->retry_task = GNUNET_SCHEDULER_add_at (n,
    982                                              &download_keys,
    983                                              d);
    984   }
    985 }
    986 
    987 
    988 /**
    989  * First task.
    990  *
    991  * @param cls closure, NULL
    992  * @param args remaining command-line arguments
    993  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
    994  * @param c configuration
    995  */
    996 static void
    997 run (void *cls,
    998      char *const *args,
    999      const char *cfgfile,
   1000      const struct GNUNET_CONFIGURATION_Handle *c)
   1001 {
   1002   (void) args;
   1003   (void) cfgfile;
   1004 
   1005   cfg = c;
   1006   GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
   1007                                  NULL);
   1008   ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
   1009                           &rc);
   1010   rc = GNUNET_CURL_gnunet_rc_create (ctx);
   1011   if (NULL == ctx)
   1012   {
   1013     GNUNET_break (0);
   1014     GNUNET_SCHEDULER_shutdown ();
   1015     global_ret = EXIT_FAILURE;
   1016     return;
   1017   }
   1018   if (NULL == ctx)
   1019   {
   1020     GNUNET_break (0);
   1021     GNUNET_SCHEDULER_shutdown ();
   1022     global_ret = EXIT_FAILURE;
   1023     return;
   1024   }
   1025   if (NULL ==
   1026       (db_plugin = TALER_MERCHANTDB_plugin_load (cfg)) )
   1027   {
   1028     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1029                 "Failed to initialize DB subsystem\n");
   1030     GNUNET_SCHEDULER_shutdown ();
   1031     global_ret = EXIT_NOTCONFIGURED;
   1032     return;
   1033   }
   1034   if (GNUNET_OK !=
   1035       db_plugin->connect (db_plugin->cls))
   1036   {
   1037     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1038                 "Failed to connect to database\n");
   1039     GNUNET_SCHEDULER_shutdown ();
   1040     global_ret = EXIT_FAILURE;
   1041     return;
   1042   }
   1043   {
   1044     struct GNUNET_DB_EventHeaderP es = {
   1045       .size = ntohs (sizeof(es)),
   1046       .type = ntohs (TALER_DBEVENT_MERCHANT_DONAU_KEYS)
   1047     };
   1048 
   1049     eh = db_plugin->event_listen (db_plugin->cls,
   1050                                   &es,
   1051                                   GNUNET_TIME_UNIT_FOREVER_REL,
   1052                                   &force_donau_keys,
   1053                                   NULL);
   1054   }
   1055   {
   1056     struct GNUNET_DB_EventHeaderP es = {
   1057       .size = ntohs (sizeof(es)),
   1058       .type = ntohs (TALER_DBEVENT_MERCHANT_DONAU_CHARITY_ID)
   1059     };
   1060 
   1061     eh_charity = db_plugin->event_listen (
   1062       db_plugin->cls,
   1063       &es,
   1064       GNUNET_TIME_UNIT_FOREVER_REL,
   1065       &force_donau_charity_id,
   1066       NULL);
   1067   }
   1068 
   1069   {
   1070     enum GNUNET_DB_QueryStatus qs;
   1071 
   1072     qs = db_plugin->select_all_donau_instances (db_plugin->cls,
   1073                                                 &accept_donau,
   1074                                                 NULL);
   1075     if (qs < 0)
   1076     {
   1077       GNUNET_break (0);
   1078       GNUNET_SCHEDULER_shutdown ();
   1079       return;
   1080     }
   1081   }
   1082   if ( (0 == active_inquiries) &&
   1083        (test_mode) )
   1084   {
   1085     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1086                 "No donau keys inquiries to start, exiting.\n");
   1087     GNUNET_SCHEDULER_shutdown ();
   1088     return;
   1089   }
   1090 }
   1091 
   1092 
   1093 /**
   1094  * The main function of taler-merchant-donaukeyupdate
   1095  *
   1096  * @param argc number of arguments from the command line
   1097  * @param argv command line arguments
   1098  * @return 0 ok, 1 on error
   1099  */
   1100 int
   1101 main (int argc,
   1102       char *const *argv)
   1103 {
   1104   struct GNUNET_GETOPT_CommandLineOption options[] = {
   1105     GNUNET_GETOPT_option_timetravel ('T',
   1106                                      "timetravel"),
   1107     GNUNET_GETOPT_option_flag ('t',
   1108                                "test",
   1109                                "run in test mode and exit when idle",
   1110                                &test_mode),
   1111     GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION),
   1112     GNUNET_GETOPT_OPTION_END
   1113   };
   1114   enum GNUNET_GenericReturnValue ret;
   1115 
   1116   ret = GNUNET_PROGRAM_run (
   1117     TALER_MERCHANT_project_data (),
   1118     argc, argv,
   1119     "taler-merchant-donaukeyupdate",
   1120     gettext_noop (
   1121       "background process that ensures our key and configuration data on Donau is up-to-date"),
   1122     options,
   1123     &run, NULL);
   1124   if (GNUNET_SYSERR == ret)
   1125     return EXIT_INVALIDARGUMENT;
   1126   if (GNUNET_NO == ret)
   1127     return EXIT_SUCCESS;
   1128   return global_ret;
   1129 }
   1130 
   1131 
   1132 /* end of taler-merchant-donaukeyupdate.c */