LCOV - code coverage report
Current view: top level - backend - taler-merchant-reconciliation.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 52.7 % 429 226
Test Date: 2026-09-11 17:45:24 Functions: 87.5 % 16 14

            Line data    Source code
       1              : /*
       2              :   This file is part of TALER
       3              :   Copyright (C) 2023-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-reconciliation.c
      18              :  * @brief Process that reconciles information about incoming bank transfers with orders by asking the exchange
      19              :  * @author Christian Grothoff
      20              :  */
      21              : #include "platform.h"
      22              : struct Inquiry;
      23              : #define TALER_EXCHANGE_GET_TRANSFERS_RESULT_CLOSURE struct Inquiry
      24              : #include "microhttpd.h"
      25              : #include <gnunet/gnunet_util_lib.h>
      26              : #include <jansson.h>
      27              : #include <pthread.h>
      28              : #include <taler/taler_dbevents.h>
      29              : #include "taler/taler_merchant_util.h"
      30              : #include "taler/taler_merchant_bank_lib.h"
      31              : #include "merchantdb_lib.h"
      32              : #include "merchantdb_lib.h"
      33              : #include "merchant-database/update_to_expected_transfer_finalized.h"
      34              : #include "merchant-database/insert_transfer_details.h"
      35              : #include "merchant-database/iterate_deposits_by_contract_and_coin.h"
      36              : #include "merchant-database/get_exchange_wire_fee.h"
      37              : #include "merchant-database/get_exchange_keys.h"
      38              : #include "merchant-database/iterate_open_expected_transfers.h"
      39              : #include "merchant-database/set_instance.h"
      40              : #include "merchant-database/update_expected_transfer_status.h"
      41              : #include "merchant-database/event_listen.h"
      42              : #include "merchant-database/preflight.h"
      43              : 
      44              : /**
      45              :  * Timeout for the exchange interaction.  Rather long as we should do
      46              :  * long-polling and do not want to wake up too often.
      47              :  */
      48              : #define EXCHANGE_TIMEOUT GNUNET_TIME_relative_multiply ( \
      49              :           GNUNET_TIME_UNIT_MINUTES, \
      50              :           30)
      51              : 
      52              : /**
      53              :  * How many inquiries do we process concurrently at most.
      54              :  */
      55              : #define OPEN_INQUIRY_LIMIT 1024
      56              : 
      57              : /**
      58              :  * How many inquiries do we process concurrently per exchange at most.
      59              :  */
      60              : #define EXCHANGE_INQUIRY_LIMIT 16
      61              : 
      62              : 
      63              : /**
      64              :  * Information about an inquiry job.
      65              :  */
      66              : struct Inquiry;
      67              : 
      68              : 
      69              : /**
      70              :  * Information about an exchange.
      71              :  */
      72              : struct Exchange
      73              : {
      74              :   /**
      75              :    * Kept in a DLL.
      76              :    */
      77              :   struct Exchange *next;
      78              : 
      79              :   /**
      80              :    * Kept in a DLL.
      81              :    */
      82              :   struct Exchange *prev;
      83              : 
      84              :   /**
      85              :    * Head of active inquiries.
      86              :    */
      87              :   struct Inquiry *w_head;
      88              : 
      89              :   /**
      90              :    * Tail of active inquiries.
      91              :    */
      92              :   struct Inquiry *w_tail;
      93              : 
      94              :   /**
      95              :    * Which exchange are we tracking here.
      96              :    */
      97              :   char *exchange_url;
      98              : 
      99              :   /**
     100              :    * The keys of this exchange
     101              :    */
     102              :   struct TALER_EXCHANGE_Keys *keys;
     103              : 
     104              :   /**
     105              :    * How many active inquiries do we have right now with this exchange.
     106              :    */
     107              :   unsigned int exchange_inquiries;
     108              : 
     109              :   /**
     110              :    * How long should we wait between requests
     111              :    * for transfer details?
     112              :    */
     113              :   struct GNUNET_TIME_Relative transfer_delay;
     114              : 
     115              : };
     116              : 
     117              : 
     118              : /**
     119              :  * Information about an inquiry job.
     120              :  */
     121              : struct Inquiry
     122              : {
     123              :   /**
     124              :    * Kept in a DLL.
     125              :    */
     126              :   struct Inquiry *next;
     127              : 
     128              :   /**
     129              :    * Kept in a DLL.
     130              :    */
     131              :   struct Inquiry *prev;
     132              : 
     133              :   /**
     134              :    * Handle to the exchange that made the transfer.
     135              :    */
     136              :   struct Exchange *exchange;
     137              : 
     138              :   /**
     139              :    * Task where we retry fetching transfer details from the exchange.
     140              :    */
     141              :   struct GNUNET_SCHEDULER_Task *task;
     142              : 
     143              :   /**
     144              :    * For which merchant instance is this tracking request?
     145              :    */
     146              :   char *instance_id;
     147              : 
     148              :   /**
     149              :    * payto:// URI used for the transfer.
     150              :    */
     151              :   struct TALER_FullPayto payto_uri;
     152              : 
     153              :   /**
     154              :    * Handle for the GET /transfers request.
     155              :    */
     156              :   struct TALER_EXCHANGE_GetTransfersHandle *wdh;
     157              : 
     158              :   /**
     159              :    * When did the transfer happen?
     160              :    */
     161              :   struct GNUNET_TIME_Timestamp execution_time;
     162              : 
     163              :   /**
     164              :    * Argument for the /wire/transfers request.
     165              :    */
     166              :   struct TALER_WireTransferIdentifierRawP wtid;
     167              : 
     168              :   /**
     169              :    * Row of the wire transfer in our database.
     170              :    */
     171              :   uint64_t rowid;
     172              : 
     173              : };
     174              : 
     175              : 
     176              : /**
     177              :  * Head of known exchanges.
     178              :  */
     179              : static struct Exchange *e_head;
     180              : 
     181              : /**
     182              :  * Tail of known exchanges.
     183              :  */
     184              : static struct Exchange *e_tail;
     185              : 
     186              : /**
     187              :  * The merchant's configuration.
     188              :  */
     189              : static const struct GNUNET_CONFIGURATION_Handle *cfg;
     190              : 
     191              : /**
     192              :  * Our database connection.
     193              :  */
     194              : static struct TALER_MERCHANTDB_PostgresContext *pg;
     195              : 
     196              : /**
     197              :  * Handle to the context for interacting with the bank.
     198              :  */
     199              : static struct GNUNET_CURL_Context *ctx;
     200              : 
     201              : /**
     202              :  * Scheduler context for running the @e ctx.
     203              :  */
     204              : static struct GNUNET_CURL_RescheduleContext *rc;
     205              : 
     206              : /**
     207              :  * Main task for #find_work().
     208              :  */
     209              : static struct GNUNET_SCHEDULER_Task *task;
     210              : 
     211              : /**
     212              :  * Event handler to learn that there are new transfers
     213              :  * to check.
     214              :  */
     215              : static struct GNUNET_DB_EventHandler *eh;
     216              : 
     217              : /**
     218              :  * Event handler to learn that there may be new exchange
     219              :  * keys to check.
     220              :  */
     221              : static struct GNUNET_DB_EventHandler *eh_keys;
     222              : 
     223              : /**
     224              :  * How many active inquiries do we have right now.
     225              :  */
     226              : static unsigned int active_inquiries;
     227              : 
     228              : /**
     229              :  * Set to true if we ever encountered any problem.
     230              :  */
     231              : static bool found_problem;
     232              : 
     233              : /**
     234              :  * Value to return from main(). 0 on success, non-zero on errors.
     235              :  */
     236              : static int global_ret;
     237              : 
     238              : /**
     239              :  * Should we enable HTTP/2 and HTTP/3 when talking to the exchange?
     240              :  * Those are not expected to be terribly beneficial for a client with
     241              :  * stable connections to a few servers, but they could cause stability
     242              :  * issues with libcurl.  Hence we *default* to HTTP/1.1-only, as that
     243              :  * is the conservative and most tested code path.
     244              :  */
     245              : static int enable_h3;
     246              : 
     247              : /**
     248              :  * #GNUNET_YES if we are in test mode and should exit when idle.
     249              :  */
     250              : static int test_mode;
     251              : 
     252              : /**
     253              :  * True if the last DB query was limited by the
     254              :  * #OPEN_INQUIRY_LIMIT and we thus should check again
     255              :  * as soon as we are substantially below that limit,
     256              :  * and not only when we get a DB notification.
     257              :  */
     258              : static bool at_limit;
     259              : 
     260              : 
     261              : /**
     262              :  * Initiate download from exchange.
     263              :  *
     264              :  * @param cls a `struct Inquiry *`
     265              :  */
     266              : static void
     267              : exchange_request (void *cls);
     268              : 
     269              : 
     270              : /**
     271              :  * The exchange @a e is ready to handle more inquiries,
     272              :  * prepare to launch them.
     273              :  *
     274              :  * @param[in,out] e exchange to potentially launch inquiries on
     275              :  */
     276              : static void
     277            8 : launch_inquiries_at_exchange (struct Exchange *e)
     278              : {
     279            8 :   for (struct Inquiry *w = e->w_head;
     280            8 :        NULL != w;
     281            0 :        w = w->next)
     282              :   {
     283            0 :     if (e->exchange_inquiries >= EXCHANGE_INQUIRY_LIMIT)
     284            0 :       break;
     285            0 :     if ( (NULL == w->task) &&
     286            0 :          (NULL == w->wdh) )
     287              :     {
     288            0 :       e->exchange_inquiries++;
     289            0 :       w->task = GNUNET_SCHEDULER_add_now (&exchange_request,
     290              :                                           w);
     291              :     }
     292              :   }
     293            8 : }
     294              : 
     295              : 
     296              : /**
     297              :  * Updates the transaction status for inquiry @a w to the given values.
     298              :  *
     299              :  * @param w inquiry to update status for
     300              :  * @param next_attempt when should we retry @a w (if ever)
     301              :  * @param http_status HTTP status of the response
     302              :  * @param ec error code to use (if any)
     303              :  * @param last_hint hint delivered with the response (if any, possibly NULL)
     304              :  * @param needs_retry true if we should try the HTTP request again
     305              :  */
     306              : static void
     307           16 : update_transaction_status (const struct Inquiry *w,
     308              :                            struct GNUNET_TIME_Absolute next_attempt,
     309              :                            unsigned int http_status,
     310              :                            enum TALER_ErrorCode ec,
     311              :                            const char *last_hint,
     312              :                            bool needs_retry)
     313              : {
     314              :   enum GNUNET_DB_QueryStatus qs;
     315              : 
     316           16 :   qs = TALER_MERCHANTDB_set_instance (pg,
     317           16 :                                       w->instance_id);
     318           16 :   if (qs <= 0)
     319              :   {
     320            0 :     GNUNET_break (0);
     321            0 :     global_ret = EXIT_FAILURE;
     322            0 :     GNUNET_SCHEDULER_shutdown ();
     323            0 :     return;
     324              :   }
     325           16 :   qs = TALER_MERCHANTDB_update_expected_transfer_status (pg,
     326           16 :                                                          w->exchange->exchange_url,
     327              :                                                          &w->wtid,
     328              :                                                          next_attempt,
     329              :                                                          http_status,
     330              :                                                          ec,
     331              :                                                          last_hint,
     332              :                                                          needs_retry);
     333           16 :   GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
     334              :                 TALER_MERCHANTDB_set_instance (
     335              :                   pg,
     336              :                   NULL));
     337           16 :   if (qs < 0)
     338              :   {
     339            0 :     GNUNET_break (0);
     340            0 :     global_ret = EXIT_FAILURE;
     341            0 :     GNUNET_SCHEDULER_shutdown ();
     342            0 :     return;
     343              :   }
     344              : }
     345              : 
     346              : 
     347              : /**
     348              :  * Interact with the database to get the current set
     349              :  * of exchange keys known to us.
     350              :  *
     351              :  * @param e the exchange to check
     352              :  */
     353              : static void
     354            8 : sync_keys (struct Exchange *e)
     355              : {
     356              :   enum GNUNET_DB_QueryStatus qs;
     357              :   struct TALER_EXCHANGE_Keys *keys;
     358              :   struct GNUNET_TIME_Absolute first_retry;
     359              : 
     360            8 :   qs = TALER_MERCHANTDB_get_exchange_keys (pg,
     361            8 :                                            e->exchange_url,
     362              :                                            &first_retry,
     363              :                                            &keys);
     364            8 :   if (qs < 0)
     365              :   {
     366            0 :     GNUNET_break (0);
     367            0 :     return;
     368              :   }
     369            8 :   if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) ||
     370            8 :        (NULL == keys) )
     371              :   {
     372            0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     373              :                 "Cannot launch inquiries at `%s': lacking /keys response\n",
     374              :                 e->exchange_url);
     375            0 :     return;
     376              :   }
     377            8 :   TALER_EXCHANGE_keys_decref (e->keys);
     378            8 :   e->keys = keys;
     379            8 :   launch_inquiries_at_exchange (e);
     380              : }
     381              : 
     382              : 
     383              : /**
     384              :  * Lookup our internal data structure for the given
     385              :  * @a exchange_url or create one if we do not yet have
     386              :  * one.
     387              :  *
     388              :  * @param exchange_url base URL of the exchange
     389              :  * @return our state for this exchange
     390              :  */
     391              : static struct Exchange *
     392            8 : find_exchange (const char *exchange_url)
     393              : {
     394              :   struct Exchange *e;
     395              : 
     396            8 :   for (e = e_head; NULL != e; e = e->next)
     397            0 :     if (0 == strcmp (exchange_url,
     398            0 :                      e->exchange_url))
     399            0 :       return e;
     400            8 :   e = GNUNET_new (struct Exchange);
     401            8 :   e->exchange_url = GNUNET_strdup (exchange_url);
     402            8 :   GNUNET_CONTAINER_DLL_insert (e_head,
     403              :                                e_tail,
     404              :                                e);
     405            8 :   sync_keys (e);
     406            8 :   return e;
     407              : }
     408              : 
     409              : 
     410              : /**
     411              :  * Finds new transfers that require work in the merchant database.
     412              :  *
     413              :  * @param cls NULL
     414              :  */
     415              : static void
     416              : find_work (void *cls);
     417              : 
     418              : 
     419              : /**
     420              :  * Free resources of @a w.
     421              :  *
     422              :  * @param[in] w inquiry job to terminate
     423              :  */
     424              : static void
     425            8 : end_inquiry (struct Inquiry *w)
     426              : {
     427            8 :   struct Exchange *e = w->exchange;
     428              : 
     429            8 :   GNUNET_assert (active_inquiries > 0);
     430            8 :   active_inquiries--;
     431              :   /* The exchange-request slot (e->exchange_inquiries) is taken when the
     432              :      exchange_request task is scheduled (w->task) and held until either that
     433              :      task fails to create the request or the request completes.  Reclaim it
     434              :      here for whichever of the (mutually exclusive) states is active, and
     435              :      cancel a still-queued task to avoid a use-after-free on the freed w. */
     436            8 :   if (NULL != w->task)
     437              :   {
     438            0 :     GNUNET_SCHEDULER_cancel (w->task);
     439            0 :     w->task = NULL;
     440            0 :     GNUNET_assert (e->exchange_inquiries > 0);
     441            0 :     e->exchange_inquiries--;
     442              :   }
     443            8 :   if (NULL != w->wdh)
     444              :   {
     445            0 :     TALER_EXCHANGE_get_transfers_cancel (w->wdh);
     446            0 :     w->wdh = NULL;
     447            0 :     GNUNET_assert (e->exchange_inquiries > 0);
     448            0 :     e->exchange_inquiries--;
     449              :   }
     450            8 :   GNUNET_free (w->instance_id);
     451            8 :   GNUNET_free (w->payto_uri.full_payto);
     452            8 :   GNUNET_CONTAINER_DLL_remove (e->w_head,
     453              :                                e->w_tail,
     454              :                                w);
     455            8 :   GNUNET_free (w);
     456            8 :   if ( (active_inquiries < OPEN_INQUIRY_LIMIT / 2) &&
     457            8 :        (NULL == task) &&
     458              :        (at_limit) )
     459              :   {
     460            0 :     at_limit = false;
     461            0 :     GNUNET_assert (NULL == task);
     462            0 :     task = GNUNET_SCHEDULER_add_now (&find_work,
     463              :                                      NULL);
     464              :   }
     465            8 :   if ( (NULL == task) &&
     466            8 :        (! at_limit) &&
     467            8 :        (0 == active_inquiries) &&
     468              :        (test_mode) )
     469              :   {
     470            8 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     471              :                 "No more open inquiries and in test mode. Exiting.\n");
     472            8 :     GNUNET_SCHEDULER_shutdown ();
     473            8 :     return;
     474              :   }
     475              : }
     476              : 
     477              : 
     478              : /**
     479              :  * We're being aborted with CTRL-C (or SIGTERM). Shut down.
     480              :  *
     481              :  * @param cls closure (NULL)
     482              :  */
     483              : static void
     484           12 : shutdown_task (void *cls)
     485              : {
     486              :   (void) cls;
     487           12 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     488              :               "Running shutdown\n");
     489           20 :   while (NULL != e_head)
     490              :   {
     491            8 :     struct Exchange *e = e_head;
     492              : 
     493            8 :     while (NULL != e->w_head)
     494              :     {
     495            0 :       struct Inquiry *w = e->w_head;
     496              : 
     497            0 :       end_inquiry (w);
     498              :     }
     499            8 :     GNUNET_free (e->exchange_url);
     500            8 :     if (NULL != e->keys)
     501              :     {
     502            8 :       TALER_EXCHANGE_keys_decref (e->keys);
     503            8 :       e->keys = NULL;
     504              :     }
     505            8 :     GNUNET_CONTAINER_DLL_remove (e_head,
     506              :                                  e_tail,
     507              :                                  e);
     508            8 :     GNUNET_free (e);
     509              :   }
     510           12 :   if (NULL != eh)
     511              :   {
     512           12 :     TALER_MERCHANTDB_event_listen_cancel (eh);
     513           12 :     eh = NULL;
     514              :   }
     515           12 :   if (NULL != eh_keys)
     516              :   {
     517           12 :     TALER_MERCHANTDB_event_listen_cancel (eh_keys);
     518           12 :     eh_keys = NULL;
     519              :   }
     520           12 :   if (NULL != task)
     521              :   {
     522            0 :     GNUNET_SCHEDULER_cancel (task);
     523            0 :     task = NULL;
     524              :   }
     525           12 :   if (NULL != pg)
     526              :   {
     527           12 :     TALER_MERCHANTDB_disconnect (pg);
     528           12 :     pg = NULL;
     529              :   }
     530           12 :   cfg = NULL;
     531           12 :   if (NULL != ctx)
     532              :   {
     533           12 :     GNUNET_CURL_fini (ctx);
     534           12 :     ctx = NULL;
     535              :   }
     536           12 :   if (NULL != rc)
     537              :   {
     538           12 :     GNUNET_CURL_gnunet_rc_destroy (rc);
     539           12 :     rc = NULL;
     540              :   }
     541           12 : }
     542              : 
     543              : 
     544              : /**
     545              :  * Check that the given @a wire_fee is what the @a e should charge
     546              :  * at the @a execution_time.  If the fee is correct (according to our
     547              :  * database), return #GNUNET_OK.  If we do not have the fee structure in our
     548              :  * DB, we just accept it and return #GNUNET_NO; if we have proof that the fee
     549              :  * is bogus, we respond with the proof to the client and return
     550              :  * #GNUNET_SYSERR.
     551              :  *
     552              :  * @param w inquiry to check fees of
     553              :  * @param execution_time time of the wire transfer
     554              :  * @param wire_fee fee claimed by the exchange
     555              :  * @return #GNUNET_SYSERR if we returned hard proof of
     556              :  *   missbehavior from the exchange to the client
     557              :  */
     558              : static enum GNUNET_GenericReturnValue
     559            8 : check_wire_fee (struct Inquiry *w,
     560              :                 struct GNUNET_TIME_Timestamp execution_time,
     561              :                 const struct TALER_Amount *wire_fee)
     562              : {
     563            8 :   struct Exchange *e = w->exchange;
     564            8 :   const struct TALER_EXCHANGE_Keys *keys = e->keys;
     565              :   struct TALER_WireFeeSet fees;
     566              :   struct TALER_MasterSignatureP master_sig;
     567              :   struct GNUNET_TIME_Timestamp start_date;
     568              :   struct GNUNET_TIME_Timestamp end_date;
     569              :   enum GNUNET_DB_QueryStatus qs;
     570              :   char *wire_method;
     571              : 
     572            8 :   if (NULL == keys)
     573              :   {
     574            0 :     GNUNET_break (0);
     575            0 :     return GNUNET_NO;
     576              :   }
     577            8 :   wire_method = TALER_payto_get_method (w->payto_uri.full_payto);
     578            8 :   qs = TALER_MERCHANTDB_get_exchange_wire_fee (pg,
     579              :                                                &keys->master_pub,
     580              :                                                wire_method,
     581              :                                                execution_time,
     582              :                                                &fees,
     583              :                                                &start_date,
     584              :                                                &end_date,
     585              :                                                &master_sig);
     586            8 :   switch (qs)
     587              :   {
     588            0 :   case GNUNET_DB_STATUS_HARD_ERROR:
     589            0 :     GNUNET_break (0);
     590            0 :     GNUNET_free (wire_method);
     591            0 :     return GNUNET_SYSERR;
     592            0 :   case GNUNET_DB_STATUS_SOFT_ERROR:
     593            0 :     GNUNET_free (wire_method);
     594            0 :     return GNUNET_NO;
     595            0 :   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
     596            0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     597              :                 "Failed to find wire fee for `%s' and method `%s' at %s in DB, accepting blindly that the fee is %s\n",
     598              :                 TALER_B2S (&keys->master_pub),
     599              :                 wire_method,
     600              :                 GNUNET_TIME_timestamp2s (execution_time),
     601              :                 TALER_amount2s (wire_fee));
     602            0 :     GNUNET_free (wire_method);
     603            0 :     return GNUNET_OK;
     604            8 :   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
     605            8 :     break;
     606              :   }
     607            8 :   if ( (GNUNET_OK !=
     608            8 :         TALER_amount_cmp_currency (&fees.wire,
     609            8 :                                    wire_fee)) ||
     610            8 :        (0 > TALER_amount_cmp (&fees.wire,
     611              :                               wire_fee)) )
     612              :   {
     613            0 :     GNUNET_break_op (0);
     614            0 :     GNUNET_free (wire_method);
     615            0 :     return GNUNET_SYSERR;   /* expected_fee >= wire_fee */
     616              :   }
     617            8 :   GNUNET_free (wire_method);
     618            8 :   return GNUNET_OK;
     619              : }
     620              : 
     621              : 
     622              : /**
     623              :  * Closure for #check_transfer()
     624              :  */
     625              : struct CheckTransferContext
     626              : {
     627              : 
     628              :   /**
     629              :    * Pointer to the detail that we are currently
     630              :    * checking in #check_transfer().
     631              :    */
     632              :   const struct TALER_TrackTransferDetails *current_detail;
     633              : 
     634              :   /**
     635              :    * Which transaction detail are we currently looking at?
     636              :    */
     637              :   unsigned int current_offset;
     638              : 
     639              :   /**
     640              :    * #GNUNET_NO if we did not find a matching coin.
     641              :    * #GNUNET_SYSERR if we found a matching coin, but the amounts do not match.
     642              :    * #GNUNET_OK if we did find a matching coin.
     643              :    */
     644              :   enum GNUNET_GenericReturnValue check_transfer_result;
     645              : 
     646              :   /**
     647              :    * Set to error code, if any.
     648              :    */
     649              :   enum TALER_ErrorCode ec;
     650              : 
     651              :   /**
     652              :    * Set to true if @e ec indicates a permanent failure.
     653              :    */
     654              :   bool failure;
     655              : };
     656              : 
     657              : 
     658              : /**
     659              :  * This function checks that the information about the coin which
     660              :  * was paid back by _this_ wire transfer matches what _we_ (the merchant)
     661              :  * knew about this coin.
     662              :  *
     663              :  * @param cls closure with our `struct CheckTransferContext  *`
     664              :  * @param exchange_url URL of the exchange that issued @a coin_pub
     665              :  * @param amount_with_fee amount the exchange will transfer for this coin
     666              :  * @param deposit_fee fee the exchange will charge for this coin
     667              :  * @param refund_fee fee the exchange will charge for refunding this coin
     668              :  * @param wire_fee paid wire fee
     669              :  * @param h_wire hash of merchant's wire details
     670              :  * @param deposit_timestamp when did the exchange receive the deposit
     671              :  * @param refund_deadline until when are refunds allowed
     672              :  * @param exchange_sig signature by the exchange
     673              :  * @param exchange_pub exchange signing key used for @a exchange_sig
     674              :  */
     675              : static void
     676            8 : check_transfer (void *cls,
     677              :                 const char *exchange_url,
     678              :                 const struct TALER_Amount *amount_with_fee,
     679              :                 const struct TALER_Amount *deposit_fee,
     680              :                 const struct TALER_Amount *refund_fee,
     681              :                 const struct TALER_Amount *wire_fee,
     682              :                 const struct TALER_MerchantWireHashP *h_wire,
     683              :                 struct GNUNET_TIME_Timestamp deposit_timestamp,
     684              :                 struct GNUNET_TIME_Timestamp refund_deadline,
     685              :                 const struct TALER_ExchangeSignatureP *exchange_sig,
     686              :                 const struct TALER_ExchangePublicKeyP *exchange_pub)
     687              : {
     688            8 :   struct CheckTransferContext *ctc = cls;
     689            8 :   const struct TALER_TrackTransferDetails *ttd = ctc->current_detail;
     690              : 
     691            8 :   if (GNUNET_SYSERR == ctc->check_transfer_result)
     692              :   {
     693            0 :     GNUNET_break (0);
     694            0 :     return;   /* already had a serious issue; odd that we're called more than once as well... */
     695              :   }
     696            8 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     697              :               "Checking coin with value %s\n",
     698              :               TALER_amount2s (amount_with_fee));
     699            8 :   if ( (GNUNET_OK !=
     700            8 :         TALER_amount_cmp_currency (amount_with_fee,
     701            8 :                                    &ttd->coin_value)) ||
     702            8 :        (0 != TALER_amount_cmp (amount_with_fee,
     703              :                                &ttd->coin_value)) )
     704              :   {
     705              :     /* Disagreement between the exchange and us about how much this
     706              :        coin is worth! */
     707            0 :     GNUNET_break_op (0);
     708            0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     709              :                 "Disagreement about coin value %s\n",
     710              :                 TALER_amount2s (amount_with_fee));
     711            0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     712              :                 "Exchange gave it a value of %s\n",
     713              :                 TALER_amount2s (&ttd->coin_value));
     714            0 :     ctc->check_transfer_result = GNUNET_SYSERR;
     715              :     /* Build the `TrackTransferConflictDetails` */
     716            0 :     ctc->ec = TALER_EC_MERCHANT_PRIVATE_POST_TRANSFERS_CONFLICTING_REPORTS;
     717            0 :     ctc->failure = true;
     718              :     /* FIXME-#9426: this should be reported to the auditor (once the auditor has an API for this) */
     719            0 :     return;
     720              :   }
     721            8 :   if ( (GNUNET_OK !=
     722            8 :         TALER_amount_cmp_currency (deposit_fee,
     723            8 :                                    &ttd->coin_fee)) ||
     724            8 :        (0 != TALER_amount_cmp (deposit_fee,
     725              :                                &ttd->coin_fee)) )
     726              :   {
     727              :     /* Disagreement between the exchange and us about how much this
     728              :        coin is worth! */
     729            0 :     GNUNET_break_op (0);
     730            0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     731              :                 "Expected fee is %s\n",
     732              :                 TALER_amount2s (&ttd->coin_fee));
     733            0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     734              :                 "Fee claimed by exchange is %s\n",
     735              :                 TALER_amount2s (deposit_fee));
     736            0 :     ctc->check_transfer_result = GNUNET_SYSERR;
     737              :     /* Build the `TrackTransferConflictDetails` */
     738            0 :     ctc->ec = TALER_EC_MERCHANT_PRIVATE_POST_TRANSFERS_CONFLICTING_REPORTS;
     739            0 :     ctc->failure = true;
     740              :     /* FIXME-#9426: this should be reported to the auditor (once the auditor has an API for this) */
     741            0 :     return;
     742              :   }
     743            8 :   ctc->check_transfer_result = GNUNET_OK;
     744              : }
     745              : 
     746              : 
     747              : /**
     748              :  * Function called with detailed wire transfer data, including all
     749              :  * of the coin transactions that were combined into the wire transfer.
     750              :  *
     751              :  * @param cls closure a `struct Inquiry *`
     752              :  * @param tgr response details
     753              :  */
     754              : static void
     755            8 : wire_transfer_cb (struct Inquiry *w,
     756              :                   const struct TALER_EXCHANGE_GetTransfersResponse *tgr)
     757              : {
     758            8 :   struct Exchange *e = w->exchange;
     759            8 :   const struct TALER_EXCHANGE_TransferData *td = NULL;
     760              : 
     761            8 :   GNUNET_assert (e->exchange_inquiries > 0);
     762            8 :   e->exchange_inquiries--;
     763            8 :   w->wdh = NULL;
     764            8 :   if (EXCHANGE_INQUIRY_LIMIT - 1 == e->exchange_inquiries)
     765            0 :     launch_inquiries_at_exchange (e);
     766            8 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     767              :               "Got response code %u from exchange for GET /transfers/$WTID\n",
     768              :               tgr->hr.http_status);
     769            8 :   switch (tgr->hr.http_status)
     770              :   {
     771            8 :   case MHD_HTTP_OK:
     772            8 :     td = &tgr->details.ok.td;
     773            8 :     w->execution_time = td->execution_time;
     774            8 :     e->transfer_delay = GNUNET_TIME_UNIT_ZERO;
     775            8 :     break;
     776            0 :   case MHD_HTTP_BAD_REQUEST:
     777              :   case MHD_HTTP_FORBIDDEN:
     778              :   case MHD_HTTP_NOT_FOUND:
     779            0 :     found_problem = true;
     780            0 :     update_transaction_status (w,
     781            0 :                                GNUNET_TIME_UNIT_FOREVER_ABS,
     782            0 :                                tgr->hr.http_status,
     783            0 :                                tgr->hr.ec,
     784            0 :                                tgr->hr.hint,
     785              :                                false);
     786            0 :     end_inquiry (w);
     787            0 :     return;
     788            0 :   case MHD_HTTP_INTERNAL_SERVER_ERROR:
     789              :   case MHD_HTTP_BAD_GATEWAY:
     790              :   case MHD_HTTP_GATEWAY_TIMEOUT:
     791            0 :     e->transfer_delay = GNUNET_TIME_STD_BACKOFF (e->transfer_delay);
     792            0 :     update_transaction_status (w,
     793              :                                GNUNET_TIME_relative_to_absolute (
     794              :                                  e->transfer_delay),
     795            0 :                                tgr->hr.http_status,
     796            0 :                                tgr->hr.ec,
     797            0 :                                tgr->hr.hint,
     798              :                                true);
     799            0 :     end_inquiry (w);
     800            0 :     return;
     801            0 :   default:
     802            0 :     found_problem = true;
     803            0 :     e->transfer_delay = GNUNET_TIME_STD_BACKOFF (e->transfer_delay);
     804            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     805              :                 "Unexpected HTTP status %u\n",
     806              :                 tgr->hr.http_status);
     807            0 :     update_transaction_status (w,
     808              :                                GNUNET_TIME_relative_to_absolute (
     809              :                                  e->transfer_delay),
     810            0 :                                tgr->hr.http_status,
     811            0 :                                tgr->hr.ec,
     812            0 :                                tgr->hr.hint,
     813              :                                true);
     814            0 :     end_inquiry (w);
     815            0 :     return;
     816              :   }
     817            8 :   TALER_MERCHANTDB_preflight (pg);
     818              : 
     819              :   {
     820              :     enum GNUNET_DB_QueryStatus qs;
     821              : 
     822            8 :     qs = TALER_MERCHANTDB_set_instance (pg,
     823            8 :                                         w->instance_id);
     824            8 :     if (0 > qs)
     825              :     {
     826              :       /* Always report on DB error as well to enable diagnostics */
     827            0 :       GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
     828            0 :       end_inquiry (w);
     829            0 :       global_ret = EXIT_FAILURE;
     830            0 :       GNUNET_SCHEDULER_shutdown ();
     831            0 :       return;
     832              :     }
     833            8 :     qs = TALER_MERCHANTDB_insert_transfer_details (pg,
     834            8 :                                                    w->instance_id,
     835            8 :                                                    w->exchange->exchange_url,
     836              :                                                    w->payto_uri,
     837            8 :                                                    &w->wtid,
     838              :                                                    td,
     839              :                                                    NULL);
     840            8 :     if (0 > qs)
     841              :     {
     842              :       /* Always report on DB error as well to enable diagnostics */
     843            0 :       GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
     844            0 :       end_inquiry (w);
     845            0 :       global_ret = EXIT_FAILURE;
     846            0 :       GNUNET_SCHEDULER_shutdown ();
     847            0 :       return;
     848              :     }
     849              :     // FIXME: insert_transfer_details has more complex
     850              :     // error possibilities inside, expose them here
     851              :     // and persist them with the transaction status
     852              :     // if they arise (especially no_account, no_exchange, conflict)
     853              :     // -- not sure how no_instance could happen...
     854            8 :     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     855              :     {
     856            0 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     857              :                   "Transfer already known. Ignoring duplicate.\n");
     858            0 :       GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
     859              :                     TALER_MERCHANTDB_set_instance (
     860              :                       pg,
     861              :                       NULL));
     862            0 :       end_inquiry (w);
     863            0 :       return;
     864              :     }
     865              :   }
     866              : 
     867              :   {
     868            8 :     struct CheckTransferContext ctc = {
     869              :       .ec = TALER_EC_NONE,
     870              :       .failure = false
     871              :     };
     872              : 
     873           16 :     for (unsigned int i = 0; i<td->details_length; i++)
     874              :     {
     875            8 :       const struct TALER_TrackTransferDetails *ttd = &td->details[i];
     876              :       enum GNUNET_DB_QueryStatus qs;
     877              : 
     878            8 :       if (TALER_EC_NONE != ctc.ec)
     879            0 :         break; /* already encountered an error */
     880            8 :       ctc.current_offset = i;
     881            8 :       ctc.current_detail = ttd;
     882              :       /* Set the coin as "never seen" before. */
     883            8 :       ctc.check_transfer_result = GNUNET_NO;
     884            8 :       qs = TALER_MERCHANTDB_iterate_deposits_by_contract_and_coin (
     885              :         pg,
     886            8 :         w->instance_id,
     887              :         &ttd->h_contract_terms,
     888              :         &ttd->coin_pub,
     889              :         &check_transfer,
     890              :         &ctc);
     891            8 :       switch (qs)
     892              :       {
     893            0 :       case GNUNET_DB_STATUS_SOFT_ERROR:
     894            0 :         GNUNET_break (0);
     895            0 :         ctc.ec = TALER_EC_GENERIC_DB_FETCH_FAILED;
     896            0 :         break;
     897            0 :       case GNUNET_DB_STATUS_HARD_ERROR:
     898            0 :         GNUNET_break (0);
     899            0 :         ctc.ec = TALER_EC_GENERIC_DB_FETCH_FAILED;
     900            0 :         break;
     901            0 :       case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
     902              :         /* The exchange says we made this deposit, but WE do not
     903              :            recall making it (corrupted / unreliable database?)!
     904              :            Well, let's say thanks and accept the money! */
     905            0 :         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     906              :                     "Failed to find payment data in DB\n");
     907            0 :         ctc.check_transfer_result = GNUNET_OK;
     908            0 :         break;
     909            8 :       case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
     910            8 :         break;
     911              :       }
     912            8 :       switch (ctc.check_transfer_result)
     913              :       {
     914            0 :       case GNUNET_NO:
     915              :         /* Internal error: how can we have called #check_transfer()
     916              :            but still have no result? */
     917            0 :         GNUNET_break (0);
     918            0 :         ctc.ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
     919            0 :         GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
     920              :                       TALER_MERCHANTDB_set_instance (
     921              :                         pg,
     922              :                         NULL));
     923            0 :         end_inquiry (w);
     924            0 :         return;
     925            0 :       case GNUNET_SYSERR:
     926              :         /* #check_transfer() failed, report conflict! */
     927            0 :         GNUNET_break_op (0);
     928            0 :         GNUNET_assert (TALER_EC_NONE != ctc.ec);
     929            0 :         break;
     930            8 :       case GNUNET_OK:
     931            8 :         break;
     932              :       }
     933              :     }
     934            8 :     if (TALER_EC_NONE != ctc.ec)
     935              :     {
     936            0 :       update_transaction_status (
     937              :         w,
     938            0 :         ctc.failure
     939              :         ? GNUNET_TIME_UNIT_FOREVER_ABS
     940            0 :         : GNUNET_TIME_relative_to_absolute (
     941              :           GNUNET_TIME_UNIT_MINUTES),
     942              :         MHD_HTTP_OK,
     943              :         ctc.ec,
     944              :         NULL /* no hint */,
     945            0 :         ! ctc.failure);
     946            0 :       GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
     947              :                     TALER_MERCHANTDB_set_instance (
     948              :                       pg,
     949              :                       NULL));
     950            0 :       end_inquiry (w);
     951            0 :       return;
     952              :     }
     953              :   }
     954              : 
     955            8 :   if (GNUNET_SYSERR ==
     956            8 :       check_wire_fee (w,
     957              :                       td->execution_time,
     958              :                       &td->wire_fee))
     959              :   {
     960            0 :     GNUNET_break_op (0);
     961            0 :     update_transaction_status (w,
     962            0 :                                GNUNET_TIME_UNIT_FOREVER_ABS,
     963              :                                MHD_HTTP_OK,
     964              :                                TALER_EC_MERCHANT_PRIVATE_POST_TRANSFERS_BAD_WIRE_FEE,
     965              :                                TALER_amount2s (&td->wire_fee),
     966              :                                false);
     967            0 :     GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
     968              :                   TALER_MERCHANTDB_set_instance (
     969              :                     pg,
     970              :                     NULL));
     971            0 :     end_inquiry (w);
     972            0 :     return;
     973              :   }
     974              : 
     975              :   {
     976              :     enum GNUNET_DB_QueryStatus qs;
     977              : 
     978            8 :     qs = TALER_MERCHANTDB_update_to_expected_transfer_finalized (pg,
     979            8 :                                                                  w->exchange->exchange_url,
     980            8 :                                                                  &w->wtid,
     981              :                                                                  &td->h_details,
     982              :                                                                  &td->total_amount,
     983              :                                                                  &td->wire_fee,
     984              :                                                                  &td->exchange_pub,
     985              :                                                                  &td->exchange_sig,
     986              :                                                                  td->exchange_payto_uri);
     987            8 :     if (qs < 0)
     988              :     {
     989            0 :       GNUNET_break (0);
     990            0 :       global_ret = EXIT_FAILURE;
     991            0 :       GNUNET_SCHEDULER_shutdown ();
     992            0 :       return;
     993              :     }
     994              :   }
     995            8 :   GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
     996              :                 TALER_MERCHANTDB_set_instance (
     997              :                   pg,
     998              :                   NULL));
     999            8 :   end_inquiry (w);
    1000              : }
    1001              : 
    1002              : 
    1003              : /**
    1004              :  * Initiate download from an exchange for a given inquiry.
    1005              :  *
    1006              :  * @param cls a `struct Inquiry *`
    1007              :  */
    1008              : static void
    1009            8 : exchange_request (void *cls)
    1010              : {
    1011            8 :   struct Inquiry *w = cls;
    1012            8 :   struct Exchange *e = w->exchange;
    1013              : 
    1014            8 :   w->task = NULL;
    1015            8 :   if (NULL == e->keys)
    1016            0 :     return;
    1017           16 :   w->wdh = TALER_EXCHANGE_get_transfers_create (
    1018              :     ctx,
    1019            8 :     e->exchange_url,
    1020              :     e->keys,
    1021            8 :     &w->wtid);
    1022            8 :   if (NULL == w->wdh)
    1023              :   {
    1024            0 :     GNUNET_break (0);
    1025            0 :     e->exchange_inquiries--;
    1026            0 :     e->transfer_delay = GNUNET_TIME_STD_BACKOFF (e->transfer_delay);
    1027            0 :     update_transaction_status (w,
    1028              :                                GNUNET_TIME_relative_to_absolute (
    1029              :                                  e->transfer_delay),
    1030              :                                0 /* failed to begin */,
    1031              :                                TALER_EC_MERCHANT_EXCHANGE_TRANSFERS_TRANSIENT_FAILURE,
    1032              :                                "Failed to initiate GET request at exchange",
    1033              :                                true);
    1034            0 :     end_inquiry (w);
    1035            0 :     return;
    1036              :   }
    1037            8 :   GNUNET_assert (TALER_EC_NONE ==
    1038              :                  TALER_EXCHANGE_get_transfers_start (w->wdh,
    1039              :                                                      &wire_transfer_cb,
    1040              :                                                      w));
    1041              : 
    1042              :   /* Wait at least 1m for the network transfer */
    1043            8 :   update_transaction_status (w,
    1044              :                              GNUNET_TIME_relative_to_absolute (
    1045              :                                GNUNET_TIME_UNIT_MINUTES),
    1046              :                              0 /* timeout */,
    1047              :                              TALER_EC_MERCHANT_EXCHANGE_TRANSFERS_AWAITING_LIST,
    1048              :                              "Initiated GET with exchange",
    1049              :                              true);
    1050              : }
    1051              : 
    1052              : 
    1053              : /**
    1054              :  * Function called with information about a transfer we
    1055              :  * should ask the exchange about.
    1056              :  *
    1057              :  * @param cls closure (NULL)
    1058              :  * @param rowid row of the transfer in the merchant database
    1059              :  * @param instance_id instance that received the transfer
    1060              :  * @param exchange_url base URL of the exchange that initiated the transfer
    1061              :  * @param payto_uri account of the merchant that received the transfer
    1062              :  * @param wtid wire transfer subject identifying the aggregation
    1063              :  * @param next_attempt when should we next try to interact with the exchange
    1064              :  */
    1065              : static void
    1066            8 : start_inquiry (
    1067              :   void *cls,
    1068              :   uint64_t rowid,
    1069              :   const char *instance_id,
    1070              :   const char *exchange_url,
    1071              :   struct TALER_FullPayto payto_uri,
    1072              :   const struct TALER_WireTransferIdentifierRawP *wtid,
    1073              :   struct GNUNET_TIME_Absolute next_attempt)
    1074              : {
    1075              :   struct Exchange *e;
    1076              :   struct Inquiry *w;
    1077              : 
    1078              :   (void) cls;
    1079            8 :   if (GNUNET_TIME_absolute_is_future (next_attempt))
    1080              :   {
    1081            0 :     if (NULL == task)
    1082            0 :       task = GNUNET_SCHEDULER_add_at (next_attempt,
    1083              :                                       &find_work,
    1084              :                                       NULL);
    1085            0 :     return;
    1086              :   }
    1087            8 :   active_inquiries++;
    1088              : 
    1089            8 :   e = find_exchange (exchange_url);
    1090            8 :   for (w = e->w_head; NULL != w; w = w->next)
    1091              :   {
    1092            0 :     if (0 == GNUNET_memcmp (&w->wtid,
    1093              :                             wtid))
    1094              :     {
    1095            0 :       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    1096              :                   "Already processing inquiry. Aborting ongoing inquiry\n");
    1097            0 :       end_inquiry (w);
    1098            0 :       break;
    1099              :     }
    1100              :   }
    1101              : 
    1102            8 :   w = GNUNET_new (struct Inquiry);
    1103            8 :   w->payto_uri.full_payto = GNUNET_strdup (payto_uri.full_payto);
    1104            8 :   w->instance_id = GNUNET_strdup (instance_id);
    1105            8 :   w->rowid = rowid;
    1106            8 :   w->wtid = *wtid;
    1107            8 :   GNUNET_CONTAINER_DLL_insert (e->w_head,
    1108              :                                e->w_tail,
    1109              :                                w);
    1110            8 :   w->exchange = e;
    1111            8 :   if (NULL != w->exchange->keys)
    1112              :   {
    1113            8 :     e->exchange_inquiries++;
    1114            8 :     w->task = GNUNET_SCHEDULER_add_now (&exchange_request,
    1115              :                                         w);
    1116              :   }
    1117              :   /* Wait at least 1 minute for /keys */
    1118            8 :   update_transaction_status (w,
    1119              :                              GNUNET_TIME_relative_to_absolute (
    1120              :                                GNUNET_TIME_UNIT_MINUTES),
    1121              :                              0 /* timeout */,
    1122              :                              TALER_EC_MERCHANT_EXCHANGE_TRANSFERS_AWAITING_KEYS,
    1123              :                              exchange_url,
    1124              :                              true);
    1125              : }
    1126              : 
    1127              : 
    1128              : static void
    1129           12 : find_work (void *cls)
    1130              : {
    1131              :   enum GNUNET_DB_QueryStatus qs;
    1132              :   int limit;
    1133              : 
    1134              :   (void) cls;
    1135           12 :   task = NULL;
    1136           12 :   GNUNET_assert (OPEN_INQUIRY_LIMIT >= active_inquiries);
    1137           12 :   limit = OPEN_INQUIRY_LIMIT - active_inquiries;
    1138           12 :   if (0 == limit)
    1139              :   {
    1140            0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1141              :                 "Not looking for work: at limit\n");
    1142            0 :     at_limit = true;
    1143            0 :     return;
    1144              :   }
    1145           12 :   at_limit = false;
    1146           12 :   qs = TALER_MERCHANTDB_iterate_open_expected_transfers (pg,
    1147              :                                                          limit,
    1148              :                                                          &start_inquiry,
    1149              :                                                          NULL);
    1150           12 :   if (qs < 0)
    1151              :   {
    1152            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1153              :                 "Failed to obtain open transfers from database\n");
    1154            0 :     GNUNET_SCHEDULER_shutdown ();
    1155            0 :     return;
    1156              :   }
    1157           12 :   if (qs >= limit)
    1158              :   {
    1159              :     /* DB limited response, re-trigger DB interaction
    1160              :        the moment we significantly fall below the
    1161              :        limit */
    1162            0 :     at_limit = true;
    1163              :   }
    1164           12 :   if (0 == active_inquiries)
    1165              :   {
    1166            4 :     if (test_mode)
    1167              :     {
    1168            4 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1169              :                   "No more open inquiries and in test mode. Existing.\n");
    1170            4 :       GNUNET_SCHEDULER_shutdown ();
    1171            4 :       return;
    1172              :     }
    1173            0 :     GNUNET_log (
    1174              :       GNUNET_ERROR_TYPE_INFO,
    1175              :       "No open inquiries found, waiting for notification to resume\n");
    1176              :   }
    1177              : }
    1178              : 
    1179              : 
    1180              : /**
    1181              :  * Function called when transfers are added to the merchant database.  We look
    1182              :  * for more work.
    1183              :  *
    1184              :  * @param cls closure (NULL)
    1185              :  * @param extra additional event data provided
    1186              :  * @param extra_size number of bytes in @a extra
    1187              :  */
    1188              : static void
    1189            0 : transfer_added (void *cls,
    1190              :                 const void *extra,
    1191              :                 size_t extra_size)
    1192              : {
    1193              :   (void) cls;
    1194              :   (void) extra;
    1195              :   (void) extra_size;
    1196            0 :   if (active_inquiries > OPEN_INQUIRY_LIMIT / 2)
    1197              :   {
    1198              :     /* Trigger DB only once we are substantially below the limit */
    1199            0 :     at_limit = true;
    1200            0 :     return;
    1201              :   }
    1202            0 :   if (NULL != task)
    1203            0 :     return;
    1204            0 :   task = GNUNET_SCHEDULER_add_now (&find_work,
    1205              :                                    NULL);
    1206              : }
    1207              : 
    1208              : 
    1209              : /**
    1210              :  * Function called when keys were changed in the
    1211              :  * merchant database. Updates ours.
    1212              :  *
    1213              :  * @param cls closure (NULL)
    1214              :  * @param extra additional event data provided
    1215              :  * @param extra_size number of bytes in @a extra
    1216              :  */
    1217              : static void
    1218            0 : keys_changed (void *cls,
    1219              :               const void *extra,
    1220              :               size_t extra_size)
    1221              : {
    1222            0 :   const char *url = extra;
    1223              :   struct Exchange *e;
    1224              : 
    1225              :   (void) cls;
    1226            0 :   if ( (NULL == extra) ||
    1227              :        (0 == extra_size) )
    1228              :   {
    1229            0 :     GNUNET_break (0);
    1230            0 :     return;
    1231              :   }
    1232            0 :   if ('\0' != url[extra_size - 1])
    1233              :   {
    1234            0 :     GNUNET_break (0);
    1235            0 :     return;
    1236              :   }
    1237            0 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1238              :               "Received keys change notification: reload `%s'\n",
    1239              :               url);
    1240            0 :   e = find_exchange (url);
    1241            0 :   sync_keys (e);
    1242              : }
    1243              : 
    1244              : 
    1245              : /**
    1246              :  * First task.
    1247              :  *
    1248              :  * @param cls closure, NULL
    1249              :  * @param args remaining command-line arguments
    1250              :  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
    1251              :  * @param c configuration
    1252              :  */
    1253              : static void
    1254           12 : run (void *cls,
    1255              :      char *const *args,
    1256              :      const char *cfgfile,
    1257              :      const struct GNUNET_CONFIGURATION_Handle *c)
    1258              : {
    1259              :   (void) args;
    1260              :   (void) cfgfile;
    1261              : 
    1262           12 :   cfg = c;
    1263           12 :   TALER_EXCHANGE_setup (enable_h3
    1264           12 :                         ? TALER_EXCHANGE_GO_ENABLE_HTTP3
    1265              :                         : TALER_EXCHANGE_GO_FORCE_HTTP1_1);
    1266           12 :   GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
    1267              :                                  NULL);
    1268           12 :   ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
    1269              :                           &rc);
    1270           12 :   rc = GNUNET_CURL_gnunet_rc_create (ctx);
    1271           12 :   if (NULL == ctx)
    1272              :   {
    1273            0 :     GNUNET_break (0);
    1274            0 :     GNUNET_SCHEDULER_shutdown ();
    1275            0 :     global_ret = EXIT_FAILURE;
    1276            0 :     return;
    1277              :   }
    1278           12 :   if (NULL ==
    1279           12 :       (pg = TALER_MERCHANTDB_connect (cfg)))
    1280              :   {
    1281            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1282              :                 "Failed to initialize DB subsystem. Consider running taler-merchant-dbconfig!\n");
    1283            0 :     GNUNET_SCHEDULER_shutdown ();
    1284            0 :     global_ret = EXIT_FAILURE;
    1285            0 :     return;
    1286              :   }
    1287              :   {
    1288           12 :     struct GNUNET_DB_EventHeaderP es = {
    1289           12 :       .size = htons (sizeof (es)),
    1290           12 :       .type = htons (TALER_DBEVENT_MERCHANT_WIRE_TRANSFER_EXPECTED)
    1291              :     };
    1292              : 
    1293           24 :     eh = TALER_MERCHANTDB_event_listen (pg,
    1294              :                                         &es,
    1295           12 :                                         GNUNET_TIME_UNIT_FOREVER_REL,
    1296              :                                         &transfer_added,
    1297              :                                         NULL);
    1298              :   }
    1299              :   {
    1300           12 :     struct GNUNET_DB_EventHeaderP es = {
    1301           12 :       .size = htons (sizeof (es)),
    1302           12 :       .type = htons (TALER_DBEVENT_MERCHANT_EXCHANGE_KEYS)
    1303              :     };
    1304              : 
    1305              :     eh_keys
    1306           24 :       = TALER_MERCHANTDB_event_listen (pg,
    1307              :                                        &es,
    1308           12 :                                        GNUNET_TIME_UNIT_FOREVER_REL,
    1309              :                                        &keys_changed,
    1310              :                                        NULL);
    1311              :   }
    1312              : 
    1313           12 :   GNUNET_assert (NULL == task);
    1314           12 :   task = GNUNET_SCHEDULER_add_now (&find_work,
    1315              :                                    NULL);
    1316              : }
    1317              : 
    1318              : 
    1319              : /**
    1320              :  * The main function of taler-merchant-reconciliation
    1321              :  *
    1322              :  * @param argc number of arguments from the command line
    1323              :  * @param argv command line arguments
    1324              :  * @return 0 ok, 1 on error
    1325              :  */
    1326              : int
    1327           12 : main (int argc,
    1328              :       char *const *argv)
    1329              : {
    1330           12 :   struct GNUNET_GETOPT_CommandLineOption options[] = {
    1331           12 :     GNUNET_GETOPT_option_flag ('3',
    1332              :                                "http3",
    1333              :                                "enable support for HTTP/2 and HTTP/3",
    1334              :                                &enable_h3),
    1335           12 :     GNUNET_GETOPT_option_timetravel ('T',
    1336              :                                      "timetravel"),
    1337           12 :     GNUNET_GETOPT_option_flag ('t',
    1338              :                                "test",
    1339              :                                "run in test mode and exit when idle",
    1340              :                                &test_mode),
    1341           12 :     GNUNET_GETOPT_option_version (VERSION),
    1342              :     GNUNET_GETOPT_OPTION_END
    1343              :   };
    1344              :   enum GNUNET_GenericReturnValue ret;
    1345              : 
    1346           12 :   ret = GNUNET_PROGRAM_run (
    1347              :     TALER_MERCHANT_project_data (),
    1348              :     argc, argv,
    1349              :     "taler-merchant-reconciliation",
    1350              :     gettext_noop (
    1351              :       "background process that reconciles bank transfers with orders by asking the exchange"),
    1352              :     options,
    1353              :     &run, NULL);
    1354           12 :   if (GNUNET_SYSERR == ret)
    1355            0 :     return EXIT_NOTCONFIGURED;
    1356           12 :   if (GNUNET_NO == ret)
    1357            0 :     return EXIT_SUCCESS;
    1358           12 :   if ( (found_problem) &&
    1359            0 :        (0 == global_ret) )
    1360            0 :     global_ret = 7;
    1361           12 :   return global_ret;
    1362              : }
    1363              : 
    1364              : 
    1365              : /* end of taler-merchant-reconciliation.c */
        

Generated by: LCOV version 2.0-1