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

            Line data    Source code
       1              : /*
       2              :   This file is part of TALER
       3              :   Copyright (C) 2023-2025 Taler Systems SA
       4              : 
       5              :   TALER is free software; you can redistribute it and/or modify it under the
       6              :   terms of the GNU Affero General Public License as published by the Free Software
       7              :   Foundation; either version 3, or (at your option) any later version.
       8              : 
       9              :   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
      10              :   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
      11              :   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
      12              : 
      13              :   You should have received a copy of the GNU Affero General Public License along with
      14              :   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
      15              : */
      16              : /**
      17              :  * @file 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 2.0-1