merchant

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

taler-merchant-donaukeyupdate.c (32092B)


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