LCOV - code coverage report
Current view: top level - backend - taler-merchant-reconciliation.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 213 398 53.5 %
Date: 2025-08-28 06:06:54 Functions: 14 16 87.5 %

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

Generated by: LCOV version 1.16