merchant

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

taler-merchant-exchangekeyupdate.c (31007B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2023, 2024, 2026 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-exchangekeyupdate.c
     18  * @brief Process that ensures our /keys data for all exchanges is current
     19  * @author Christian Grothoff
     20  */
     21 #include "platform.h"
     22 #include "microhttpd.h"
     23 #include <gnunet/gnunet_util_lib.h>
     24 #include <jansson.h>
     25 #include <pthread.h>
     26 #include <taler/taler_dbevents.h>
     27 struct Exchange;
     28 #define TALER_EXCHANGE_GET_KEYS_RESULT_CLOSURE struct Exchange
     29 #include <taler/exchange/get-keys.h>
     30 
     31 #include "taler/taler_merchant_util.h"
     32 #include "taler/taler_merchant_bank_lib.h"
     33 #include "merchantdb_lib.h"
     34 #include "merchant-database/delete_exchange_accounts.h"
     35 #include "merchant-database/insert_exchange_keys.h"
     36 #include "merchant-database/insert_exchange_signing_key.h"
     37 #include "merchant-database/start.h"
     38 #include "merchant-database/preflight.h"
     39 #include "merchant-database/event_notify.h"
     40 #include "merchant-database/event_listen.h"
     41 #include "merchant-database/insert_exchange_account.h"
     42 #include "merchant-database/get_exchange_keys.h"
     43 #include "merchant-database/insert_exchange_wire_fee.h"
     44 
     45 /**
     46  * Maximum frequency for the exchange interaction.
     47  */
     48 #define EXCHANGE_MAXFREQ GNUNET_TIME_relative_multiply ( \
     49           GNUNET_TIME_UNIT_MINUTES, \
     50           5)
     51 
     52 /**
     53  * How many inquiries do we process concurrently at most.
     54  */
     55 #define OPEN_INQUIRY_LIMIT 1024
     56 
     57 /**
     58  * How often do we retry after DB serialization errors (at most)?
     59  */
     60 #define MAX_RETRIES 3
     61 
     62 /**
     63  * Information about an exchange.
     64  */
     65 struct Exchange
     66 {
     67   /**
     68    * Kept in a DLL.
     69    */
     70   struct Exchange *next;
     71 
     72   /**
     73    * Kept in a DLL.
     74    */
     75   struct Exchange *prev;
     76 
     77   /**
     78    * Base URL of the exchange are we tracking here.
     79    */
     80   char *exchange_url;
     81 
     82   /**
     83    * Expected currency of the exchange.
     84    */
     85   char *currency;
     86 
     87   /**
     88    * A /keys request to this exchange, NULL if not active.
     89    */
     90   struct TALER_EXCHANGE_GetKeysHandle *conn;
     91 
     92   /**
     93    * The keys of this exchange, NULL if not known.
     94    */
     95   struct TALER_EXCHANGE_Keys *keys;
     96 
     97   /**
     98    * Task where we retry fetching /keys from the exchange.
     99    */
    100   struct GNUNET_SCHEDULER_Task *retry_task;
    101 
    102   /**
    103    * Master public key expected for this exchange.
    104    */
    105   struct TALER_MasterPublicKeyP master_pub;
    106 
    107   /**
    108    * How soon can may we, at the earliest, re-download /keys?
    109    */
    110   struct GNUNET_TIME_Absolute first_retry;
    111 
    112   /**
    113    * How long should we wait between the next retry?
    114    * Used for exponential back-offs.
    115    */
    116   struct GNUNET_TIME_Relative retry_delay;
    117 
    118   /**
    119    * Are we waiting for /keys downloads due to our
    120    * hard limit?
    121    */
    122   bool limited;
    123 
    124   /**
    125    * Are we force-retrying a /keys download because some keys
    126    * were missing (and we thus should not cherry-pick, as
    127    * a major reason for a force-reload would be an
    128    * exchange that has lost keys and backfilled them, which
    129    * breaks keys downloads with cherry-picking).
    130    */
    131   bool force_retry;
    132 };
    133 
    134 
    135 /**
    136  * Head of known exchanges.
    137  */
    138 static struct Exchange *e_head;
    139 
    140 /**
    141  * Tail of known exchanges.
    142  */
    143 static struct Exchange *e_tail;
    144 
    145 /**
    146  * The merchant's configuration.
    147  */
    148 static const struct GNUNET_CONFIGURATION_Handle *cfg;
    149 
    150 /**
    151  * Our database plugin.
    152  */
    153 static struct TALER_MERCHANTDB_PostgresContext *pg;
    154 
    155 /**
    156  * Our event handler listening for /keys forced downloads.
    157  */
    158 static struct GNUNET_DB_EventHandler *eh;
    159 
    160 /**
    161  * Handle to the context for interacting with the bank.
    162  */
    163 static struct GNUNET_CURL_Context *ctx;
    164 
    165 /**
    166  * Scheduler context for running the @e ctx.
    167  */
    168 static struct GNUNET_CURL_RescheduleContext *rc;
    169 
    170 /**
    171  * How many active inquiries do we have right now.
    172  */
    173 static unsigned int active_inquiries;
    174 
    175 /**
    176  * Value to return from main(). 0 on success, non-zero on errors.
    177  */
    178 static int global_ret;
    179 
    180 /**
    181  * Should we enable HTTP/2 and HTTP/3 when talking to the exchange?
    182  * Those are not expected to be terribly beneficial for a client with
    183  * stable connections to a few servers, but they could cause stability
    184  * issues with libcurl.  Hence we *default* to HTTP/1.1-only, as that
    185  * is the conservative and most tested code path.
    186  */
    187 static int enable_h3;
    188 
    189 /**
    190  * #GNUNET_YES if we are in test mode and should exit when idle.
    191  */
    192 static int test_mode;
    193 
    194 /**
    195  * True if the last DB query was limited by the
    196  * #OPEN_INQUIRY_LIMIT and we thus should check again
    197  * as soon as we are substantially below that limit,
    198  * and not only when we get a DB notification.
    199  */
    200 static bool at_limit;
    201 
    202 
    203 /**
    204  * Function that initiates a /keys download.
    205  *
    206  * @param cls a `struct Exchange *`
    207  */
    208 static void
    209 download_keys (void *cls);
    210 
    211 
    212 /**
    213  * An inquiry finished, check if we need to start more.
    214  */
    215 static void
    216 end_inquiry (void)
    217 {
    218   GNUNET_assert (active_inquiries > 0);
    219   active_inquiries--;
    220   if ( (active_inquiries < OPEN_INQUIRY_LIMIT / 2) &&
    221        (at_limit) )
    222   {
    223     at_limit = false;
    224     for (struct Exchange *e = e_head;
    225          NULL != e;
    226          e = e->next)
    227     {
    228       if (! e->limited)
    229         continue;
    230       e->limited = false;
    231       /* done synchronously so that the active_inquiries
    232          is updated immediately */
    233       download_keys (e);
    234       if (at_limit)
    235         break;
    236     }
    237   }
    238   if ( (! at_limit) &&
    239        (0 == active_inquiries) &&
    240        (test_mode) )
    241   {
    242     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    243                 "No more open inquiries and in test mode. Existing.\n");
    244     GNUNET_SCHEDULER_shutdown ();
    245     return;
    246   }
    247 }
    248 
    249 
    250 /**
    251  * Add account restriction @a a to array of @a restrictions.
    252  *
    253  * @param[in,out] restrictions JSON array to build
    254  * @param r restriction to add to @a restrictions
    255  * @return #GNUNET_SYSERR if @a r is malformed
    256  */
    257 static enum GNUNET_GenericReturnValue
    258 add_restriction (json_t *restrictions,
    259                  const struct TALER_EXCHANGE_AccountRestriction *r)
    260 {
    261   json_t *jr;
    262 
    263   jr = NULL;
    264   switch (r->type)
    265   {
    266   case TALER_EXCHANGE_AR_INVALID:
    267     GNUNET_break_op (0);
    268     return GNUNET_SYSERR;
    269   case TALER_EXCHANGE_AR_DENY:
    270     jr = GNUNET_JSON_PACK (
    271       GNUNET_JSON_pack_string ("type",
    272                                "deny")
    273       );
    274     break;
    275   case TALER_EXCHANGE_AR_REGEX:
    276     jr = GNUNET_JSON_PACK (
    277       GNUNET_JSON_pack_string (
    278         "type",
    279         "regex"),
    280       GNUNET_JSON_pack_string (
    281         "regex",
    282         r->details.regex.posix_egrep),
    283       GNUNET_JSON_pack_string (
    284         "human_hint",
    285         r->details.regex.human_hint),
    286       GNUNET_JSON_pack_object_incref (
    287         "human_hint_i18n",
    288         (json_t *) r->details.regex.human_hint_i18n)
    289       );
    290     break;
    291   }
    292   if (NULL == jr)
    293   {
    294     GNUNET_break_op (0);
    295     return GNUNET_SYSERR;
    296   }
    297   GNUNET_assert (0 ==
    298                  json_array_append_new (restrictions,
    299                                         jr));
    300   return GNUNET_OK;
    301 
    302 }
    303 
    304 
    305 /**
    306  * The /keys download from @e failed with @a http_status and @a ec.
    307  * Record the failure in the database.
    308  *
    309  * @param e exchange that failed
    310  * @param http_status HTTP status returned
    311  * @param ec Taler error code
    312  */
    313 static void
    314 fail_keys (const struct Exchange *e,
    315            unsigned int http_status,
    316            enum TALER_ErrorCode ec)
    317 {
    318   enum GNUNET_DB_QueryStatus qs;
    319 
    320   qs = TALER_MERCHANTDB_insert_exchange_keys (
    321     pg,
    322     e->exchange_url,
    323     NULL,
    324     GNUNET_TIME_relative_to_absolute (e->retry_delay),
    325     http_status,
    326     ec);
    327   if (0 > qs)
    328   {
    329     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    330     return;
    331   }
    332 }
    333 
    334 
    335 /**
    336  * Update our information in the database about the
    337  * /keys of an exchange. Run inside of a database
    338  * transaction scope that will re-try and/or commit
    339  * depending on the return value.
    340  *
    341  * @param keys information to persist
    342  * @param first_retry earliest we may retry fetching the keys
    343  * @return transaction status
    344  */
    345 static enum GNUNET_DB_QueryStatus
    346 insert_keys_data (const struct TALER_EXCHANGE_Keys *keys,
    347                   struct GNUNET_TIME_Absolute first_retry)
    348 {
    349   enum GNUNET_DB_QueryStatus qs;
    350 
    351   /* store exchange online signing keys in our DB */
    352   for (unsigned int i = 0; i<keys->num_sign_keys; i++)
    353   {
    354     const struct TALER_EXCHANGE_SigningPublicKey *sign_key
    355       = &keys->sign_keys[i];
    356 
    357     qs = TALER_MERCHANTDB_insert_exchange_signing_key (
    358       pg,
    359       &keys->master_pub,
    360       &sign_key->key,
    361       sign_key->valid_from,
    362       sign_key->valid_until,
    363       sign_key->valid_legal,
    364       &sign_key->master_sig);
    365     /* 0 is OK, we may already have the key in the DB! */
    366     if (0 > qs)
    367     {
    368       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    369       return qs;
    370     }
    371   }
    372 
    373   qs = TALER_MERCHANTDB_insert_exchange_keys (pg,
    374                                               keys->exchange_url,
    375                                               keys,
    376                                               first_retry,
    377                                               MHD_HTTP_OK,
    378                                               TALER_EC_NONE);
    379   if (0 > qs)
    380   {
    381     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    382     return qs;
    383   }
    384 
    385   qs = TALER_MERCHANTDB_delete_exchange_accounts (pg,
    386                                                   &keys->master_pub);
    387   if (0 > qs)
    388   {
    389     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    390     return qs;
    391   }
    392 
    393   for (unsigned int i = 0; i<keys->accounts_len; i++)
    394   {
    395     const struct TALER_EXCHANGE_WireAccount *account
    396       = &keys->accounts[i];
    397     json_t *debit_restrictions;
    398     json_t *credit_restrictions;
    399 
    400     debit_restrictions = json_array ();
    401     GNUNET_assert (NULL != debit_restrictions);
    402     credit_restrictions = json_array ();
    403     GNUNET_assert (NULL != credit_restrictions);
    404     for (unsigned int j = 0; j<account->debit_restrictions_length; j++)
    405     {
    406       if (GNUNET_OK !=
    407           add_restriction (debit_restrictions,
    408                            &account->debit_restrictions[j]))
    409       {
    410         TALER_MERCHANTDB_rollback (pg);
    411         GNUNET_break (0);
    412         json_decref (debit_restrictions);
    413         json_decref (credit_restrictions);
    414         return GNUNET_DB_STATUS_HARD_ERROR;
    415       }
    416     }
    417     for (unsigned int j = 0; j<account->credit_restrictions_length; j++)
    418     {
    419       if (GNUNET_OK !=
    420           add_restriction (credit_restrictions,
    421                            &account->credit_restrictions[j]))
    422       {
    423         TALER_MERCHANTDB_rollback (pg);
    424         GNUNET_break (0);
    425         json_decref (debit_restrictions);
    426         json_decref (credit_restrictions);
    427         return GNUNET_DB_STATUS_HARD_ERROR;
    428       }
    429     }
    430     qs = TALER_MERCHANTDB_insert_exchange_account (
    431       pg,
    432       &keys->master_pub,
    433       account->fpayto_uri,
    434       account->conversion_url,
    435       debit_restrictions,
    436       credit_restrictions,
    437       &account->master_sig);
    438     json_decref (debit_restrictions);
    439     json_decref (credit_restrictions);
    440     if (qs < 0)
    441     {
    442       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    443       return qs;
    444     }
    445   } /* end 'for all accounts' */
    446 
    447   for (unsigned int i = 0; i<keys->fees_len; i++)
    448   {
    449     const struct TALER_EXCHANGE_WireFeesByMethod *fbm
    450       = &keys->fees[i];
    451     const char *wire_method = fbm->method;
    452     const struct TALER_EXCHANGE_WireAggregateFees *fees
    453       = fbm->fees_head;
    454 
    455     while (NULL != fees)
    456     {
    457       struct GNUNET_HashCode h_wire_method;
    458 
    459       GNUNET_CRYPTO_hash (wire_method,
    460                           strlen (wire_method) + 1,
    461                           &h_wire_method);
    462       qs = TALER_MERCHANTDB_insert_exchange_wire_fee (
    463         pg,
    464         &keys->master_pub,
    465         &h_wire_method,
    466         &fees->fees,
    467         fees->start_date,
    468         fees->end_date,
    469         &fees->master_sig);
    470       if (0 > qs)
    471       {
    472         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    473         return qs;
    474       }
    475       fees = fees->next;
    476     } /* all fees for this method */
    477   } /* for all methods (i) */
    478 
    479   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    480               "Updated keys for %s, inserted %d signing keys, %d denom keys, %d fees-by-wire\n",
    481               keys->exchange_url,
    482               keys->num_sign_keys,
    483               keys->num_denom_keys,
    484               keys->fees_len);
    485 
    486   {
    487     struct GNUNET_DB_EventHeaderP es = {
    488       .size = htons (sizeof (es)),
    489       .type = htons (TALER_DBEVENT_MERCHANT_EXCHANGE_KEYS)
    490     };
    491 
    492     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    493                 "Informing other processes about keys change for %s\n",
    494                 keys->exchange_url);
    495     TALER_MERCHANTDB_event_notify (pg,
    496                                    &es,
    497                                    keys->exchange_url,
    498                                    strlen (keys->exchange_url) + 1);
    499   }
    500   return qs;
    501 }
    502 
    503 
    504 /**
    505  * Run database transaction to store the @a keys in
    506  * the merchant database (and notify other processes
    507  * that may care about them).
    508  *
    509  * @param keys the keys to store
    510  * @param first_retry earliest we may retry fetching the keys
    511  * @return true on success
    512  */
    513 static bool
    514 store_keys (struct TALER_EXCHANGE_Keys *keys,
    515             struct GNUNET_TIME_Absolute first_retry)
    516 {
    517   enum GNUNET_DB_QueryStatus qs;
    518 
    519   TALER_MERCHANTDB_preflight (pg);
    520   for (unsigned int r = 0; r<MAX_RETRIES; r++)
    521   {
    522     if (GNUNET_OK !=
    523         TALER_MERCHANTDB_start (pg,
    524                                 "update exchange key data"))
    525     {
    526       TALER_MERCHANTDB_rollback (pg);
    527       GNUNET_break (0);
    528       return false;
    529     }
    530 
    531     qs = insert_keys_data (keys,
    532                            first_retry);
    533     if (0 > qs)
    534     {
    535       TALER_MERCHANTDB_rollback (pg);
    536       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    537         continue;
    538       GNUNET_break (0);
    539       return false;
    540     }
    541 
    542     qs = TALER_MERCHANTDB_commit (pg);
    543     if (0 > qs)
    544     {
    545       TALER_MERCHANTDB_rollback (pg);
    546       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    547         continue;
    548       GNUNET_break (0);
    549       return false;
    550     }
    551     break;
    552   } /* end of retry loop */
    553   if (qs < 0)
    554   {
    555     GNUNET_break (0);
    556     return false;
    557   }
    558   return true;
    559 }
    560 
    561 
    562 /**
    563  * Function called with information about who is auditing
    564  * a particular exchange and what keys the exchange is using.
    565  *
    566  * @param e the exchange to update
    567  * @param kr response data
    568  * @param[in] keys the keys of the exchange
    569  */
    570 static void
    571 cert_cb (
    572   struct Exchange *e,
    573   const struct TALER_EXCHANGE_KeysResponse *kr,
    574   struct TALER_EXCHANGE_Keys *keys)
    575 {
    576   struct GNUNET_TIME_Absolute n;
    577   struct GNUNET_TIME_Absolute first_retry;
    578 
    579   e->conn = NULL;
    580   e->retry_delay
    581     = GNUNET_TIME_STD_BACKOFF (e->retry_delay);
    582   switch (kr->hr.http_status)
    583   {
    584   case MHD_HTTP_OK:
    585     TALER_EXCHANGE_keys_decref (e->keys);
    586     e->keys = NULL;
    587     if (0 != strcasecmp (e->currency,
    588                          keys->currency))
    589     {
    590       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    591                   "/keys response from `%s' is for currency `%s', but we expected `%s'. Ignoring response.\n",
    592                   e->exchange_url,
    593                   keys->currency,
    594                   e->currency);
    595       fail_keys (e,
    596                  MHD_HTTP_OK,
    597                  TALER_EC_GENERIC_CURRENCY_MISMATCH);
    598       TALER_EXCHANGE_keys_decref (keys);
    599       break;
    600     }
    601     if (0 != GNUNET_memcmp (&keys->master_pub,
    602                             &e->master_pub))
    603     {
    604       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    605                   "Master public key in %skeys response does not match. Ignoring response.\n",
    606                   e->exchange_url);
    607       fail_keys (e,
    608                  MHD_HTTP_OK,
    609                  TALER_EC_MERCHANT_GENERIC_EXCHANGE_MASTER_KEY_MISMATCH);
    610       TALER_EXCHANGE_keys_decref (keys);
    611       break;
    612     }
    613     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    614                 "Got new keys for %s, updating database\n",
    615                 e->exchange_url);
    616     first_retry = GNUNET_TIME_relative_to_absolute (
    617       EXCHANGE_MAXFREQ);
    618     if (! store_keys (keys,
    619                       first_retry))
    620     {
    621       GNUNET_break (0);
    622       TALER_EXCHANGE_keys_decref (keys);
    623       break;
    624     }
    625     e->keys = keys;
    626     /* Reset back-off */
    627     e->retry_delay = EXCHANGE_MAXFREQ;
    628     /* limit retry */
    629     e->first_retry = first_retry;
    630     /* Limit by expiration */
    631     n = GNUNET_TIME_absolute_max (e->first_retry,
    632                                   keys->key_data_expiration.abs_time);
    633     if (NULL != e->retry_task)
    634       GNUNET_SCHEDULER_cancel (e->retry_task);
    635     e->retry_task = GNUNET_SCHEDULER_add_at (n,
    636                                              &download_keys,
    637                                              e);
    638     end_inquiry ();
    639     return;
    640   default:
    641     GNUNET_break (NULL == keys);
    642     fail_keys (e,
    643                kr->hr.http_status,
    644                TALER_EC_MERCHANT_GENERIC_EXCHANGE_KEYS_FAILURE);
    645     break;
    646   }
    647   /* Try again (soon-ish) */
    648   n = GNUNET_TIME_absolute_max (
    649     e->first_retry,
    650     GNUNET_TIME_relative_to_absolute (e->retry_delay));
    651   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    652               "Will download %skeys in %s\n",
    653               e->exchange_url,
    654               GNUNET_TIME_relative2s (
    655                 GNUNET_TIME_absolute_get_remaining (n),
    656                 true));
    657   if (NULL != e->retry_task)
    658     GNUNET_SCHEDULER_cancel (e->retry_task);
    659   e->retry_task
    660     = GNUNET_SCHEDULER_add_at (n,
    661                                &download_keys,
    662                                e);
    663   end_inquiry ();
    664 }
    665 
    666 
    667 static void
    668 download_keys (void *cls)
    669 {
    670   struct Exchange *e = cls;
    671 
    672   e->retry_task = NULL;
    673   GNUNET_break (OPEN_INQUIRY_LIMIT >= active_inquiries);
    674   if (OPEN_INQUIRY_LIMIT <= active_inquiries)
    675   {
    676     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    677                 "Cannot run job: at limit\n");
    678     e->limited = true;
    679     at_limit = true;
    680     return;
    681   }
    682   e->retry_delay
    683     = GNUNET_TIME_STD_BACKOFF (e->retry_delay);
    684   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    685               "Downloading keys from %s (%s)\n",
    686               e->exchange_url,
    687               e->force_retry ? "forced" : "regular");
    688   e->conn = TALER_EXCHANGE_get_keys_create (ctx,
    689                                             e->exchange_url);
    690   if ( (NULL != e->conn) &&
    691        (! e->force_retry) )
    692     TALER_EXCHANGE_get_keys_set_options (
    693       e->conn,
    694       TALER_EXCHANGE_get_keys_option_last_keys (e->keys));
    695   e->force_retry = false;
    696   if ( (NULL != e->conn) &&
    697        (TALER_EC_NONE ==
    698         TALER_EXCHANGE_get_keys_start (e->conn,
    699                                        &cert_cb,
    700                                        e)) )
    701   {
    702     active_inquiries++;
    703   }
    704   else
    705   {
    706     struct GNUNET_TIME_Relative n;
    707 
    708     if (NULL != e->conn)
    709     {
    710       TALER_EXCHANGE_get_keys_cancel (e->conn);
    711       e->conn = NULL;
    712     }
    713     n = GNUNET_TIME_relative_max (e->retry_delay,
    714                                   EXCHANGE_MAXFREQ);
    715     e->retry_task
    716       = GNUNET_SCHEDULER_add_delayed (n,
    717                                       &download_keys,
    718                                       e);
    719   }
    720 }
    721 
    722 
    723 /**
    724  * Lookup exchange by @a exchange_url. Create one
    725  * if it does not exist.
    726  *
    727  * @param exchange_url base URL to match against
    728  * @return NULL if not found
    729  */
    730 static struct Exchange *
    731 lookup_exchange (const char *exchange_url)
    732 {
    733   for (struct Exchange *e = e_head;
    734        NULL != e;
    735        e = e->next)
    736     if (0 == strcmp (e->exchange_url,
    737                      exchange_url))
    738       return e;
    739   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    740               "Got notification about unknown exchange `%s'\n",
    741               exchange_url);
    742   return NULL;
    743 }
    744 
    745 
    746 /**
    747  * Force immediate (re)loading of /keys for an exchange.
    748  *
    749  * @param cls NULL
    750  * @param extra base URL of the exchange that changed
    751  * @param extra_len number of bytes in @a extra
    752  */
    753 static void
    754 force_exchange_keys (void *cls,
    755                      const void *extra,
    756                      size_t extra_len)
    757 {
    758   const char *url = extra;
    759   struct Exchange *e;
    760 
    761   if ( (NULL == extra) ||
    762        (0 == extra_len) )
    763   {
    764     GNUNET_break (0);
    765     return;
    766   }
    767   if ('\0' != url[extra_len - 1])
    768   {
    769     GNUNET_break (0);
    770     return;
    771   }
    772   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    773               "Received keys change notification: reload `%s'\n",
    774               url);
    775   e = lookup_exchange (url);
    776   if (NULL == e)
    777   {
    778     GNUNET_break (0);
    779     return;
    780   }
    781   if (NULL != e->conn)
    782   {
    783     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    784                 "Already downloading %skeys\n",
    785                 url);
    786     return;
    787   }
    788   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    789               "Will download %skeys in %s\n",
    790               url,
    791               GNUNET_TIME_relative2s (
    792                 GNUNET_TIME_absolute_get_remaining (
    793                   e->first_retry),
    794                 true));
    795   if (NULL != e->retry_task)
    796     GNUNET_SCHEDULER_cancel (e->retry_task);
    797   e->force_retry = true;
    798   e->retry_task
    799     = GNUNET_SCHEDULER_add_at (e->first_retry,
    800                                &download_keys,
    801                                e);
    802 }
    803 
    804 
    805 /**
    806  * Function called on each configuration section. Finds sections
    807  * about exchanges, parses the entries.
    808  *
    809  * @param cls NULL
    810  * @param section name of the section
    811  */
    812 static void
    813 accept_exchanges (void *cls,
    814                   const char *section)
    815 {
    816   char *url;
    817   char *mks;
    818   char *currency;
    819 
    820   (void) cls;
    821   if (0 !=
    822       strncasecmp (section,
    823                    "merchant-exchange-",
    824                    strlen ("merchant-exchange-")))
    825     return;
    826   if (GNUNET_YES ==
    827       GNUNET_CONFIGURATION_get_value_yesno (cfg,
    828                                             section,
    829                                             "DISABLED"))
    830     return;
    831   if (GNUNET_OK !=
    832       GNUNET_CONFIGURATION_get_value_string (cfg,
    833                                              section,
    834                                              "EXCHANGE_BASE_URL",
    835                                              &url))
    836   {
    837     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    838                                section,
    839                                "EXCHANGE_BASE_URL");
    840     global_ret = EXIT_NOTCONFIGURED;
    841     GNUNET_SCHEDULER_shutdown ();
    842     return;
    843   }
    844   for (struct Exchange *e = e_head;
    845        NULL != e;
    846        e = e->next)
    847   {
    848     if (0 == strcmp (url,
    849                      e->exchange_url))
    850     {
    851       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    852                   "Exchange `%s' configured in multiple sections, maybe set DISABLED=YES in section `%s'?\n",
    853                   url,
    854                   section);
    855       GNUNET_free (url);
    856       global_ret = EXIT_NOTCONFIGURED;
    857       GNUNET_SCHEDULER_shutdown ();
    858       return;
    859     }
    860   }
    861   if (GNUNET_OK !=
    862       GNUNET_CONFIGURATION_get_value_string (cfg,
    863                                              section,
    864                                              "CURRENCY",
    865                                              &currency))
    866   {
    867     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    868                                section,
    869                                "CURRENCY");
    870     GNUNET_free (url);
    871     global_ret = EXIT_NOTCONFIGURED;
    872     GNUNET_SCHEDULER_shutdown ();
    873     return;
    874   }
    875   if (GNUNET_OK !=
    876       GNUNET_CONFIGURATION_get_value_string (cfg,
    877                                              section,
    878                                              "MASTER_KEY",
    879                                              &mks))
    880   {
    881     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    882                                section,
    883                                "MASTER_KEY");
    884     global_ret = EXIT_NOTCONFIGURED;
    885     GNUNET_SCHEDULER_shutdown ();
    886     GNUNET_free (currency);
    887     GNUNET_free (url);
    888     return;
    889   }
    890 
    891   {
    892     struct Exchange *e;
    893 
    894     e = GNUNET_new (struct Exchange);
    895     e->exchange_url = url;
    896     e->currency = currency;
    897     GNUNET_CONTAINER_DLL_insert (e_head,
    898                                  e_tail,
    899                                  e);
    900     if (GNUNET_OK !=
    901         GNUNET_CRYPTO_eddsa_public_key_from_string (
    902           mks,
    903           strlen (mks),
    904           &e->master_pub.eddsa_pub))
    905     {
    906       GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    907                                  section,
    908                                  "MASTER_KEY",
    909                                  "malformed EdDSA key");
    910       global_ret = EXIT_NOTCONFIGURED;
    911       GNUNET_SCHEDULER_shutdown ();
    912       GNUNET_free (mks);
    913       return;
    914     }
    915     GNUNET_free (mks);
    916 
    917     {
    918       enum GNUNET_DB_QueryStatus qs;
    919       struct TALER_EXCHANGE_Keys *keys = NULL;
    920 
    921       qs = TALER_MERCHANTDB_get_exchange_keys (pg,
    922                                                url,
    923                                                &e->first_retry,
    924                                                &keys);
    925       if (qs < 0)
    926       {
    927         GNUNET_break (0);
    928         global_ret = EXIT_FAILURE;
    929         GNUNET_SCHEDULER_shutdown ();
    930         return;
    931       }
    932       if ( (NULL != keys) &&
    933            (0 != strcasecmp (keys->currency,
    934                              e->currency)) )
    935       {
    936         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    937                     "/keys cached in our database were for currency `%s', but we expected `%s'. Fetching /keys again.\n",
    938                     keys->currency,
    939                     e->currency);
    940         TALER_EXCHANGE_keys_decref (keys);
    941         keys = NULL;
    942       }
    943       if ( (NULL != keys) &&
    944            (0 != GNUNET_memcmp (&e->master_pub,
    945                                 &keys->master_pub)) )
    946       {
    947         /* master pub differs => fetch keys again */
    948         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    949                     "Master public key of exchange `%s' differs from our configuration. Fetching /keys again.\n",
    950                     e->exchange_url);
    951         TALER_EXCHANGE_keys_decref (keys);
    952         keys = NULL;
    953       }
    954       e->keys = keys;
    955       if (NULL == keys)
    956       {
    957         /* done synchronously so that the active_inquiries
    958            is updated immediately */
    959 
    960         download_keys (e);
    961       }
    962       else
    963       {
    964         e->retry_task
    965           = GNUNET_SCHEDULER_add_at (keys->key_data_expiration.abs_time,
    966                                      &download_keys,
    967                                      e);
    968       }
    969     }
    970     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    971                 "Exchange `%s' setup\n",
    972                 e->exchange_url);
    973   }
    974 }
    975 
    976 
    977 /**
    978  * We're being aborted with CTRL-C (or SIGTERM). Shut down.
    979  *
    980  * @param cls closure (NULL)
    981  */
    982 static void
    983 shutdown_task (void *cls)
    984 {
    985   (void) cls;
    986   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    987               "Running shutdown\n");
    988   while (NULL != e_head)
    989   {
    990     struct Exchange *e = e_head;
    991 
    992     GNUNET_free (e->exchange_url);
    993     GNUNET_free (e->currency);
    994     if (NULL != e->conn)
    995     {
    996       TALER_EXCHANGE_get_keys_cancel (e->conn);
    997       e->conn = NULL;
    998     }
    999     if (NULL != e->keys)
   1000     {
   1001       TALER_EXCHANGE_keys_decref (e->keys);
   1002       e->keys = NULL;
   1003     }
   1004     if (NULL != e->retry_task)
   1005     {
   1006       GNUNET_SCHEDULER_cancel (e->retry_task);
   1007       e->retry_task = NULL;
   1008     }
   1009     GNUNET_CONTAINER_DLL_remove (e_head,
   1010                                  e_tail,
   1011                                  e);
   1012     GNUNET_free (e);
   1013   }
   1014   if (NULL != eh)
   1015   {
   1016     TALER_MERCHANTDB_event_listen_cancel (eh);
   1017     eh = NULL;
   1018   }
   1019   if (NULL != pg)
   1020   {
   1021     TALER_MERCHANTDB_disconnect (pg);
   1022     pg = NULL;
   1023   }
   1024   cfg = NULL;
   1025   if (NULL != ctx)
   1026   {
   1027     GNUNET_CURL_fini (ctx);
   1028     ctx = NULL;
   1029   }
   1030   if (NULL != rc)
   1031   {
   1032     GNUNET_CURL_gnunet_rc_destroy (rc);
   1033     rc = NULL;
   1034   }
   1035 }
   1036 
   1037 
   1038 /**
   1039  * First task.
   1040  *
   1041  * @param cls closure, NULL
   1042  * @param args remaining command-line arguments
   1043  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
   1044  * @param c configuration
   1045  */
   1046 static void
   1047 run (void *cls,
   1048      char *const *args,
   1049      const char *cfgfile,
   1050      const struct GNUNET_CONFIGURATION_Handle *c)
   1051 {
   1052   (void) args;
   1053   (void) cfgfile;
   1054 
   1055   cfg = c;
   1056   TALER_EXCHANGE_setup (enable_h3
   1057                         ? TALER_EXCHANGE_GO_ENABLE_HTTP3
   1058                         : TALER_EXCHANGE_GO_FORCE_HTTP1_1);
   1059   GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
   1060                                  NULL);
   1061   ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
   1062                           &rc);
   1063   rc = GNUNET_CURL_gnunet_rc_create (ctx);
   1064   if (NULL == ctx)
   1065   {
   1066     GNUNET_break (0);
   1067     GNUNET_SCHEDULER_shutdown ();
   1068     global_ret = EXIT_FAILURE;
   1069     return;
   1070   }
   1071   if (NULL ==
   1072       (pg = TALER_MERCHANTDB_connect (cfg)))
   1073   {
   1074     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1075                 "Failed to initialize DB subsystem. Consider running taler-merchant-dbconfig!\n");
   1076     GNUNET_SCHEDULER_shutdown ();
   1077     global_ret = EXIT_NOTCONFIGURED;
   1078     return;
   1079   }
   1080   {
   1081     struct GNUNET_DB_EventHeaderP es = {
   1082       .size = htons (sizeof (es)),
   1083       .type = htons (TALER_DBEVENT_MERCHANT_EXCHANGE_FORCE_KEYS)
   1084     };
   1085 
   1086     eh = TALER_MERCHANTDB_event_listen (pg,
   1087                                         &es,
   1088                                         GNUNET_TIME_UNIT_FOREVER_REL,
   1089                                         &force_exchange_keys,
   1090                                         NULL);
   1091   }
   1092   GNUNET_CONFIGURATION_iterate_sections (cfg,
   1093                                          &accept_exchanges,
   1094                                          NULL);
   1095   if ( (0 == active_inquiries) &&
   1096        (test_mode) )
   1097   {
   1098     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1099                 "No more open inquiries and in test mode. Existing.\n");
   1100     GNUNET_SCHEDULER_shutdown ();
   1101     return;
   1102   }
   1103 }
   1104 
   1105 
   1106 /**
   1107  * The main function of taler-merchant-exchangekeyupdate
   1108  *
   1109  * @param argc number of arguments from the command line
   1110  * @param argv command line arguments
   1111  * @return 0 ok, 1 on error
   1112  */
   1113 int
   1114 main (int argc,
   1115       char *const *argv)
   1116 {
   1117   struct GNUNET_GETOPT_CommandLineOption options[] = {
   1118     GNUNET_GETOPT_option_flag ('3',
   1119                                "http3",
   1120                                "enable support for HTTP/2 and HTTP/3",
   1121                                &enable_h3),
   1122     GNUNET_GETOPT_option_timetravel ('T',
   1123                                      "timetravel"),
   1124     GNUNET_GETOPT_option_flag ('t',
   1125                                "test",
   1126                                "run in test mode and exit when idle",
   1127                                &test_mode),
   1128     GNUNET_GETOPT_option_version (VERSION),
   1129     GNUNET_GETOPT_OPTION_END
   1130   };
   1131   enum GNUNET_GenericReturnValue ret;
   1132 
   1133   ret = GNUNET_PROGRAM_run (
   1134     TALER_MERCHANT_project_data (),
   1135     argc, argv,
   1136     "taler-merchant-exchangekeyupdate",
   1137     gettext_noop (
   1138       "background process that ensures our key and configuration data on exchanges is up-to-date"),
   1139     options,
   1140     &run, NULL);
   1141   if (GNUNET_SYSERR == ret)
   1142     return EXIT_INVALIDARGUMENT;
   1143   if (GNUNET_NO == ret)
   1144     return EXIT_SUCCESS;
   1145   return global_ret;
   1146 }
   1147 
   1148 
   1149 /* end of taler-merchant-exchangekeyupdate.c */