LCOV - code coverage report
Current view: top level - backend - taler-merchant-reconciliation.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 218 401 54.4 %
Date: 2025-06-23 16:22:09 Functions: 14 16 87.5 %

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

Generated by: LCOV version 1.16