LCOV - code coverage report
Current view: top level - backend - taler-merchant-donaukeyupdate.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 35.8 % 369 132
Test Date: 2026-09-04 23:42:01 Functions: 56.2 % 16 9

            Line data    Source code
       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            2 : end_inquiry (void)
     291              : {
     292            2 :   GNUNET_assert (active_inquiries > 0);
     293            2 :   active_inquiries--;
     294            2 :   if ( (active_inquiries < OPEN_INQUIRY_LIMIT / 2) &&
     295              :        (at_limit) )
     296              :   {
     297            0 :     at_limit = false;
     298            0 :     for (struct Donau *d = d_head;
     299            0 :          NULL != d;
     300            0 :          d = d->next)
     301              :     {
     302            0 :       if (! d->limited)
     303            0 :         continue;
     304            0 :       d->limited = false;
     305              :       /* done synchronously so that the active_inquiries
     306              :          is updated immediately */
     307            0 :       download_keys (d);
     308            0 :       if (at_limit)
     309            0 :         break;
     310              :     }
     311            0 :     for (struct ForceCharityCtx *fcc = fcc_head;
     312            0 :          NULL != fcc;
     313            0 :          fcc = fcc->next)
     314              :     {
     315            0 :       if (at_limit)
     316            0 :         break;
     317            0 :       if (! fcc->limited)
     318            0 :         continue;
     319            0 :       fcc->limited = false;
     320              :       /* done synchronously so that the active_inquiries
     321              :          is updated immediately */
     322            0 :       download_charity_id (fcc);
     323              :     }
     324              :   }
     325            2 :   if ( (! at_limit) &&
     326            2 :        (0 == active_inquiries) &&
     327              :        (test_mode) )
     328              :   {
     329            2 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     330              :                 "No more open inquiries and in test mode. Exiting.\n");
     331            2 :     GNUNET_SCHEDULER_shutdown ();
     332            2 :     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            2 : insert_donau_keys_data (const struct DONAU_Keys *keys,
     346              :                         struct GNUNET_TIME_Absolute first_retry)
     347              : {
     348            2 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     349              :               "Inserting Donau keys into the database %s\n",
     350              :               keys->donau_url);
     351            2 :   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            2 : store_donau_keys (struct DONAU_Keys *keys,
     366              :                   struct GNUNET_TIME_Absolute first_retry)
     367              : {
     368              :   enum GNUNET_DB_QueryStatus qs;
     369            2 :   TALER_MERCHANTDB_preflight (pg);
     370            2 :   for (unsigned int r = 0; r < MAX_RETRIES; r++)
     371              :   {
     372            2 :     if (GNUNET_OK !=
     373            2 :         TALER_MERCHANTDB_start (pg,
     374              :                                 "update donau key data"))
     375              :     {
     376            0 :       TALER_MERCHANTDB_rollback (pg);
     377            0 :       GNUNET_break (0);
     378            0 :       return false;
     379              :     }
     380              : 
     381            2 :     qs = insert_donau_keys_data (keys,
     382              :                                  first_retry);
     383            2 :     if (0 > qs)
     384              :     {
     385            0 :       TALER_MERCHANTDB_rollback (pg);
     386            0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     387              :                   "Error while inserting Donau keys into the database: status %d",
     388              :                   qs);
     389            0 :       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     390            0 :         continue;
     391            0 :       GNUNET_break (0);
     392            0 :       return false;
     393              :     }
     394              : 
     395            2 :     qs = TALER_MERCHANTDB_commit (pg);
     396            2 :     if (0 > qs)
     397              :     {
     398            0 :       TALER_MERCHANTDB_rollback (pg);
     399            0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     400              :                   "Failed to commit Donau keys to the database: status %d",
     401              :                   qs);
     402            0 :       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     403            0 :         continue;
     404            0 :       GNUNET_break (0);
     405            0 :       return false;
     406              :     }
     407            2 :     break;
     408              :   }
     409            2 :   if (qs < 0)
     410              :   {
     411            0 :     GNUNET_break (0);
     412            0 :     return false;
     413              :   }
     414            2 :   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            0 : 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            0 :   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            0 :   TALER_MERCHANTDB_preflight (pg);
     438              : 
     439            0 :   for (unsigned int r = 0; r < MAX_RETRIES; r++)
     440              :   {
     441            0 :     if (GNUNET_OK !=
     442            0 :         TALER_MERCHANTDB_start (pg,
     443              :                                 "update donau charity data"))
     444              :     {
     445            0 :       TALER_MERCHANTDB_rollback (pg);
     446            0 :       GNUNET_break (0);
     447            0 :       return false;
     448              :     }
     449              : 
     450            0 :     qs = TALER_MERCHANTDB_update_donau_instance (pg,
     451              :                                                  donau_url,
     452              :                                                  charity,
     453              :                                                  charity_id);
     454            0 :     if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     455              :     {
     456            0 :       TALER_MERCHANTDB_rollback (pg);
     457            0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     458              :                   "Error while updating charity into the database: status %d",
     459              :                   qs);
     460            0 :       continue;
     461              :     }
     462            0 :     if (0 >= qs)
     463              :     {
     464            0 :       TALER_MERCHANTDB_rollback (pg);
     465            0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     466              :                   "Error while updating charity into the database: status %d",
     467              :                   qs);
     468            0 :       return false;
     469              :     }
     470              : 
     471            0 :     qs = TALER_MERCHANTDB_commit (pg);
     472            0 :     if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     473              :     {
     474            0 :       TALER_MERCHANTDB_rollback (pg);
     475            0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     476              :                   "Failed to commit charity data to the database: status %d",
     477              :                   qs);
     478            0 :       continue;
     479              :     }
     480            0 :     if (0 > qs)
     481              :     {
     482            0 :       TALER_MERCHANTDB_rollback (pg);
     483            0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     484              :                   "Failed to commit charity data to the database: status %d",
     485              :                   qs);
     486            0 :       return false;
     487              :     }
     488            0 :     break;
     489              :   }
     490            0 :   if (0 >= qs)
     491              :   {
     492            0 :     GNUNET_break (0);
     493            0 :     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            0 :     return false;
     499              :   }
     500            0 :   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            2 : donau_cert_cb (
     513              :   void *cls,
     514              :   const struct DONAU_KeysResponse *kr,
     515              :   struct DONAU_Keys *keys)
     516              : {
     517            2 :   struct Donau *d = cls;
     518              :   struct GNUNET_TIME_Absolute n;
     519              :   struct GNUNET_TIME_Absolute first_retry;
     520              : 
     521            2 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     522              :               "Starting donau cert with object \n");
     523              : 
     524            2 :   d->conn = NULL;
     525            2 :   switch (kr->hr.http_status)
     526              :   {
     527            2 :   case MHD_HTTP_OK:
     528            2 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     529              :                 "Got new keys for %s, updating database\n",
     530              :                 d->donau_url);
     531            2 :     first_retry = GNUNET_TIME_relative_to_absolute (DONAU_MAXFREQ);
     532            2 :     if (! store_donau_keys (keys,
     533              :                             first_retry))
     534              :     {
     535            0 :       GNUNET_break (0);
     536            0 :       DONAU_keys_decref (keys);
     537            0 :       break;
     538              :     }
     539              : 
     540            2 :     DONAU_keys_decref (d->keys);
     541            2 :     d->keys = keys;
     542              :     /* Reset back-off */
     543            2 :     d->retry_delay = DONAU_MAXFREQ;
     544              :     /* limit retry */
     545            2 :     d->first_retry = first_retry;
     546              : 
     547            2 :     if (0 < keys->num_sign_keys)
     548            2 :       n = GNUNET_TIME_absolute_max (d->first_retry,
     549            2 :                                     keys->sign_keys[0].expire_sign.abs_time);
     550              :     else
     551            0 :       n = d->first_retry;
     552            2 :     if (NULL != d->retry_task)
     553            0 :       GNUNET_SCHEDULER_cancel (d->retry_task);
     554            2 :     d->retry_task = GNUNET_SCHEDULER_add_at (n,
     555              :                                              &download_keys,
     556              :                                              d);
     557            2 :     end_inquiry ();
     558            2 :     return;
     559            0 :   default:
     560            0 :     GNUNET_break (NULL == keys);
     561            0 :     break;
     562              :   }
     563              : 
     564              :   d->retry_delay
     565            0 :     = GNUNET_TIME_STD_BACKOFF (d->retry_delay);
     566            0 :   n = GNUNET_TIME_absolute_max (
     567              :     d->first_retry,
     568              :     GNUNET_TIME_relative_to_absolute (d->retry_delay));
     569              : 
     570            0 :   if (NULL != d->retry_task)
     571            0 :     GNUNET_SCHEDULER_cancel (d->retry_task);
     572              :   d->retry_task
     573            0 :     = GNUNET_SCHEDULER_add_at (n,
     574              :                                &download_keys,
     575              :                                d);
     576            0 :   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            2 : download_keys (void *cls)
     587              : {
     588            2 :   struct Donau *d = cls;
     589              : 
     590            2 :   d->retry_task = NULL;
     591            2 :   GNUNET_break (OPEN_INQUIRY_LIMIT >= active_inquiries);
     592            2 :   if (OPEN_INQUIRY_LIMIT <= active_inquiries)
     593              :   {
     594            0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     595              :                 "Cannot start more donaukeys inquiries, already at limit\n");
     596            0 :     d->limited = true;
     597            0 :     at_limit = true;
     598            0 :     return;
     599              :   }
     600              :   d->retry_delay
     601            2 :     = GNUNET_TIME_STD_BACKOFF (d->retry_delay);
     602            2 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     603              :               "Downloading keys from %s (%s)\n",
     604              :               d->donau_url,
     605              :               d->force_retry ? "forced" : "regular");
     606            4 :   d->conn = DONAU_get_keys (ctx,
     607            2 :                             d->donau_url,
     608              :                             &donau_cert_cb,
     609              :                             d);
     610            2 :   d->force_retry = false;
     611            2 :   if (NULL != d->conn)
     612              :   {
     613            2 :     active_inquiries++;
     614              :   }
     615              :   else
     616              :   {
     617              :     struct GNUNET_TIME_Relative n;
     618              : 
     619            0 :     n = GNUNET_TIME_relative_max (d->retry_delay,
     620              :                                   DONAU_MAXFREQ);
     621              : 
     622              :     d->retry_task
     623            0 :       = 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            0 : donau_charity_cb (void *cls,
     639              :                   const struct DONAU_GetCharityResponse *gcr)
     640              : {
     641            0 :   struct ForceCharityCtx *fcc = cls;
     642            0 :   fcc->h = NULL;
     643              : 
     644            0 :   switch (gcr->hr.http_status)
     645              :   {
     646            0 :   case MHD_HTTP_OK:
     647            0 :     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            0 :     if (! store_donau_charity (fcc->charity_id,
     653            0 :                                fcc->donau_url,
     654              :                                &gcr->details.ok.charity))
     655              :     {
     656            0 :       GNUNET_break (0);
     657              :     }
     658            0 :     break;
     659              : 
     660            0 :   default:
     661            0 :     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            0 :     break;
     667              :   }
     668              : 
     669            0 :   end_inquiry ();
     670            0 : }
     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            0 : download_charity_id (void *cls)
     680              : {
     681            0 :   struct ForceCharityCtx *fcc = cls;
     682              : 
     683            0 :   fcc->retry_task = NULL;
     684              :   /* nothing to do if a request is already outstanding */
     685            0 :   if (NULL != fcc->h)
     686            0 :     return;
     687              : 
     688              :   /* respect global inquiry limit */
     689            0 :   GNUNET_break (OPEN_INQUIRY_LIMIT >= active_inquiries);
     690            0 :   if (OPEN_INQUIRY_LIMIT <= active_inquiries)
     691              :   {
     692            0 :     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            0 :     fcc->limited = true;
     697            0 :     at_limit = true;
     698            0 :     return;
     699              :   }
     700              : 
     701            0 :   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            0 :   fcc->h = DONAU_charity_get (ctx,
     707            0 :                               fcc->donau_url,
     708              :                               fcc->charity_id,
     709              :                               NULL,          /* bearer token -- not needed */
     710              :                               &donau_charity_cb,
     711              :                               fcc);
     712              : 
     713            0 :   if (NULL != fcc->h)
     714              :   {
     715            0 :     fcc->retry_delay = GNUNET_TIME_UNIT_ZERO;
     716            0 :     active_inquiries++;
     717              :   }
     718              :   else
     719              :   {
     720            0 :     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            0 :       = GNUNET_TIME_STD_BACKOFF (fcc->retry_delay);
     727              :     fcc->retry_task
     728            0 :       = 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            0 : lookup_donau (const char *donau_url)
     744              : {
     745            0 :   for (struct Donau *d = d_head;
     746            0 :        NULL != d;
     747            0 :        d = d->next)
     748            0 :     if (0 == strcmp (d->donau_url,
     749              :                      donau_url))
     750            0 :       return d;
     751            0 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     752              :               "Got notification about unknown Donau `%s'\n",
     753              :               donau_url);
     754            0 :   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            0 : lookup_donau_charity (uint64_t di_serial)
     766              : {
     767            0 :   for (struct ForceCharityCtx *fcc = fcc_head;
     768            0 :        NULL != fcc;
     769            0 :        fcc = fcc->next)
     770            0 :     if (fcc->di_serial == di_serial)
     771            0 :       return fcc;
     772            0 :   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            0 : force_donau_charity_id (void *cls,
     785              :                         const void *extra,
     786              :                         size_t extra_len)
     787              : {
     788              :   uint64_t di_serial;
     789            0 :   char *donau_url = NULL;
     790            0 :   uint64_t charity_id = -1;
     791              :   enum GNUNET_DB_QueryStatus qs;
     792              :   struct ForceCharityCtx *fcc;
     793              : 
     794            0 :   if ( (sizeof(uint64_t) != extra_len) ||
     795              :        (NULL == extra) )
     796              :   {
     797            0 :     GNUNET_break (0);
     798            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     799              :                 "Incorrect extra for the force_donau_charity_id");
     800            0 :     return;
     801              :   }
     802            0 :   GNUNET_memcpy (&di_serial,
     803              :                  extra,
     804              :                  sizeof(uint64_t));
     805            0 :   di_serial = GNUNET_ntohll (di_serial);
     806            0 :   qs = TALER_MERCHANTDB_get_donau_instance_by_serial (pg,
     807              :                                                       di_serial,
     808              :                                                       &donau_url,
     809              :                                                       &charity_id);
     810              : 
     811            0 :   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
     812              :   {
     813            0 :     GNUNET_break (0);
     814            0 :     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            0 :     return;
     819              :   }
     820              : 
     821            0 :   fcc = lookup_donau_charity (di_serial);
     822            0 :   if (NULL == fcc)
     823              :   {
     824            0 :     fcc = GNUNET_new (struct ForceCharityCtx);
     825            0 :     fcc->di_serial   = di_serial;
     826            0 :     fcc->donau_url   = donau_url;      /* take ownership */
     827            0 :     fcc->charity_id  = charity_id;
     828            0 :     GNUNET_CONTAINER_DLL_insert (fcc_head, fcc_tail, fcc);
     829              : 
     830            0 :     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            0 :     GNUNET_free (donau_url);
     840            0 :     if (NULL != fcc->h)
     841              :     {
     842            0 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     843              :                   "Already downloading charity_id for donau `%s'\n",
     844              :                   fcc->donau_url);
     845            0 :       return;
     846              :     }
     847            0 :     if (NULL != fcc->retry_task)
     848              :     {
     849            0 :       GNUNET_SCHEDULER_cancel (fcc->retry_task);
     850            0 :       fcc->retry_task = NULL;
     851              :     }
     852            0 :     fcc->charity_id = charity_id;
     853              :   }
     854            0 :   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            0 : force_donau_keys (void *cls,
     867              :                   const void *extra,
     868              :                   size_t extra_len)
     869              : {
     870            0 :   const char *url = extra;
     871              :   struct Donau *d;
     872              : 
     873            0 :   if ( (NULL == extra) ||
     874              :        (0 == extra_len) )
     875              :   {
     876            0 :     GNUNET_break (0);
     877            0 :     return;
     878              :   }
     879            0 :   if ('\0' != url[extra_len - 1])
     880              :   {
     881            0 :     GNUNET_break (0);
     882            0 :     return;
     883              :   }
     884            0 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     885              :               "Received keys update notification: reload `%s'\n",
     886              :               url);
     887              : 
     888            0 :   d = lookup_donau (url);
     889            0 :   if (NULL == d)
     890              :   {
     891            0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     892              :                 "Donau instance `%s' not found. Creating new instance.\n",
     893              :                 url);
     894              : 
     895            0 :     d = GNUNET_new (struct Donau);
     896            0 :     d->donau_url = GNUNET_strdup (url);
     897            0 :     d->retry_delay = DONAU_MAXFREQ;
     898            0 :     d->first_retry = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_ZERO);
     899              : 
     900            0 :     GNUNET_CONTAINER_DLL_insert (d_head,
     901              :                                  d_tail,
     902              :                                  d);
     903            0 :     download_keys (d);
     904              :   }
     905              : 
     906            0 :   if (NULL != d->conn)
     907              :   {
     908            0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     909              :                 "Already downloading %skeys\n",
     910              :                 url);
     911            0 :     return;
     912              :   }
     913            0 :   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            0 :   if (NULL != d->retry_task)
     921            0 :     GNUNET_SCHEDULER_cancel (d->retry_task);
     922            0 :   d->force_retry = true;
     923              :   d->retry_task
     924            0 :     = 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            2 : shutdown_task (void *cls)
     937              : {
     938              :   (void) cls;
     939            2 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     940              :               "Running shutdown\n");
     941            4 :   while (NULL != d_head)
     942              :   {
     943            2 :     struct Donau *d = d_head;
     944              : 
     945            2 :     GNUNET_free (d->donau_url);
     946            2 :     GNUNET_free (d->currency);
     947            2 :     if (NULL != d->conn)
     948              :     {
     949            0 :       DONAU_get_keys_cancel (d->conn);
     950            0 :       d->conn = NULL;
     951              :     }
     952            2 :     if (NULL != d->keys)
     953              :     {
     954            2 :       DONAU_keys_decref (d->keys);
     955            2 :       d->keys = NULL;
     956              :     }
     957            2 :     if (NULL != d->retry_task)
     958              :     {
     959            2 :       GNUNET_SCHEDULER_cancel (d->retry_task);
     960            2 :       d->retry_task = NULL;
     961              :     }
     962            2 :     GNUNET_CONTAINER_DLL_remove (d_head,
     963              :                                  d_tail,
     964              :                                  d);
     965            2 :     GNUNET_free (d);
     966              :   }
     967            2 :   while (NULL != fcc_head)
     968              :   {
     969            0 :     struct ForceCharityCtx *fcc = fcc_head;
     970              : 
     971            0 :     if (NULL != fcc->h)
     972              :     {
     973            0 :       DONAU_charity_get_cancel (fcc->h);
     974            0 :       fcc->h = NULL;
     975              :     }
     976            0 :     if (NULL != fcc->retry_task)
     977              :     {
     978            0 :       GNUNET_SCHEDULER_cancel (fcc->retry_task);
     979            0 :       fcc->retry_task = NULL;
     980              :     }
     981            0 :     GNUNET_CONTAINER_DLL_remove (fcc_head,
     982              :                                  fcc_tail,
     983              :                                  fcc);
     984            0 :     GNUNET_free (fcc->donau_url);
     985            0 :     GNUNET_free (fcc);
     986              :   }
     987            2 :   if (NULL != eh)
     988              :   {
     989            2 :     TALER_MERCHANTDB_event_listen_cancel (eh);
     990            2 :     eh = NULL;
     991              :   }
     992            2 :   if (NULL != eh_charity)
     993              :   {
     994            2 :     TALER_MERCHANTDB_event_listen_cancel (eh_charity);
     995            2 :     eh_charity = NULL;
     996              :   }
     997            2 :   if (NULL != pg)
     998              :   {
     999            2 :     TALER_MERCHANTDB_disconnect (pg);
    1000            2 :     pg = NULL;
    1001              :   }
    1002            2 :   cfg = NULL;
    1003            2 :   if (NULL != ctx)
    1004              :   {
    1005            2 :     GNUNET_CURL_fini (ctx);
    1006            2 :     ctx = NULL;
    1007              :   }
    1008            2 :   if (NULL != rc)
    1009              :   {
    1010            2 :     GNUNET_CURL_gnunet_rc_destroy (rc);
    1011            2 :     rc = NULL;
    1012              :   }
    1013            2 : }
    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            2 : 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            2 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1048              :               "Donau instance `%s' not found. Creating new instance.\n",
    1049              :               donau_url);
    1050            2 :   d = GNUNET_new (struct Donau);
    1051            2 :   d->donau_url = GNUNET_strdup (donau_url);
    1052            2 :   d->retry_delay = DONAU_MAXFREQ;
    1053            2 :   d->first_retry = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_ZERO);
    1054            2 :   GNUNET_CONTAINER_DLL_insert (d_head,
    1055              :                                d_tail,
    1056              :                                d);
    1057            2 :   if (NULL == donau_keys_json)
    1058              :   {
    1059            2 :     download_keys (d);
    1060            2 :     return;
    1061              :   }
    1062            0 :   d->keys = DONAU_keys_from_json (donau_keys_json);
    1063            0 :   if (NULL == d->keys)
    1064              :   {
    1065            0 :     GNUNET_break (0);
    1066            0 :     download_keys (d);
    1067            0 :     return;
    1068              :   }
    1069            0 :   d->retry_delay = DONAU_MAXFREQ;
    1070            0 :   d->first_retry = GNUNET_TIME_relative_to_absolute (DONAU_MAXFREQ);
    1071              : 
    1072              :   {
    1073              :     struct GNUNET_TIME_Absolute n;
    1074              : 
    1075            0 :     if (0 < d->keys->num_sign_keys)
    1076            0 :       n = GNUNET_TIME_absolute_min (
    1077              :         d->first_retry,
    1078            0 :         d->keys->sign_keys[0].expire_sign.abs_time);
    1079              :     else
    1080            0 :       n = d->first_retry;
    1081            0 :     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            0 :     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            2 : 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            2 :   cfg = c;
    1112            2 :   DONAU_setup (enable_h3
    1113            2 :                ? DONAU_GO_ENABLE_HTTP3
    1114              :                : DONAU_GO_FORCE_HTTP1_1);
    1115            2 :   GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
    1116              :                                  NULL);
    1117            2 :   ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
    1118              :                           &rc);
    1119            2 :   if (NULL == ctx)
    1120              :   {
    1121            0 :     GNUNET_break (0);
    1122            0 :     GNUNET_SCHEDULER_shutdown ();
    1123            0 :     global_ret = EXIT_FAILURE;
    1124            0 :     return;
    1125              :   }
    1126            2 :   rc = GNUNET_CURL_gnunet_rc_create (ctx);
    1127            2 :   if (NULL ==
    1128            2 :       (pg = TALER_MERCHANTDB_connect (cfg)) )
    1129              :   {
    1130            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1131              :                 "Failed to initialize DB subsystem\n");
    1132            0 :     GNUNET_SCHEDULER_shutdown ();
    1133            0 :     global_ret = EXIT_FAILURE;
    1134            0 :     return;
    1135              :   }
    1136              :   {
    1137            2 :     struct GNUNET_DB_EventHeaderP es = {
    1138            2 :       .size = htons (sizeof(es)),
    1139            2 :       .type = htons (TALER_DBEVENT_MERCHANT_DONAU_KEYS)
    1140              :     };
    1141              : 
    1142            4 :     eh = TALER_MERCHANTDB_event_listen (pg,
    1143              :                                         &es,
    1144            2 :                                         GNUNET_TIME_UNIT_FOREVER_REL,
    1145              :                                         &force_donau_keys,
    1146              :                                         NULL);
    1147              :   }
    1148              :   {
    1149            2 :     struct GNUNET_DB_EventHeaderP es = {
    1150            2 :       .size = htons (sizeof(es)),
    1151            2 :       .type = htons (TALER_DBEVENT_MERCHANT_DONAU_CHARITY_ID)
    1152              :     };
    1153              : 
    1154            4 :     eh_charity = TALER_MERCHANTDB_event_listen (
    1155              :       pg,
    1156              :       &es,
    1157            2 :       GNUNET_TIME_UNIT_FOREVER_REL,
    1158              :       &force_donau_charity_id,
    1159              :       NULL);
    1160              :   }
    1161              : 
    1162              :   {
    1163              :     enum GNUNET_DB_QueryStatus qs;
    1164              : 
    1165            2 :     qs = TALER_MERCHANTDB_iterate_all_donau_instances (pg,
    1166              :                                                        &accept_donau,
    1167              :                                                        NULL);
    1168            2 :     if (qs < 0)
    1169              :     {
    1170            0 :       GNUNET_break (0);
    1171            0 :       GNUNET_SCHEDULER_shutdown ();
    1172            0 :       return;
    1173              :     }
    1174              :   }
    1175            2 :   if ( (0 == active_inquiries) &&
    1176              :        (test_mode) )
    1177              :   {
    1178            0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1179              :                 "No donau keys inquiries to start, exiting.\n");
    1180            0 :     GNUNET_SCHEDULER_shutdown ();
    1181            0 :     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            2 : main (int argc,
    1195              :       char *const *argv)
    1196              : {
    1197            2 :   struct GNUNET_GETOPT_CommandLineOption options[] = {
    1198            2 :     GNUNET_GETOPT_option_flag ('3',
    1199              :                                "http3",
    1200              :                                "enable support for HTTP/2 and HTTP/3",
    1201              :                                &enable_h3),
    1202            2 :     GNUNET_GETOPT_option_timetravel ('T',
    1203              :                                      "timetravel"),
    1204            2 :     GNUNET_GETOPT_option_flag ('t',
    1205              :                                "test",
    1206              :                                "run in test mode and exit when idle",
    1207              :                                &test_mode),
    1208            2 :     GNUNET_GETOPT_option_version (VERSION),
    1209              :     GNUNET_GETOPT_OPTION_END
    1210              :   };
    1211              :   enum GNUNET_GenericReturnValue ret;
    1212              : 
    1213            2 :   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            2 :   if (GNUNET_SYSERR == ret)
    1222            0 :     return EXIT_INVALIDARGUMENT;
    1223            2 :   if (GNUNET_NO == ret)
    1224            0 :     return EXIT_SUCCESS;
    1225            2 :   return global_ret;
    1226              : }
    1227              : 
    1228              : 
    1229              : /* end of taler-merchant-donaukeyupdate.c */
        

Generated by: LCOV version 2.0-1