LCOV - code coverage report
Current view: top level - backend - taler-merchant-depositcheck.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 134 347 38.6 %
Date: 2025-06-23 16:22:09 Functions: 9 13 69.2 %

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   Copyright (C) 2024 Taler Systems SA
       4             : 
       5             :   TALER is free software; you can redistribute it and/or modify it under the
       6             :   terms of the GNU Affero General Public License as published by the Free Software
       7             :   Foundation; either version 3, or (at your option) any later version.
       8             : 
       9             :   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
      10             :   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
      11             :   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
      12             : 
      13             :   You should have received a copy of the GNU Affero General Public License along with
      14             :   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
      15             : */
      16             : /**
      17             :  * @file taler-merchant-depositcheck.c
      18             :  * @brief Process that inquires with the exchange for deposits that should have been wired
      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_merchant_util.h"
      27             : #include "taler_merchantdb_lib.h"
      28             : #include "taler_merchantdb_plugin.h"
      29             : #include <taler/taler_dbevents.h>
      30             : 
      31             : /**
      32             :  * How many requests do we make at most in parallel to the same exchange?
      33             :  */
      34             : #define CONCURRENCY_LIMIT 32
      35             : 
      36             : /**
      37             :  * How long do we not try a deposit check if the deposit
      38             :  * was put on hold due to a KYC/AML block?
      39             :  */
      40             : #define KYC_RETRY_DELAY GNUNET_TIME_UNIT_HOURS
      41             : 
      42             : /**
      43             :  * Information we keep per exchange.
      44             :  */
      45             : struct Child
      46             : {
      47             : 
      48             :   /**
      49             :    * Kept in a DLL.
      50             :    */
      51             :   struct Child *next;
      52             : 
      53             :   /**
      54             :    * Kept in a DLL.
      55             :    */
      56             :   struct Child *prev;
      57             : 
      58             :   /**
      59             :    * The child process.
      60             :    */
      61             :   struct GNUNET_OS_Process *process;
      62             : 
      63             :   /**
      64             :    * Wait handle.
      65             :    */
      66             :   struct GNUNET_ChildWaitHandle *cwh;
      67             : 
      68             :   /**
      69             :    * Which exchange is this state for?
      70             :    */
      71             :   char *base_url;
      72             : 
      73             :   /**
      74             :    * Task to restart the child.
      75             :    */
      76             :   struct GNUNET_SCHEDULER_Task *rt;
      77             : 
      78             :   /**
      79             :    * When should the child be restarted at the earliest?
      80             :    */
      81             :   struct GNUNET_TIME_Absolute next_start;
      82             : 
      83             :   /**
      84             :    * Current minimum delay between restarts, grows
      85             :    * exponentially if child exits before this time.
      86             :    */
      87             :   struct GNUNET_TIME_Relative rd;
      88             : 
      89             : };
      90             : 
      91             : 
      92             : /**
      93             :  * Information we keep per exchange interaction.
      94             :  */
      95             : struct ExchangeInteraction
      96             : {
      97             :   /**
      98             :    * Kept in a DLL.
      99             :    */
     100             :   struct ExchangeInteraction *next;
     101             : 
     102             :   /**
     103             :    * Kept in a DLL.
     104             :    */
     105             :   struct ExchangeInteraction *prev;
     106             : 
     107             :   /**
     108             :    * Handle for exchange interaction.
     109             :    */
     110             :   struct TALER_EXCHANGE_DepositGetHandle *dgh;
     111             : 
     112             :   /**
     113             :    * Wire deadline for the deposit.
     114             :    */
     115             :   struct GNUNET_TIME_Absolute wire_deadline;
     116             : 
     117             :   /**
     118             :    * Current value for the retry backoff
     119             :    */
     120             :   struct GNUNET_TIME_Relative retry_backoff;
     121             : 
     122             :   /**
     123             :    * Target account hash of the deposit.
     124             :    */
     125             :   struct TALER_MerchantWireHashP h_wire;
     126             : 
     127             :   /**
     128             :    * Deposited amount.
     129             :    */
     130             :   struct TALER_Amount amount_with_fee;
     131             : 
     132             :   /**
     133             :    * Deposit fee paid.
     134             :    */
     135             :   struct TALER_Amount deposit_fee;
     136             : 
     137             :   /**
     138             :    * Public key of the deposited coin.
     139             :    */
     140             :   struct TALER_CoinSpendPublicKeyP coin_pub;
     141             : 
     142             :   /**
     143             :    * Hash over the @e contract_terms.
     144             :    */
     145             :   struct TALER_PrivateContractHashP h_contract_terms;
     146             : 
     147             :   /**
     148             :    * Merchant instance's private key.
     149             :    */
     150             :   struct TALER_MerchantPrivateKeyP merchant_priv;
     151             : 
     152             :   /**
     153             :    * Serial number of the row in the deposits table
     154             :    * that we are processing.
     155             :    */
     156             :   uint64_t deposit_serial;
     157             : 
     158             :   /**
     159             :    * The instance the deposit belongs to.
     160             :    */
     161             :   char *instance_id;
     162             : 
     163             : };
     164             : 
     165             : 
     166             : /**
     167             :  * Head of list of children we forked.
     168             :  */
     169             : static struct Child *c_head;
     170             : 
     171             : /**
     172             :  * Tail of list of children we forked.
     173             :  */
     174             : static struct Child *c_tail;
     175             : 
     176             : /**
     177             :  * Key material of the exchange.
     178             :  */
     179             : static struct TALER_EXCHANGE_Keys *keys;
     180             : 
     181             : /**
     182             :  * Head of list of active exchange interactions.
     183             :  */
     184             : static struct ExchangeInteraction *w_head;
     185             : 
     186             : /**
     187             :  * Tail of list of active exchange interactions.
     188             :  */
     189             : static struct ExchangeInteraction *w_tail;
     190             : 
     191             : /**
     192             :  * Number of active entries in the @e w_head list.
     193             :  */
     194             : static uint64_t w_count;
     195             : 
     196             : /**
     197             :  * Notification handler from database on new work.
     198             :  */
     199             : static struct GNUNET_DB_EventHandler *eh;
     200             : 
     201             : /**
     202             :  * Notification handler from database on new keys.
     203             :  */
     204             : static struct GNUNET_DB_EventHandler *keys_eh;
     205             : 
     206             : /**
     207             :  * The merchant's configuration.
     208             :  */
     209             : static const struct GNUNET_CONFIGURATION_Handle *cfg;
     210             : 
     211             : /**
     212             :  * Name of the configuration file we use.
     213             :  */
     214             : static char *cfg_filename;
     215             : 
     216             : /**
     217             :  * Our database plugin.
     218             :  */
     219             : static struct TALER_MERCHANTDB_Plugin *db_plugin;
     220             : 
     221             : /**
     222             :  * Next wire deadline that @e task is scheduled for.
     223             :  */
     224             : static struct GNUNET_TIME_Absolute next_deadline;
     225             : 
     226             : /**
     227             :  * Next task to run, if any.
     228             :  */
     229             : static struct GNUNET_SCHEDULER_Task *task;
     230             : 
     231             : /**
     232             :  * Handle to the context for interacting with the exchange.
     233             :  */
     234             : static struct GNUNET_CURL_Context *ctx;
     235             : 
     236             : /**
     237             :  * Scheduler context for running the @e ctx.
     238             :  */
     239             : static struct GNUNET_CURL_RescheduleContext *rc;
     240             : 
     241             : /**
     242             :  * Which exchange are we monitoring? NULL if we
     243             :  * are the parent of the workers.
     244             :  */
     245             : static char *exchange_url;
     246             : 
     247             : /**
     248             :  * Value to return from main(). 0 on success, non-zero on errors.
     249             :  */
     250             : static int global_ret;
     251             : 
     252             : /**
     253             :  * #GNUNET_YES if we are in test mode and should exit when idle.
     254             :  */
     255             : static int test_mode;
     256             : 
     257             : 
     258             : /**
     259             :  * We're being aborted with CTRL-C (or SIGTERM). Shut down.
     260             :  *
     261             :  * @param cls closure
     262             :  */
     263             : static void
     264          12 : shutdown_task (void *cls)
     265             : {
     266             :   struct Child *c;
     267             :   struct ExchangeInteraction *w;
     268             : 
     269             :   (void) cls;
     270          12 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     271             :               "Running shutdown\n");
     272          12 :   if (NULL != eh)
     273             :   {
     274           8 :     db_plugin->event_listen_cancel (eh);
     275           8 :     eh = NULL;
     276             :   }
     277          12 :   if (NULL != keys_eh)
     278             :   {
     279           8 :     db_plugin->event_listen_cancel (keys_eh);
     280           8 :     keys_eh = NULL;
     281             :   }
     282          12 :   if (NULL != task)
     283             :   {
     284           0 :     GNUNET_SCHEDULER_cancel (task);
     285           0 :     task = NULL;
     286             :   }
     287          12 :   while (NULL != (w = w_head))
     288             :   {
     289           0 :     GNUNET_CONTAINER_DLL_remove (w_head,
     290             :                                  w_tail,
     291             :                                  w);
     292           0 :     if (NULL != w->dgh)
     293             :     {
     294           0 :       TALER_EXCHANGE_deposits_get_cancel (w->dgh);
     295           0 :       w->dgh = NULL;
     296             :     }
     297           0 :     w_count--;
     298           0 :     GNUNET_free (w->instance_id);
     299           0 :     GNUNET_free (w);
     300             :   }
     301          20 :   while (NULL != (c = c_head))
     302             :   {
     303           8 :     GNUNET_CONTAINER_DLL_remove (c_head,
     304             :                                  c_tail,
     305             :                                  c);
     306           8 :     if (NULL != c->rt)
     307             :     {
     308           0 :       GNUNET_SCHEDULER_cancel (c->rt);
     309           0 :       c->rt = NULL;
     310             :     }
     311           8 :     if (NULL != c->cwh)
     312             :     {
     313           0 :       GNUNET_wait_child_cancel (c->cwh);
     314           0 :       c->cwh = NULL;
     315             :     }
     316           8 :     if (NULL != c->process)
     317             :     {
     318           0 :       enum GNUNET_OS_ProcessStatusType type
     319             :         = GNUNET_OS_PROCESS_UNKNOWN;
     320           0 :       unsigned long code = 0;
     321             : 
     322           0 :       GNUNET_break (0 ==
     323             :                     GNUNET_OS_process_kill (c->process,
     324             :                                             SIGTERM));
     325           0 :       GNUNET_break (GNUNET_OK ==
     326             :                     GNUNET_OS_process_wait_status (c->process,
     327             :                                                    &type,
     328             :                                                    &code));
     329           0 :       if ( (GNUNET_OS_PROCESS_EXITED != type) ||
     330           0 :            (0 != code) )
     331           0 :         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     332             :                     "Process for exchange %s had trouble (%d/%d)\n",
     333             :                     c->base_url,
     334             :                     (int) type,
     335             :                     (int) code);
     336           0 :       GNUNET_OS_process_destroy (c->process);
     337             :     }
     338           8 :     GNUNET_free (c->base_url);
     339           8 :     GNUNET_free (c);
     340             :   }
     341          12 :   if (NULL != db_plugin)
     342             :   {
     343           8 :     db_plugin->rollback (db_plugin->cls); /* just in case */
     344           8 :     TALER_MERCHANTDB_plugin_unload (db_plugin);
     345           8 :     db_plugin = NULL;
     346             :   }
     347          12 :   cfg = NULL;
     348          12 :   if (NULL != ctx)
     349             :   {
     350           8 :     GNUNET_CURL_fini (ctx);
     351           8 :     ctx = NULL;
     352             :   }
     353          12 :   if (NULL != rc)
     354             :   {
     355           8 :     GNUNET_CURL_gnunet_rc_destroy (rc);
     356           8 :     rc = NULL;
     357             :   }
     358          12 : }
     359             : 
     360             : 
     361             : /**
     362             :  * Task to get more deposits to work on from the database.
     363             :  *
     364             :  * @param cls NULL
     365             :  */
     366             : static void
     367             : select_work (void *cls);
     368             : 
     369             : 
     370             : /**
     371             :  * Make sure to run the select_work() task at
     372             :  * the @a next_deadline.
     373             :  *
     374             :  * @param deadline time when work becomes ready
     375             :  */
     376             : static void
     377           0 : run_at (struct GNUNET_TIME_Absolute deadline)
     378             : {
     379           0 :   if ( (NULL != task) &&
     380           0 :        (GNUNET_TIME_absolute_cmp (deadline,
     381             :                                   >,
     382             :                                   next_deadline)) )
     383             :   {
     384           0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     385             :                 "Not scheduling for %s yet, already have earlier task pending\n",
     386             :                 GNUNET_TIME_absolute2s (deadline));
     387           0 :     return;
     388             :   }
     389           0 :   if (NULL == keys)
     390             :   {
     391           0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     392             :                 "Not scheduling for %s yet, no /keys available\n",
     393             :                 GNUNET_TIME_absolute2s (deadline));
     394           0 :     return; /* too early */
     395             :   }
     396           0 :   next_deadline = deadline;
     397           0 :   if (NULL != task)
     398           0 :     GNUNET_SCHEDULER_cancel (task);
     399           0 :   task = GNUNET_SCHEDULER_add_at (deadline,
     400             :                                   &select_work,
     401             :                                   NULL);
     402             : }
     403             : 
     404             : 
     405             : /**
     406             :  * Function called with detailed wire transfer data.
     407             :  *
     408             :  * @param cls closure with a `struct ExchangeInteraction *`
     409             :  * @param dr HTTP response data
     410             :  */
     411             : static void
     412           0 : deposit_get_cb (
     413             :   void *cls,
     414             :   const struct TALER_EXCHANGE_GetDepositResponse *dr)
     415             : {
     416           0 :   struct ExchangeInteraction *w = cls;
     417             :   struct GNUNET_TIME_Absolute future_retry;
     418             : 
     419           0 :   w->dgh = NULL;
     420             :   future_retry
     421           0 :     = GNUNET_TIME_relative_to_absolute (w->retry_backoff);
     422           0 :   switch (dr->hr.http_status)
     423             :   {
     424           0 :   case MHD_HTTP_OK:
     425             :     {
     426             :       enum GNUNET_DB_QueryStatus qs;
     427           0 :       bool cleared = false;
     428             : 
     429           0 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     430             :                   "Exchange returned wire transfer over %s for deposited coin %s\n",
     431             :                   TALER_amount2s (&dr->details.ok.coin_contribution),
     432             :                   TALER_B2S (&w->coin_pub));
     433           0 :       qs = db_plugin->insert_deposit_to_transfer (
     434           0 :         db_plugin->cls,
     435             :         w->deposit_serial,
     436             :         &dr->details.ok,
     437             :         &cleared);
     438           0 :       if (qs < 0)
     439             :       {
     440           0 :         GNUNET_break (0);
     441           0 :         GNUNET_SCHEDULER_shutdown ();
     442           0 :         return;
     443             :       }
     444           0 :       if (! cleared)
     445             :       {
     446           0 :         qs = db_plugin->update_deposit_confirmation_status (
     447           0 :           db_plugin->cls,
     448             :           w->deposit_serial,
     449             :           true, /* this failed, wire_pending remains true */
     450             :           GNUNET_TIME_absolute_to_timestamp (future_retry),
     451             :           w->retry_backoff,
     452             :           "wire transfer unknown");
     453             :       }
     454           0 :       if (qs < 0)
     455             :       {
     456           0 :         GNUNET_break (0);
     457           0 :         GNUNET_SCHEDULER_shutdown ();
     458           0 :         return;
     459             :       }
     460           0 :       break;
     461             :     }
     462           0 :   case MHD_HTTP_ACCEPTED:
     463             :     {
     464             :       /* got a 'preliminary' reply from the exchange,
     465             :          remember our target UUID */
     466             :       enum GNUNET_DB_QueryStatus qs;
     467             :       struct GNUNET_TIME_Timestamp now;
     468             : 
     469           0 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     470             :                   "Exchange returned KYC requirement (%d) for deposited coin %s\n",
     471             :                   dr->details.accepted.kyc_ok,
     472             :                   TALER_B2S (&w->coin_pub));
     473           0 :       now = GNUNET_TIME_timestamp_get ();
     474           0 :       qs = db_plugin->account_kyc_set_failed (
     475           0 :         db_plugin->cls,
     476           0 :         w->instance_id,
     477           0 :         &w->h_wire,
     478             :         exchange_url,
     479             :         now,
     480             :         MHD_HTTP_ACCEPTED,
     481           0 :         dr->details.accepted.kyc_ok);
     482           0 :       if (qs < 0)
     483             :       {
     484           0 :         GNUNET_break (0);
     485           0 :         GNUNET_SCHEDULER_shutdown ();
     486           0 :         return;
     487             :       }
     488           0 :       if (dr->details.accepted.kyc_ok)
     489             :       {
     490           0 :         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     491             :                     "Bumping wire transfer deadline in DB to %s as that is when we will retry\n",
     492             :                     GNUNET_TIME_absolute2s (future_retry));
     493           0 :         qs = db_plugin->update_deposit_confirmation_status (
     494           0 :           db_plugin->cls,
     495             :           w->deposit_serial,
     496             :           true, /* wire_pending is still true! */
     497             :           GNUNET_TIME_absolute_to_timestamp (future_retry),
     498             :           w->retry_backoff,
     499             :           "Exchange reported 202 Accepted but no KYC block");
     500           0 :         if (qs < 0)
     501             :         {
     502           0 :           GNUNET_break (0);
     503           0 :           GNUNET_SCHEDULER_shutdown ();
     504           0 :           return;
     505             :         }
     506             :       }
     507             :       else
     508             :       {
     509             :         future_retry
     510           0 :           = GNUNET_TIME_absolute_max (
     511             :               future_retry,
     512             :               GNUNET_TIME_relative_to_absolute (
     513             :                 KYC_RETRY_DELAY));
     514           0 :         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     515             :                     "Bumping wire transfer deadline in DB to %s as that is when we will retry\n",
     516             :                     GNUNET_TIME_absolute2s (future_retry));
     517           0 :         qs = db_plugin->update_deposit_confirmation_status (
     518           0 :           db_plugin->cls,
     519             :           w->deposit_serial,
     520             :           true,
     521             :           GNUNET_TIME_absolute_to_timestamp (future_retry),
     522             :           w->retry_backoff,
     523             :           "Exchange reported 202 Accepted due to KYC/AML block");
     524           0 :         if (qs < 0)
     525             :         {
     526           0 :           GNUNET_break (0);
     527           0 :           GNUNET_SCHEDULER_shutdown ();
     528           0 :           return;
     529             :         }
     530             :       }
     531           0 :       break;
     532             :     }
     533           0 :   default:
     534             :     {
     535             :       enum GNUNET_DB_QueryStatus qs;
     536             :       char *msg;
     537             : 
     538           0 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     539             :                   "Exchange %s returned tracking failure for deposited coin %s\n",
     540             :                   exchange_url,
     541             :                   TALER_B2S (&w->coin_pub));
     542           0 :       GNUNET_asprintf (&msg,
     543             :                        "Unexpected exchange status %u (#%d, %s)\n",
     544           0 :                        dr->hr.http_status,
     545           0 :                        (int) dr->hr.ec,
     546           0 :                        dr->hr.hint);
     547           0 :       qs = db_plugin->update_deposit_confirmation_status (
     548           0 :         db_plugin->cls,
     549             :         w->deposit_serial,
     550             :         true, /* this failed, wire_pending remains true */
     551             :         GNUNET_TIME_absolute_to_timestamp (future_retry),
     552             :         w->retry_backoff,
     553             :         msg);
     554           0 :       GNUNET_free (msg);
     555           0 :       if (qs < 0)
     556             :       {
     557           0 :         GNUNET_break (0);
     558           0 :         GNUNET_SCHEDULER_shutdown ();
     559           0 :         return;
     560             :       }
     561           0 :       return;
     562             :     }
     563             :   } /* end switch */
     564             : 
     565           0 :   GNUNET_CONTAINER_DLL_remove (w_head,
     566             :                                w_tail,
     567             :                                w);
     568           0 :   w_count--;
     569           0 :   GNUNET_free (w->instance_id);
     570           0 :   GNUNET_free (w);
     571           0 :   GNUNET_assert (NULL != keys);
     572           0 :   if ( (w_count < CONCURRENCY_LIMIT / 2) ||
     573           0 :        (0 == w_count) )
     574             :   {
     575           0 :     if (NULL != task)
     576           0 :       GNUNET_SCHEDULER_cancel (task);
     577           0 :     task = GNUNET_SCHEDULER_add_now (&select_work,
     578             :                                      NULL);
     579             :   }
     580             : }
     581             : 
     582             : 
     583             : /**
     584             :  * Typically called by `select_work`.
     585             :  *
     586             :  * @param cls NULL
     587             :  * @param deposit_serial identifies the deposit operation
     588             :  * @param wire_deadline when is the wire due
     589             :  * @param retry_backoff current value for the retry backoff
     590             :  * @param h_contract_terms hash of the contract terms
     591             :  * @param merchant_priv private key of the merchant
     592             :  * @param instance_id row ID of the instance
     593             :  * @param h_wire hash of the merchant's wire account into  * @param amount_with_fee amount the exchange will deposit for this coin
     594             :  * @param deposit_fee fee the exchange will charge for this coin which the deposit was made
     595             :  * @param coin_pub public key of the deposited coin
     596             :  */
     597             : static void
     598           0 : pending_deposits_cb (
     599             :   void *cls,
     600             :   uint64_t deposit_serial,
     601             :   struct GNUNET_TIME_Absolute wire_deadline,
     602             :   struct GNUNET_TIME_Relative retry_backoff,
     603             :   const struct TALER_PrivateContractHashP *h_contract_terms,
     604             :   const struct TALER_MerchantPrivateKeyP *merchant_priv,
     605             :   const char *instance_id,
     606             :   const struct TALER_MerchantWireHashP *h_wire,
     607             :   const struct TALER_Amount *amount_with_fee,
     608             :   const struct TALER_Amount *deposit_fee,
     609             :   const struct TALER_CoinSpendPublicKeyP *coin_pub)
     610             : {
     611             :   struct ExchangeInteraction *w;
     612             : 
     613             :   (void) cls;
     614           0 :   if (GNUNET_TIME_absolute_is_future (wire_deadline))
     615             :   {
     616           0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     617             :                 "Pending deposit has deadline in the future at %s\n",
     618             :                 GNUNET_TIME_absolute2s (wire_deadline));
     619           0 :     run_at (wire_deadline);
     620           0 :     return;
     621             :   }
     622           0 :   w = GNUNET_new (struct ExchangeInteraction);
     623           0 :   w->deposit_serial = deposit_serial;
     624           0 :   w->wire_deadline = wire_deadline;
     625           0 :   w->retry_backoff = GNUNET_TIME_STD_BACKOFF (retry_backoff);
     626           0 :   w->h_contract_terms = *h_contract_terms;
     627           0 :   w->merchant_priv = *merchant_priv;
     628           0 :   w->h_wire = *h_wire;
     629           0 :   w->amount_with_fee = *amount_with_fee;
     630           0 :   w->deposit_fee = *deposit_fee;
     631           0 :   w->coin_pub = *coin_pub;
     632           0 :   w->instance_id = GNUNET_strdup (instance_id);
     633           0 :   GNUNET_CONTAINER_DLL_insert (w_head,
     634             :                                w_tail,
     635             :                                w);
     636           0 :   w_count++;
     637           0 :   GNUNET_assert (NULL != keys);
     638           0 :   if (GNUNET_TIME_absolute_is_past (
     639           0 :         keys->key_data_expiration.abs_time))
     640             :   {
     641             :     /* Parent should re-start us, then we will re-fetch /keys */
     642           0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     643             :                 "/keys expired, shutting down\n");
     644           0 :     GNUNET_SCHEDULER_shutdown ();
     645           0 :     return;
     646             :   }
     647           0 :   GNUNET_assert (NULL == w->dgh);
     648           0 :   w->dgh = TALER_EXCHANGE_deposits_get (
     649             :     ctx,
     650             :     exchange_url,
     651             :     keys,
     652           0 :     &w->merchant_priv,
     653           0 :     &w->h_wire,
     654           0 :     &w->h_contract_terms,
     655           0 :     &w->coin_pub,
     656           0 :     GNUNET_TIME_UNIT_ZERO,
     657             :     &deposit_get_cb,
     658             :     w);
     659             : }
     660             : 
     661             : 
     662             : /**
     663             :  * Function called on events received from Postgres.
     664             :  *
     665             :  * @param cls closure, NULL
     666             :  * @param extra additional event data provided, timestamp with wire deadline
     667             :  * @param extra_size number of bytes in @a extra
     668             :  */
     669             : static void
     670           0 : db_notify (void *cls,
     671             :            const void *extra,
     672             :            size_t extra_size)
     673             : {
     674             :   struct GNUNET_TIME_Absolute deadline;
     675             :   struct GNUNET_TIME_AbsoluteNBO nbo_deadline;
     676             : 
     677             :   (void) cls;
     678           0 :   if (sizeof (nbo_deadline) != extra_size)
     679             :   {
     680           0 :     GNUNET_break (0);
     681           0 :     return;
     682             :   }
     683           0 :   if (0 != w_count)
     684           0 :     return; /* already at work! */
     685           0 :   memcpy (&nbo_deadline,
     686             :           extra,
     687             :           extra_size);
     688           0 :   deadline = GNUNET_TIME_absolute_ntoh (nbo_deadline);
     689           0 :   run_at (deadline);
     690             : }
     691             : 
     692             : 
     693             : static void
     694           8 : select_work (void *cls)
     695             : {
     696           8 :   bool retry = false;
     697           8 :   uint64_t limit = CONCURRENCY_LIMIT - w_count;
     698             : 
     699             :   (void) cls;
     700           8 :   task = NULL;
     701           8 :   GNUNET_assert (w_count <= CONCURRENCY_LIMIT);
     702           8 :   GNUNET_assert (NULL != keys);
     703           8 :   if (0 == limit)
     704             :   {
     705           0 :     GNUNET_break (0);
     706           0 :     return;
     707             :   }
     708           8 :   if (GNUNET_TIME_absolute_is_past (
     709           8 :         keys->key_data_expiration.abs_time))
     710             :   {
     711             :     /* Parent should re-start us, then we will re-fetch /keys */
     712           0 :     GNUNET_SCHEDULER_shutdown ();
     713           0 :     return;
     714             :   }
     715             :   while (1)
     716           0 :   {
     717             :     enum GNUNET_DB_QueryStatus qs;
     718             : 
     719           8 :     db_plugin->preflight (db_plugin->cls);
     720           8 :     if (retry)
     721           0 :       limit = 1;
     722           8 :     qs = db_plugin->lookup_pending_deposits (
     723           8 :       db_plugin->cls,
     724             :       exchange_url,
     725             :       limit,
     726             :       retry,
     727             :       &pending_deposits_cb,
     728             :       NULL);
     729           8 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     730             :                 "Looking up pending deposits query status was %d\n",
     731             :                 (int) qs);
     732           8 :     switch (qs)
     733             :     {
     734           0 :     case GNUNET_DB_STATUS_HARD_ERROR:
     735             :     case GNUNET_DB_STATUS_SOFT_ERROR:
     736           0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     737             :                   "Transaction failed!\n");
     738           0 :       global_ret = EXIT_FAILURE;
     739           0 :       GNUNET_SCHEDULER_shutdown ();
     740           0 :       return;
     741           8 :     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
     742           8 :       if (test_mode)
     743             :       {
     744           8 :         GNUNET_SCHEDULER_shutdown ();
     745           8 :         return;
     746             :       }
     747           0 :       if (retry)
     748           0 :         return; /* nothing left */
     749           0 :       retry = true;
     750           0 :       continue;
     751           0 :     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
     752             :     default:
     753             :       /* wait for async completion, then select more work. */
     754           0 :       return;
     755             :     }
     756             :   }
     757             : }
     758             : 
     759             : 
     760             : /**
     761             :  * Start a copy of this process with the exchange URL
     762             :  * set to the given @a base_url
     763             :  *
     764             :  * @param base_url base URL to run with
     765             :  */
     766             : static struct GNUNET_OS_Process *
     767           8 : start_worker (const char *base_url)
     768             : {
     769           8 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     770             :               "Launching worker for exchange `%s' using `%s`\n",
     771             :               base_url,
     772             :               NULL == cfg_filename
     773             :               ? "<default>"
     774             :               : cfg_filename);
     775           8 :   if (NULL == cfg_filename)
     776           0 :     return GNUNET_OS_start_process (
     777             :       GNUNET_OS_INHERIT_STD_ALL,
     778             :       NULL,
     779             :       NULL,
     780             :       NULL,
     781             :       "taler-merchant-depositcheck",
     782             :       "taler-merchant-depositcheck",
     783             :       "-e", base_url,
     784             :       "-L", "INFO",
     785           0 :       test_mode ? "-t" : NULL,
     786             :       NULL);
     787           8 :   return GNUNET_OS_start_process (
     788             :     GNUNET_OS_INHERIT_STD_ALL,
     789             :     NULL,
     790             :     NULL,
     791             :     NULL,
     792             :     "taler-merchant-depositcheck",
     793             :     "taler-merchant-depositcheck",
     794             :     "-c", cfg_filename,
     795             :     "-e", base_url,
     796             :     "-L", "INFO",
     797           8 :     test_mode ? "-t" : NULL,
     798             :     NULL);
     799             : }
     800             : 
     801             : 
     802             : /**
     803             :  * Restart worker process for the given child.
     804             :  *
     805             :  * @param cls a `struct Child *` that needs a worker.
     806             :  */
     807             : static void
     808             : restart_child (void *cls);
     809             : 
     810             : 
     811             : /**
     812             :  * Function called upon death or completion of a child process.
     813             :  *
     814             :  * @param cls a `struct Child *`
     815             :  * @param type type of the process
     816             :  * @param exit_code status code of the process
     817             :  */
     818             : static void
     819           8 : child_done_cb (void *cls,
     820             :                enum GNUNET_OS_ProcessStatusType type,
     821             :                long unsigned int exit_code)
     822             : {
     823           8 :   struct Child *c = cls;
     824             : 
     825           8 :   c->cwh = NULL;
     826           8 :   GNUNET_OS_process_destroy (c->process);
     827           8 :   c->process = NULL;
     828           8 :   if ( (GNUNET_OS_PROCESS_EXITED != type) ||
     829             :        (0 != exit_code) )
     830             :   {
     831           0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     832             :                 "Process for exchange %s had trouble (%d/%d)\n",
     833             :                 c->base_url,
     834             :                 (int) type,
     835             :                 (int) exit_code);
     836           0 :     GNUNET_SCHEDULER_shutdown ();
     837           0 :     global_ret = EXIT_NOTINSTALLED;
     838           0 :     return;
     839             :   }
     840           8 :   if (test_mode &&
     841           8 :       (! GNUNET_TIME_relative_is_zero (c->rd)) )
     842             :   {
     843           8 :     return;
     844             :   }
     845           0 :   if (GNUNET_TIME_absolute_is_future (c->next_start))
     846           0 :     c->rd = GNUNET_TIME_STD_BACKOFF (c->rd);
     847             :   else
     848           0 :     c->rd = GNUNET_TIME_UNIT_SECONDS;
     849           0 :   c->rt = GNUNET_SCHEDULER_add_at (c->next_start,
     850             :                                    &restart_child,
     851             :                                    c);
     852             : }
     853             : 
     854             : 
     855             : static void
     856           8 : restart_child (void *cls)
     857             : {
     858           8 :   struct Child *c = cls;
     859             : 
     860           8 :   c->rt = NULL;
     861           8 :   c->next_start = GNUNET_TIME_relative_to_absolute (c->rd);
     862           8 :   c->process = start_worker (c->base_url);
     863           8 :   if (NULL == c->process)
     864             :   {
     865           0 :     GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
     866             :                          "exec");
     867           0 :     global_ret = EXIT_NO_RESTART;
     868           0 :     GNUNET_SCHEDULER_shutdown ();
     869           0 :     return;
     870             :   }
     871           8 :   c->cwh = GNUNET_wait_child (c->process,
     872             :                               &child_done_cb,
     873             :                               c);
     874             : }
     875             : 
     876             : 
     877             : /**
     878             :  * Function to iterate over section.
     879             :  *
     880             :  * @param cls closure
     881             :  * @param section name of the section
     882             :  */
     883             : static void
     884         163 : cfg_iter_cb (void *cls,
     885             :              const char *section)
     886             : {
     887             :   char *base_url;
     888             :   struct Child *c;
     889             : 
     890         163 :   if (0 !=
     891         163 :       strncasecmp (section,
     892             :                    "merchant-exchange-",
     893             :                    strlen ("merchant-exchange-")))
     894         155 :     return;
     895          12 :   if (GNUNET_YES ==
     896          12 :       GNUNET_CONFIGURATION_get_value_yesno (cfg,
     897             :                                             section,
     898             :                                             "DISABLED"))
     899           4 :     return;
     900           8 :   if (GNUNET_OK !=
     901           8 :       GNUNET_CONFIGURATION_get_value_string (cfg,
     902             :                                              section,
     903             :                                              "EXCHANGE_BASE_URL",
     904             :                                              &base_url))
     905             :   {
     906           0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
     907             :                                section,
     908             :                                "EXCHANGE_BASE_URL");
     909           0 :     return;
     910             :   }
     911           8 :   c = GNUNET_new (struct Child);
     912           8 :   c->rd = GNUNET_TIME_UNIT_SECONDS;
     913           8 :   c->base_url = base_url;
     914           8 :   GNUNET_CONTAINER_DLL_insert (c_head,
     915             :                                c_tail,
     916             :                                c);
     917           8 :   c->rt = GNUNET_SCHEDULER_add_now (&restart_child,
     918             :                                     c);
     919             : }
     920             : 
     921             : 
     922             : /**
     923             :  * Trigger (re)loading of keys from DB.
     924             :  *
     925             :  * @param cls NULL
     926             :  * @param extra base URL of the exchange that changed
     927             :  * @param extra_len number of bytes in @a extra
     928             :  */
     929             : static void
     930           8 : update_exchange_keys (void *cls,
     931             :                       const void *extra,
     932             :                       size_t extra_len)
     933             : {
     934           8 :   const char *url = extra;
     935             : 
     936           8 :   if ( (NULL == extra) ||
     937             :        (0 == extra_len) )
     938             :   {
     939           0 :     GNUNET_break (0);
     940           0 :     return;
     941             :   }
     942           8 :   if ('\0' != url[extra_len - 1])
     943             :   {
     944           0 :     GNUNET_break (0);
     945           0 :     return;
     946             :   }
     947           8 :   if (0 != strcmp (url,
     948             :                    exchange_url))
     949           0 :     return; /* not relevant for us */
     950             : 
     951             :   {
     952             :     enum GNUNET_DB_QueryStatus qs;
     953             :     struct GNUNET_TIME_Absolute earliest_retry;
     954             : 
     955           8 :     if (NULL != keys)
     956             :     {
     957           0 :       TALER_EXCHANGE_keys_decref (keys);
     958           0 :       keys = NULL;
     959             :     }
     960           8 :     qs = db_plugin->select_exchange_keys (db_plugin->cls,
     961             :                                           exchange_url,
     962             :                                           &earliest_retry,
     963             :                                           &keys);
     964           8 :     if (qs < 0)
     965             :     {
     966           0 :       GNUNET_break (0);
     967           0 :       GNUNET_SCHEDULER_shutdown ();
     968           0 :       return;
     969             :     }
     970           8 :     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     971             :     {
     972           0 :       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     973             :                   "No keys yet for `%s'\n",
     974             :                   exchange_url);
     975             :     }
     976             :   }
     977           8 :   if (NULL == keys)
     978             :   {
     979           0 :     if (NULL != task)
     980             :     {
     981           0 :       GNUNET_SCHEDULER_cancel (task);
     982           0 :       task = NULL;
     983             :     }
     984             :   }
     985             :   else
     986             :   {
     987           8 :     if (NULL == task)
     988           8 :       task = GNUNET_SCHEDULER_add_now (&select_work,
     989             :                                        NULL);
     990             :   }
     991             : }
     992             : 
     993             : 
     994             : /**
     995             :  * First task.
     996             :  *
     997             :  * @param cls closure, NULL
     998             :  * @param args remaining command-line arguments
     999             :  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
    1000             :  * @param c configuration
    1001             :  */
    1002             : static void
    1003          12 : run (void *cls,
    1004             :      char *const *args,
    1005             :      const char *cfgfile,
    1006             :      const struct GNUNET_CONFIGURATION_Handle *c)
    1007             : {
    1008             :   (void) args;
    1009             : 
    1010          12 :   cfg = c;
    1011          12 :   if (NULL != cfgfile)
    1012          12 :     cfg_filename = GNUNET_strdup (cfgfile);
    1013          12 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1014             :               "Running with configuration %s\n",
    1015             :               cfgfile);
    1016          12 :   GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
    1017             :                                  NULL);
    1018          12 :   if (NULL == exchange_url)
    1019             :   {
    1020           4 :     GNUNET_CONFIGURATION_iterate_sections (c,
    1021             :                                            &cfg_iter_cb,
    1022             :                                            NULL);
    1023           4 :     if (NULL == c_head)
    1024             :     {
    1025           0 :       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    1026             :                   "No exchanges found in configuration\n");
    1027           0 :       global_ret = EXIT_NOTCONFIGURED;
    1028           0 :       GNUNET_SCHEDULER_shutdown ();
    1029           0 :       return;
    1030             :     }
    1031           4 :     return;
    1032             :   }
    1033             : 
    1034           8 :   ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
    1035             :                           &rc);
    1036           8 :   rc = GNUNET_CURL_gnunet_rc_create (ctx);
    1037           8 :   if (NULL == ctx)
    1038             :   {
    1039           0 :     GNUNET_break (0);
    1040           0 :     GNUNET_SCHEDULER_shutdown ();
    1041           0 :     global_ret = EXIT_NO_RESTART;
    1042           0 :     return;
    1043             :   }
    1044           8 :   if (NULL ==
    1045           8 :       (db_plugin = TALER_MERCHANTDB_plugin_load (cfg)))
    1046             :   {
    1047           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1048             :                 "Failed to initialize DB subsystem\n");
    1049           0 :     GNUNET_SCHEDULER_shutdown ();
    1050           0 :     global_ret = EXIT_NOTCONFIGURED;
    1051           0 :     return;
    1052             :   }
    1053           8 :   if (GNUNET_OK !=
    1054           8 :       db_plugin->connect (db_plugin->cls))
    1055             :   {
    1056           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1057             :                 "Failed to connect to database. Consider running taler-merchant-dbinit!\n");
    1058           0 :     GNUNET_SCHEDULER_shutdown ();
    1059           0 :     global_ret = EXIT_NO_RESTART;
    1060           0 :     return;
    1061             :   }
    1062             :   {
    1063           8 :     struct GNUNET_DB_EventHeaderP es = {
    1064           8 :       .size = htons (sizeof (es)),
    1065           8 :       .type = htons (TALER_DBEVENT_MERCHANT_NEW_WIRE_DEADLINE)
    1066             :     };
    1067             : 
    1068          16 :     eh = db_plugin->event_listen (db_plugin->cls,
    1069             :                                   &es,
    1070           8 :                                   GNUNET_TIME_UNIT_FOREVER_REL,
    1071             :                                   &db_notify,
    1072             :                                   NULL);
    1073             :   }
    1074             :   {
    1075           8 :     struct GNUNET_DB_EventHeaderP es = {
    1076           8 :       .size = ntohs (sizeof (es)),
    1077           8 :       .type = ntohs (TALER_DBEVENT_MERCHANT_EXCHANGE_KEYS)
    1078             :     };
    1079             : 
    1080          16 :     keys_eh = db_plugin->event_listen (db_plugin->cls,
    1081             :                                        &es,
    1082           8 :                                        GNUNET_TIME_UNIT_FOREVER_REL,
    1083             :                                        &update_exchange_keys,
    1084             :                                        NULL);
    1085             :   }
    1086             : 
    1087           8 :   update_exchange_keys (NULL,
    1088             :                         exchange_url,
    1089           8 :                         strlen (exchange_url) + 1);
    1090             : }
    1091             : 
    1092             : 
    1093             : /**
    1094             :  * The main function of the taler-merchant-depositcheck
    1095             :  *
    1096             :  * @param argc number of arguments from the command line
    1097             :  * @param argv command line arguments
    1098             :  * @return 0 ok, 1 on error
    1099             :  */
    1100             : int
    1101          12 : main (int argc,
    1102             :       char *const *argv)
    1103             : {
    1104          12 :   struct GNUNET_GETOPT_CommandLineOption options[] = {
    1105          12 :     GNUNET_GETOPT_option_string ('e',
    1106             :                                  "exchange",
    1107             :                                  "BASE_URL",
    1108             :                                  "limit us to checking deposits of this exchange",
    1109             :                                  &exchange_url),
    1110          12 :     GNUNET_GETOPT_option_timetravel ('T',
    1111             :                                      "timetravel"),
    1112          12 :     GNUNET_GETOPT_option_flag ('t',
    1113             :                                "test",
    1114             :                                "run in test mode and exit when idle",
    1115             :                                &test_mode),
    1116          12 :     GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION),
    1117             :     GNUNET_GETOPT_OPTION_END
    1118             :   };
    1119             :   enum GNUNET_GenericReturnValue ret;
    1120             : 
    1121          12 :   ret = GNUNET_PROGRAM_run (
    1122             :     TALER_MERCHANT_project_data (),
    1123             :     argc, argv,
    1124             :     "taler-merchant-depositcheck",
    1125             :     gettext_noop (
    1126             :       "background process that checks with the exchange on deposits that are past the wire deadline"),
    1127             :     options,
    1128             :     &run, NULL);
    1129          12 :   if (GNUNET_SYSERR == ret)
    1130           0 :     return EXIT_INVALIDARGUMENT;
    1131          12 :   if (GNUNET_NO == ret)
    1132           0 :     return EXIT_SUCCESS;
    1133          12 :   return global_ret;
    1134             : }
    1135             : 
    1136             : 
    1137             : /* end of taler-merchant-depositcheck.c */

Generated by: LCOV version 1.16