LCOV - code coverage report
Current view: top level - backend - taler-merchant-depositcheck.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 52.1 % 357 186
Test Date: 2025-11-06 19:31:41 Functions: 84.6 % 13 11

            Line data    Source code
       1              : /*
       2              :   This file is part of TALER
       3              :   Copyright (C) 2024, 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-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           15 : shutdown_task (void *cls)
     265              : {
     266              :   struct Child *c;
     267              :   struct ExchangeInteraction *w;
     268              : 
     269              :   (void) cls;
     270           15 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     271              :               "Running shutdown\n");
     272           15 :   if (NULL != eh)
     273              :   {
     274           10 :     db_plugin->event_listen_cancel (eh);
     275           10 :     eh = NULL;
     276              :   }
     277           15 :   if (NULL != keys_eh)
     278              :   {
     279           10 :     db_plugin->event_listen_cancel (keys_eh);
     280           10 :     keys_eh = NULL;
     281              :   }
     282           15 :   if (NULL != task)
     283              :   {
     284            0 :     GNUNET_SCHEDULER_cancel (task);
     285            0 :     task = NULL;
     286              :   }
     287           15 :   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            5 :     GNUNET_CONTAINER_DLL_remove (c_head,
     304              :                                  c_tail,
     305              :                                  c);
     306            5 :     if (NULL != c->rt)
     307              :     {
     308            0 :       GNUNET_SCHEDULER_cancel (c->rt);
     309            0 :       c->rt = NULL;
     310              :     }
     311            5 :     if (NULL != c->cwh)
     312              :     {
     313            0 :       GNUNET_wait_child_cancel (c->cwh);
     314            0 :       c->cwh = NULL;
     315              :     }
     316            5 :     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            5 :     GNUNET_free (c->base_url);
     339            5 :     GNUNET_free (c);
     340              :   }
     341           15 :   if (NULL != db_plugin)
     342              :   {
     343           10 :     db_plugin->rollback (db_plugin->cls); /* just in case */
     344           10 :     TALER_MERCHANTDB_plugin_unload (db_plugin);
     345           10 :     db_plugin = NULL;
     346              :   }
     347           15 :   cfg = NULL;
     348           15 :   if (NULL != ctx)
     349              :   {
     350           10 :     GNUNET_CURL_fini (ctx);
     351           10 :     ctx = NULL;
     352              :   }
     353           15 :   if (NULL != rc)
     354              :   {
     355           10 :     GNUNET_CURL_gnunet_rc_destroy (rc);
     356           10 :     rc = NULL;
     357              :   }
     358           15 : }
     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            8 : deposit_get_cb (
     413              :   void *cls,
     414              :   const struct TALER_EXCHANGE_GetDepositResponse *dr)
     415              : {
     416            8 :   struct ExchangeInteraction *w = cls;
     417              :   struct GNUNET_TIME_Absolute future_retry;
     418              : 
     419            8 :   w->dgh = NULL;
     420              :   future_retry
     421            8 :     = GNUNET_TIME_relative_to_absolute (w->retry_backoff);
     422            8 :   switch (dr->hr.http_status)
     423              :   {
     424            8 :   case MHD_HTTP_OK:
     425              :     {
     426              :       enum GNUNET_DB_QueryStatus qs;
     427              : 
     428            8 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     429              :                   "Exchange returned wire transfer over %s for deposited coin %s\n",
     430              :                   TALER_amount2s (&dr->details.ok.coin_contribution),
     431              :                   TALER_B2S (&w->coin_pub));
     432            8 :       qs = db_plugin->insert_deposit_to_transfer (
     433            8 :         db_plugin->cls,
     434              :         w->deposit_serial,
     435            8 :         &w->h_wire,
     436              :         exchange_url,
     437              :         &dr->details.ok);
     438            8 :       if (qs <= 0)
     439              :       {
     440            0 :         GNUNET_break (0);
     441            0 :         GNUNET_SCHEDULER_shutdown ();
     442            0 :         return;
     443              :       }
     444            8 :       break;
     445              :     }
     446            0 :   case MHD_HTTP_ACCEPTED:
     447              :     {
     448              :       /* got a 'preliminary' reply from the exchange,
     449              :          remember our target UUID */
     450              :       enum GNUNET_DB_QueryStatus qs;
     451              :       struct GNUNET_TIME_Timestamp now;
     452              : 
     453            0 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     454              :                   "Exchange returned KYC requirement (%d) for deposited coin %s\n",
     455              :                   dr->details.accepted.kyc_ok,
     456              :                   TALER_B2S (&w->coin_pub));
     457            0 :       now = GNUNET_TIME_timestamp_get ();
     458            0 :       qs = db_plugin->account_kyc_set_failed (
     459            0 :         db_plugin->cls,
     460            0 :         w->instance_id,
     461            0 :         &w->h_wire,
     462              :         exchange_url,
     463              :         now,
     464              :         MHD_HTTP_ACCEPTED,
     465            0 :         dr->details.accepted.kyc_ok);
     466            0 :       if (qs < 0)
     467              :       {
     468            0 :         GNUNET_break (0);
     469            0 :         GNUNET_SCHEDULER_shutdown ();
     470            0 :         return;
     471              :       }
     472            0 :       if (dr->details.accepted.kyc_ok)
     473              :       {
     474            0 :         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     475              :                     "Bumping wire transfer deadline in DB to %s as that is when we will retry\n",
     476              :                     GNUNET_TIME_absolute2s (future_retry));
     477            0 :         qs = db_plugin->update_deposit_confirmation_status (
     478            0 :           db_plugin->cls,
     479              :           w->deposit_serial,
     480              :           true, /* need to try again in the future! */
     481              :           GNUNET_TIME_absolute_to_timestamp (future_retry),
     482              :           MHD_HTTP_ACCEPTED,
     483              :           TALER_EC_NONE,
     484              :           "Exchange reported 202 Accepted but no KYC block");
     485            0 :         if (qs < 0)
     486              :         {
     487            0 :           GNUNET_break (0);
     488            0 :           GNUNET_SCHEDULER_shutdown ();
     489            0 :           return;
     490              :         }
     491              :       }
     492              :       else
     493              :       {
     494              :         future_retry
     495            0 :           = GNUNET_TIME_absolute_max (
     496              :               future_retry,
     497              :               GNUNET_TIME_relative_to_absolute (
     498              :                 KYC_RETRY_DELAY));
     499            0 :         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     500              :                     "Bumping wire transfer deadline in DB to %s as that is when we will retry\n",
     501              :                     GNUNET_TIME_absolute2s (future_retry));
     502            0 :         qs = db_plugin->update_deposit_confirmation_status (
     503            0 :           db_plugin->cls,
     504              :           w->deposit_serial,
     505              :           true /* need to try again in the future */,
     506              :           GNUNET_TIME_absolute_to_timestamp (future_retry),
     507              :           MHD_HTTP_ACCEPTED,
     508              :           TALER_EC_NONE,
     509              :           "Exchange reported 202 Accepted due to KYC/AML block");
     510            0 :         if (qs < 0)
     511              :         {
     512            0 :           GNUNET_break (0);
     513            0 :           GNUNET_SCHEDULER_shutdown ();
     514            0 :           return;
     515              :         }
     516              :       }
     517            0 :       break;
     518              :     }
     519            0 :   default:
     520              :     {
     521              :       enum GNUNET_DB_QueryStatus qs;
     522            0 :       bool retry_needed = false;
     523              : 
     524            0 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     525              :                   "Exchange %s returned tracking failure for deposited coin %s: %u\n",
     526              :                   exchange_url,
     527              :                   TALER_B2S (&w->coin_pub),
     528              :                   dr->hr.http_status);
     529              :       /* rough classification by HTTP status group */
     530            0 :       switch (dr->hr.http_status / 100)
     531              :       {
     532            0 :       case 0:
     533              :         /* timeout */
     534            0 :         retry_needed = true;
     535            0 :         break;
     536            0 :       case 1:
     537              :       case 2:
     538              :       case 3:
     539              :         /* very strange */
     540            0 :         retry_needed = false;
     541            0 :         break;
     542            0 :       case 4:
     543              :         /* likely fatal */
     544            0 :         retry_needed = false;
     545            0 :         break;
     546            0 :       case 5:
     547              :         /* likely transient */
     548            0 :         retry_needed = true;
     549            0 :         break;
     550              :       }
     551            0 :       qs = db_plugin->update_deposit_confirmation_status (
     552            0 :         db_plugin->cls,
     553              :         w->deposit_serial,
     554              :         retry_needed,
     555              :         GNUNET_TIME_absolute_to_timestamp (future_retry),
     556            0 :         (uint32_t) dr->hr.http_status,
     557            0 :         dr->hr.ec,
     558            0 :         dr->hr.hint);
     559            0 :       if (qs < 0)
     560              :       {
     561            0 :         GNUNET_break (0);
     562            0 :         GNUNET_SCHEDULER_shutdown ();
     563            0 :         return;
     564              :       }
     565            0 :       break;
     566              :     }
     567              :   } /* end switch */
     568              : 
     569            8 :   GNUNET_CONTAINER_DLL_remove (w_head,
     570              :                                w_tail,
     571              :                                w);
     572            8 :   w_count--;
     573            8 :   GNUNET_free (w->instance_id);
     574            8 :   GNUNET_free (w);
     575            8 :   GNUNET_assert (NULL != keys);
     576            8 :   if (0 == w_count)
     577              :   {
     578              :     /* We only SELECT() again after having finished
     579              :        all requests, as otherwise we'll most like
     580              :        just SELECT() those again that are already
     581              :        being requested; alternatively, we could
     582              :        update the retry_time already on SELECT(),
     583              :        but this should be easier on the DB. */
     584            8 :     if (NULL != task)
     585            0 :       GNUNET_SCHEDULER_cancel (task);
     586            8 :     task = GNUNET_SCHEDULER_add_now (&select_work,
     587              :                                      NULL);
     588              :   }
     589              : }
     590              : 
     591              : 
     592              : /**
     593              :  * Typically called by `select_work`.
     594              :  *
     595              :  * @param cls NULL
     596              :  * @param deposit_serial identifies the deposit operation
     597              :  * @param wire_deadline when is the wire due
     598              :  * @param retry_time current value for the retry backoff
     599              :  * @param h_contract_terms hash of the contract terms
     600              :  * @param merchant_priv private key of the merchant
     601              :  * @param instance_id row ID of the instance
     602              :  * @param h_wire hash of the merchant's wire account into
     603              :  * @param amount_with_fee amount the exchange will deposit for this coin
     604              :  * @param deposit_fee fee the exchange will charge for this coin which the deposit was made
     605              :  * @param coin_pub public key of the deposited coin
     606              :  */
     607              : static void
     608            8 : pending_deposits_cb (
     609              :   void *cls,
     610              :   uint64_t deposit_serial,
     611              :   struct GNUNET_TIME_Absolute wire_deadline,
     612              :   struct GNUNET_TIME_Absolute retry_time,
     613              :   const struct TALER_PrivateContractHashP *h_contract_terms,
     614              :   const struct TALER_MerchantPrivateKeyP *merchant_priv,
     615              :   const char *instance_id,
     616              :   const struct TALER_MerchantWireHashP *h_wire,
     617              :   const struct TALER_Amount *amount_with_fee,
     618              :   const struct TALER_Amount *deposit_fee,
     619              :   const struct TALER_CoinSpendPublicKeyP *coin_pub)
     620              : {
     621              :   struct ExchangeInteraction *w;
     622              :   struct GNUNET_TIME_Absolute mx
     623            8 :     = GNUNET_TIME_absolute_max (wire_deadline,
     624              :                                 retry_time);
     625              :   struct GNUNET_TIME_Relative retry_backoff;
     626              : 
     627              :   (void) cls;
     628            8 :   if (GNUNET_TIME_absolute_is_future (mx))
     629              :   {
     630            0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     631              :                 "Pending deposit should be checked next at %s\n",
     632              :                 GNUNET_TIME_absolute2s (mx));
     633            0 :     run_at (mx);
     634            0 :     return;
     635              :   }
     636            8 :   if (GNUNET_TIME_absolute_is_zero (retry_time))
     637            0 :     retry_backoff = GNUNET_TIME_absolute_get_duration (wire_deadline);
     638              :   else
     639            8 :     retry_backoff = GNUNET_TIME_absolute_get_difference (wire_deadline,
     640              :                                                          retry_time);
     641            8 :   w = GNUNET_new (struct ExchangeInteraction);
     642            8 :   w->deposit_serial = deposit_serial;
     643            8 :   w->wire_deadline = wire_deadline;
     644            8 :   w->retry_backoff = GNUNET_TIME_randomized_backoff (retry_backoff,
     645              :                                                      GNUNET_TIME_UNIT_DAYS);
     646            8 :   w->h_contract_terms = *h_contract_terms;
     647            8 :   w->merchant_priv = *merchant_priv;
     648            8 :   w->h_wire = *h_wire;
     649            8 :   w->amount_with_fee = *amount_with_fee;
     650            8 :   w->deposit_fee = *deposit_fee;
     651            8 :   w->coin_pub = *coin_pub;
     652            8 :   w->instance_id = GNUNET_strdup (instance_id);
     653            8 :   GNUNET_CONTAINER_DLL_insert (w_head,
     654              :                                w_tail,
     655              :                                w);
     656            8 :   w_count++;
     657            8 :   GNUNET_assert (NULL != keys);
     658            8 :   if (GNUNET_TIME_absolute_is_past (
     659            8 :         keys->key_data_expiration.abs_time))
     660              :   {
     661              :     /* Parent should re-start us, then we will re-fetch /keys */
     662            0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     663              :                 "/keys expired, shutting down\n");
     664            0 :     GNUNET_SCHEDULER_shutdown ();
     665            0 :     return;
     666              :   }
     667            8 :   GNUNET_assert (NULL == w->dgh);
     668            8 :   w->dgh = TALER_EXCHANGE_deposits_get (
     669              :     ctx,
     670              :     exchange_url,
     671              :     keys,
     672            8 :     &w->merchant_priv,
     673            8 :     &w->h_wire,
     674            8 :     &w->h_contract_terms,
     675            8 :     &w->coin_pub,
     676            8 :     GNUNET_TIME_UNIT_ZERO,
     677              :     &deposit_get_cb,
     678              :     w);
     679              : }
     680              : 
     681              : 
     682              : /**
     683              :  * Function called on events received from Postgres.
     684              :  *
     685              :  * @param cls closure, NULL
     686              :  * @param extra additional event data provided, timestamp with wire deadline
     687              :  * @param extra_size number of bytes in @a extra
     688              :  */
     689              : static void
     690            0 : db_notify (void *cls,
     691              :            const void *extra,
     692              :            size_t extra_size)
     693              : {
     694              :   struct GNUNET_TIME_Absolute deadline;
     695              :   struct GNUNET_TIME_AbsoluteNBO nbo_deadline;
     696              : 
     697              :   (void) cls;
     698            0 :   if (sizeof (nbo_deadline) != extra_size)
     699              :   {
     700            0 :     GNUNET_break (0);
     701            0 :     return;
     702              :   }
     703            0 :   if (0 != w_count)
     704            0 :     return; /* already at work! */
     705            0 :   memcpy (&nbo_deadline,
     706              :           extra,
     707              :           extra_size);
     708            0 :   deadline = GNUNET_TIME_absolute_ntoh (nbo_deadline);
     709            0 :   run_at (deadline);
     710              : }
     711              : 
     712              : 
     713              : static void
     714           18 : select_work (void *cls)
     715              : {
     716           18 :   bool retry = false;
     717           18 :   uint64_t limit = CONCURRENCY_LIMIT - w_count;
     718              : 
     719              :   (void) cls;
     720           18 :   task = NULL;
     721           18 :   GNUNET_assert (w_count <= CONCURRENCY_LIMIT);
     722           18 :   GNUNET_assert (NULL != keys);
     723           18 :   if (0 == limit)
     724              :   {
     725            0 :     GNUNET_break (0);
     726            0 :     return;
     727              :   }
     728           18 :   if (GNUNET_TIME_absolute_is_past (
     729           18 :         keys->key_data_expiration.abs_time))
     730              :   {
     731              :     /* Parent should re-start us, then we will re-fetch /keys */
     732            0 :     GNUNET_SCHEDULER_shutdown ();
     733            0 :     return;
     734              :   }
     735              :   while (1)
     736            0 :   {
     737              :     enum GNUNET_DB_QueryStatus qs;
     738              : 
     739           18 :     db_plugin->preflight (db_plugin->cls);
     740           18 :     if (retry)
     741            0 :       limit = 1;
     742           18 :     qs = db_plugin->lookup_pending_deposits (
     743           18 :       db_plugin->cls,
     744              :       exchange_url,
     745              :       limit,
     746              :       retry,
     747              :       &pending_deposits_cb,
     748              :       NULL);
     749           18 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     750              :                 "Looking up pending deposits query status was %d\n",
     751              :                 (int) qs);
     752           18 :     switch (qs)
     753              :     {
     754            0 :     case GNUNET_DB_STATUS_HARD_ERROR:
     755              :     case GNUNET_DB_STATUS_SOFT_ERROR:
     756            0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     757              :                   "Transaction failed!\n");
     758            0 :       global_ret = EXIT_FAILURE;
     759            0 :       GNUNET_SCHEDULER_shutdown ();
     760            0 :       return;
     761           10 :     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
     762           10 :       if (test_mode)
     763              :       {
     764           10 :         GNUNET_SCHEDULER_shutdown ();
     765           10 :         return;
     766              :       }
     767            0 :       if (retry)
     768            0 :         return; /* nothing left */
     769            0 :       retry = true;
     770            0 :       continue;
     771            8 :     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
     772              :     default:
     773              :       /* wait for async completion, then select more work. */
     774            8 :       return;
     775              :     }
     776              :   }
     777              : }
     778              : 
     779              : 
     780              : /**
     781              :  * Start a copy of this process with the exchange URL
     782              :  * set to the given @a base_url
     783              :  *
     784              :  * @param base_url base URL to run with
     785              :  */
     786              : static struct GNUNET_OS_Process *
     787            5 : start_worker (const char *base_url)
     788              : {
     789              :   char toff[30];
     790              :   long long zo;
     791              : 
     792            5 :   zo = GNUNET_TIME_get_offset ();
     793            5 :   GNUNET_snprintf (toff,
     794              :                    sizeof (toff),
     795              :                    "%lld",
     796              :                    zo);
     797            5 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     798              :               "Launching worker for exchange `%s' using `%s`\n",
     799              :               base_url,
     800              :               NULL == cfg_filename
     801              :               ? "<default>"
     802              :               : cfg_filename);
     803            5 :   if (NULL == cfg_filename)
     804            0 :     return GNUNET_OS_start_process (
     805              :       GNUNET_OS_INHERIT_STD_ALL,
     806              :       NULL,
     807              :       NULL,
     808              :       NULL,
     809              :       "taler-merchant-depositcheck",
     810              :       "taler-merchant-depositcheck",
     811              :       "-e", base_url,
     812              :       "-L", "INFO",
     813              :       "-T", toff,
     814            0 :       test_mode ? "-t" : NULL,
     815              :       NULL);
     816            5 :   return GNUNET_OS_start_process (
     817              :     GNUNET_OS_INHERIT_STD_ALL,
     818              :     NULL,
     819              :     NULL,
     820              :     NULL,
     821              :     "taler-merchant-depositcheck",
     822              :     "taler-merchant-depositcheck",
     823              :     "-c", cfg_filename,
     824              :     "-e", base_url,
     825              :     "-L", "INFO",
     826              :     "-T", toff,
     827            5 :     test_mode ? "-t" : NULL,
     828              :     NULL);
     829              : }
     830              : 
     831              : 
     832              : /**
     833              :  * Restart worker process for the given child.
     834              :  *
     835              :  * @param cls a `struct Child *` that needs a worker.
     836              :  */
     837              : static void
     838              : restart_child (void *cls);
     839              : 
     840              : 
     841              : /**
     842              :  * Function called upon death or completion of a child process.
     843              :  *
     844              :  * @param cls a `struct Child *`
     845              :  * @param type type of the process
     846              :  * @param exit_code status code of the process
     847              :  */
     848              : static void
     849            5 : child_done_cb (void *cls,
     850              :                enum GNUNET_OS_ProcessStatusType type,
     851              :                long unsigned int exit_code)
     852              : {
     853            5 :   struct Child *c = cls;
     854              : 
     855            5 :   c->cwh = NULL;
     856            5 :   GNUNET_OS_process_destroy (c->process);
     857            5 :   c->process = NULL;
     858            5 :   if ( (GNUNET_OS_PROCESS_EXITED != type) ||
     859              :        (0 != exit_code) )
     860              :   {
     861            0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     862              :                 "Process for exchange %s had trouble (%d/%d)\n",
     863              :                 c->base_url,
     864              :                 (int) type,
     865              :                 (int) exit_code);
     866            0 :     GNUNET_SCHEDULER_shutdown ();
     867            0 :     global_ret = EXIT_NOTINSTALLED;
     868            0 :     return;
     869              :   }
     870            5 :   if (test_mode &&
     871            5 :       (! GNUNET_TIME_relative_is_zero (c->rd)) )
     872              :   {
     873            5 :     return;
     874              :   }
     875            0 :   if (GNUNET_TIME_absolute_is_future (c->next_start))
     876            0 :     c->rd = GNUNET_TIME_STD_BACKOFF (c->rd);
     877              :   else
     878            0 :     c->rd = GNUNET_TIME_UNIT_SECONDS;
     879            0 :   c->rt = GNUNET_SCHEDULER_add_at (c->next_start,
     880              :                                    &restart_child,
     881              :                                    c);
     882              : }
     883              : 
     884              : 
     885              : static void
     886            5 : restart_child (void *cls)
     887              : {
     888            5 :   struct Child *c = cls;
     889              : 
     890            5 :   c->rt = NULL;
     891            5 :   c->next_start = GNUNET_TIME_relative_to_absolute (c->rd);
     892            5 :   c->process = start_worker (c->base_url);
     893            5 :   if (NULL == c->process)
     894              :   {
     895            0 :     GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
     896              :                          "exec");
     897            0 :     global_ret = EXIT_NO_RESTART;
     898            0 :     GNUNET_SCHEDULER_shutdown ();
     899            0 :     return;
     900              :   }
     901            5 :   c->cwh = GNUNET_wait_child (c->process,
     902              :                               &child_done_cb,
     903              :                               c);
     904              : }
     905              : 
     906              : 
     907              : /**
     908              :  * Function to iterate over section.
     909              :  *
     910              :  * @param cls closure
     911              :  * @param section name of the section
     912              :  */
     913              : static void
     914          209 : cfg_iter_cb (void *cls,
     915              :              const char *section)
     916              : {
     917              :   char *base_url;
     918              :   struct Child *c;
     919              : 
     920          209 :   if (0 !=
     921          209 :       strncasecmp (section,
     922              :                    "merchant-exchange-",
     923              :                    strlen ("merchant-exchange-")))
     924          204 :     return;
     925           15 :   if (GNUNET_YES ==
     926           15 :       GNUNET_CONFIGURATION_get_value_yesno (cfg,
     927              :                                             section,
     928              :                                             "DISABLED"))
     929           10 :     return;
     930            5 :   if (GNUNET_OK !=
     931            5 :       GNUNET_CONFIGURATION_get_value_string (cfg,
     932              :                                              section,
     933              :                                              "EXCHANGE_BASE_URL",
     934              :                                              &base_url))
     935              :   {
     936            0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
     937              :                                section,
     938              :                                "EXCHANGE_BASE_URL");
     939            0 :     return;
     940              :   }
     941            5 :   c = GNUNET_new (struct Child);
     942            5 :   c->rd = GNUNET_TIME_UNIT_SECONDS;
     943            5 :   c->base_url = base_url;
     944            5 :   GNUNET_CONTAINER_DLL_insert (c_head,
     945              :                                c_tail,
     946              :                                c);
     947            5 :   c->rt = GNUNET_SCHEDULER_add_now (&restart_child,
     948              :                                     c);
     949              : }
     950              : 
     951              : 
     952              : /**
     953              :  * Trigger (re)loading of keys from DB.
     954              :  *
     955              :  * @param cls NULL
     956              :  * @param extra base URL of the exchange that changed
     957              :  * @param extra_len number of bytes in @a extra
     958              :  */
     959              : static void
     960           10 : update_exchange_keys (void *cls,
     961              :                       const void *extra,
     962              :                       size_t extra_len)
     963              : {
     964           10 :   const char *url = extra;
     965              : 
     966           10 :   if ( (NULL == extra) ||
     967              :        (0 == extra_len) )
     968              :   {
     969            0 :     GNUNET_break (0);
     970            0 :     return;
     971              :   }
     972           10 :   if ('\0' != url[extra_len - 1])
     973              :   {
     974            0 :     GNUNET_break (0);
     975            0 :     return;
     976              :   }
     977           10 :   if (0 != strcmp (url,
     978              :                    exchange_url))
     979            0 :     return; /* not relevant for us */
     980              : 
     981              :   {
     982              :     enum GNUNET_DB_QueryStatus qs;
     983              :     struct GNUNET_TIME_Absolute earliest_retry;
     984              : 
     985           10 :     if (NULL != keys)
     986              :     {
     987            0 :       TALER_EXCHANGE_keys_decref (keys);
     988            0 :       keys = NULL;
     989              :     }
     990           10 :     qs = db_plugin->select_exchange_keys (db_plugin->cls,
     991              :                                           exchange_url,
     992              :                                           &earliest_retry,
     993              :                                           &keys);
     994           10 :     if (qs < 0)
     995              :     {
     996            0 :       GNUNET_break (0);
     997            0 :       GNUNET_SCHEDULER_shutdown ();
     998            0 :       return;
     999              :     }
    1000           10 :     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    1001              :     {
    1002            0 :       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    1003              :                   "No keys yet for `%s'\n",
    1004              :                   exchange_url);
    1005              :     }
    1006              :   }
    1007           10 :   if (NULL == keys)
    1008              :   {
    1009            0 :     if (NULL != task)
    1010              :     {
    1011            0 :       GNUNET_SCHEDULER_cancel (task);
    1012            0 :       task = NULL;
    1013              :     }
    1014              :   }
    1015              :   else
    1016              :   {
    1017           10 :     if (NULL == task)
    1018           10 :       task = GNUNET_SCHEDULER_add_now (&select_work,
    1019              :                                        NULL);
    1020              :   }
    1021              : }
    1022              : 
    1023              : 
    1024              : /**
    1025              :  * First task.
    1026              :  *
    1027              :  * @param cls closure, NULL
    1028              :  * @param args remaining command-line arguments
    1029              :  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
    1030              :  * @param c configuration
    1031              :  */
    1032              : static void
    1033           15 : run (void *cls,
    1034              :      char *const *args,
    1035              :      const char *cfgfile,
    1036              :      const struct GNUNET_CONFIGURATION_Handle *c)
    1037              : {
    1038              :   (void) args;
    1039              : 
    1040           15 :   cfg = c;
    1041           15 :   if (NULL != cfgfile)
    1042           15 :     cfg_filename = GNUNET_strdup (cfgfile);
    1043           15 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1044              :               "Running with configuration %s\n",
    1045              :               cfgfile);
    1046           15 :   GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
    1047              :                                  NULL);
    1048           15 :   if (NULL == exchange_url)
    1049              :   {
    1050            5 :     GNUNET_CONFIGURATION_iterate_sections (c,
    1051              :                                            &cfg_iter_cb,
    1052              :                                            NULL);
    1053            5 :     if (NULL == c_head)
    1054              :     {
    1055            0 :       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    1056              :                   "No exchanges found in configuration\n");
    1057            0 :       global_ret = EXIT_NOTCONFIGURED;
    1058            0 :       GNUNET_SCHEDULER_shutdown ();
    1059            0 :       return;
    1060              :     }
    1061            5 :     return;
    1062              :   }
    1063              : 
    1064           10 :   ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
    1065              :                           &rc);
    1066           10 :   rc = GNUNET_CURL_gnunet_rc_create (ctx);
    1067           10 :   if (NULL == ctx)
    1068              :   {
    1069            0 :     GNUNET_break (0);
    1070            0 :     GNUNET_SCHEDULER_shutdown ();
    1071            0 :     global_ret = EXIT_NO_RESTART;
    1072            0 :     return;
    1073              :   }
    1074           10 :   if (NULL ==
    1075           10 :       (db_plugin = TALER_MERCHANTDB_plugin_load (cfg)))
    1076              :   {
    1077            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1078              :                 "Failed to initialize DB subsystem\n");
    1079            0 :     GNUNET_SCHEDULER_shutdown ();
    1080            0 :     global_ret = EXIT_NOTCONFIGURED;
    1081            0 :     return;
    1082              :   }
    1083           10 :   if (GNUNET_OK !=
    1084           10 :       db_plugin->connect (db_plugin->cls))
    1085              :   {
    1086            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1087              :                 "Failed to connect to database. Consider running taler-merchant-dbinit!\n");
    1088            0 :     GNUNET_SCHEDULER_shutdown ();
    1089            0 :     global_ret = EXIT_NO_RESTART;
    1090            0 :     return;
    1091              :   }
    1092              :   {
    1093           10 :     struct GNUNET_DB_EventHeaderP es = {
    1094           10 :       .size = htons (sizeof (es)),
    1095           10 :       .type = htons (TALER_DBEVENT_MERCHANT_NEW_WIRE_DEADLINE)
    1096              :     };
    1097              : 
    1098           20 :     eh = db_plugin->event_listen (db_plugin->cls,
    1099              :                                   &es,
    1100           10 :                                   GNUNET_TIME_UNIT_FOREVER_REL,
    1101              :                                   &db_notify,
    1102              :                                   NULL);
    1103              :   }
    1104              :   {
    1105           10 :     struct GNUNET_DB_EventHeaderP es = {
    1106           10 :       .size = ntohs (sizeof (es)),
    1107           10 :       .type = ntohs (TALER_DBEVENT_MERCHANT_EXCHANGE_KEYS)
    1108              :     };
    1109              : 
    1110           20 :     keys_eh = db_plugin->event_listen (db_plugin->cls,
    1111              :                                        &es,
    1112           10 :                                        GNUNET_TIME_UNIT_FOREVER_REL,
    1113              :                                        &update_exchange_keys,
    1114              :                                        NULL);
    1115              :   }
    1116              : 
    1117           10 :   update_exchange_keys (NULL,
    1118              :                         exchange_url,
    1119           10 :                         strlen (exchange_url) + 1);
    1120              : }
    1121              : 
    1122              : 
    1123              : /**
    1124              :  * The main function of the taler-merchant-depositcheck
    1125              :  *
    1126              :  * @param argc number of arguments from the command line
    1127              :  * @param argv command line arguments
    1128              :  * @return 0 ok, 1 on error
    1129              :  */
    1130              : int
    1131           15 : main (int argc,
    1132              :       char *const *argv)
    1133              : {
    1134           15 :   struct GNUNET_GETOPT_CommandLineOption options[] = {
    1135           15 :     GNUNET_GETOPT_option_string ('e',
    1136              :                                  "exchange",
    1137              :                                  "BASE_URL",
    1138              :                                  "limit us to checking deposits of this exchange",
    1139              :                                  &exchange_url),
    1140           15 :     GNUNET_GETOPT_option_timetravel ('T',
    1141              :                                      "timetravel"),
    1142           15 :     GNUNET_GETOPT_option_flag ('t',
    1143              :                                "test",
    1144              :                                "run in test mode and exit when idle",
    1145              :                                &test_mode),
    1146           15 :     GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION),
    1147              :     GNUNET_GETOPT_OPTION_END
    1148              :   };
    1149              :   enum GNUNET_GenericReturnValue ret;
    1150              : 
    1151           15 :   ret = GNUNET_PROGRAM_run (
    1152              :     TALER_MERCHANT_project_data (),
    1153              :     argc, argv,
    1154              :     "taler-merchant-depositcheck",
    1155              :     gettext_noop (
    1156              :       "background process that checks with the exchange on deposits that are past the wire deadline"),
    1157              :     options,
    1158              :     &run, NULL);
    1159           15 :   if (GNUNET_SYSERR == ret)
    1160            0 :     return EXIT_INVALIDARGUMENT;
    1161           15 :   if (GNUNET_NO == ret)
    1162            0 :     return EXIT_SUCCESS;
    1163           15 :   return global_ret;
    1164              : }
    1165              : 
    1166              : 
    1167              : /* end of taler-merchant-depositcheck.c */
        

Generated by: LCOV version 2.0-1