merchant

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

taler-merchant-donaukeyupdate.c (29749B)


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