merchant

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

taler-merchant-donaukeyupdate.c (32736B)


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