LCOV - code coverage report
Current view: top level - exchange - taler-exchange-closer.c (source / functions) Hit Total Coverage
Test: GNU Taler exchange coverage report Lines: 103 164 62.8 %
Date: 2021-08-30 06:43:37 Functions: 7 7 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   Copyright (C) 2016-2021 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             : /**
      18             :  * @file taler-exchange-closer.c
      19             :  * @brief Process that closes expired reserves
      20             :  * @author Christian Grothoff
      21             :  */
      22             : #include "platform.h"
      23             : #include <gnunet/gnunet_util_lib.h>
      24             : #include <jansson.h>
      25             : #include <pthread.h>
      26             : #include "taler_exchangedb_lib.h"
      27             : #include "taler_exchangedb_plugin.h"
      28             : #include "taler_json_lib.h"
      29             : #include "taler_bank_service.h"
      30             : 
      31             : 
      32             : /**
      33             :  * What is the smallest unit we support for wire transfers?
      34             :  * We will need to round down to a multiple of this amount.
      35             :  */
      36             : static struct TALER_Amount currency_round_unit;
      37             : 
      38             : /**
      39             :  * What is the base URL of this exchange?  Used in the
      40             :  * wire transfer subjects to that merchants and governments
      41             :  * can ask for the list of aggregated deposits.
      42             :  */
      43             : static char *exchange_base_url;
      44             : 
      45             : /**
      46             :  * The exchange's configuration.
      47             :  */
      48             : static const struct GNUNET_CONFIGURATION_Handle *cfg;
      49             : 
      50             : /**
      51             :  * Our database plugin.
      52             :  */
      53             : static struct TALER_EXCHANGEDB_Plugin *db_plugin;
      54             : 
      55             : /**
      56             :  * Next task to run, if any.
      57             :  */
      58             : static struct GNUNET_SCHEDULER_Task *task;
      59             : 
      60             : /**
      61             :  * How long should we sleep when idle before trying to find more work?
      62             :  */
      63             : static struct GNUNET_TIME_Relative aggregator_idle_sleep_interval;
      64             : 
      65             : /**
      66             :  * Value to return from main(). 0 on success, non-zero
      67             :  * on serious errors.
      68             :  */
      69             : static int global_ret;
      70             : 
      71             : /**
      72             :  * #GNUNET_YES if we are in test mode and should exit when idle.
      73             :  */
      74             : static int test_mode;
      75             : 
      76             : 
      77             : /**
      78             :  * Main work function that finds and triggers transfers for reserves
      79             :  * closures.
      80             :  *
      81             :  * @param cls closure
      82             :  */
      83             : static void
      84             : run_reserve_closures (void *cls);
      85             : 
      86             : 
      87             : /**
      88             :  * We're being aborted with CTRL-C (or SIGTERM). Shut down.
      89             :  *
      90             :  * @param cls closure
      91             :  */
      92             : static void
      93          20 : shutdown_task (void *cls)
      94             : {
      95             :   (void) cls;
      96          20 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
      97             :               "Running shutdown\n");
      98          20 :   if (NULL != task)
      99             :   {
     100           0 :     GNUNET_SCHEDULER_cancel (task);
     101           0 :     task = NULL;
     102             :   }
     103          20 :   TALER_EXCHANGEDB_plugin_unload (db_plugin);
     104          20 :   db_plugin = NULL;
     105          20 :   TALER_EXCHANGEDB_unload_accounts ();
     106          20 :   cfg = NULL;
     107          20 : }
     108             : 
     109             : 
     110             : /**
     111             :  * Parse the configuration for wirewatch.
     112             :  *
     113             :  * @return #GNUNET_OK on success
     114             :  */
     115             : static int
     116          20 : parse_wirewatch_config (void)
     117             : {
     118          20 :   if (GNUNET_OK !=
     119          20 :       GNUNET_CONFIGURATION_get_value_string (cfg,
     120             :                                              "exchange",
     121             :                                              "BASE_URL",
     122             :                                              &exchange_base_url))
     123             :   {
     124           0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
     125             :                                "exchange",
     126             :                                "BASE_URL");
     127           0 :     return GNUNET_SYSERR;
     128             :   }
     129          20 :   if (GNUNET_OK !=
     130          20 :       GNUNET_CONFIGURATION_get_value_time (cfg,
     131             :                                            "exchange",
     132             :                                            "AGGREGATOR_IDLE_SLEEP_INTERVAL",
     133             :                                            &aggregator_idle_sleep_interval))
     134             :   {
     135           0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
     136             :                                "exchange",
     137             :                                "AGGREGATOR_IDLE_SLEEP_INTERVAL");
     138           0 :     return GNUNET_SYSERR;
     139             :   }
     140          20 :   if ( (GNUNET_OK !=
     141          20 :         TALER_config_get_amount (cfg,
     142             :                                  "taler",
     143             :                                  "CURRENCY_ROUND_UNIT",
     144          20 :                                  &currency_round_unit)) ||
     145          20 :        ( (0 != currency_round_unit.fraction) &&
     146          20 :          (0 != currency_round_unit.value) ) )
     147             :   {
     148           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     149             :                 "Need non-zero amount in section `TALER' under `CURRENCY_ROUND_UNIT'\n");
     150           0 :     return GNUNET_SYSERR;
     151             :   }
     152             : 
     153          20 :   if (NULL ==
     154          20 :       (db_plugin = TALER_EXCHANGEDB_plugin_load (cfg)))
     155             :   {
     156           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     157             :                 "Failed to initialize DB subsystem\n");
     158           0 :     return GNUNET_SYSERR;
     159             :   }
     160          20 :   if (GNUNET_OK !=
     161          20 :       TALER_EXCHANGEDB_load_accounts (cfg,
     162             :                                       TALER_EXCHANGEDB_ALO_DEBIT))
     163             :   {
     164           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     165             :                 "No wire accounts configured for debit!\n");
     166           0 :     TALER_EXCHANGEDB_plugin_unload (db_plugin);
     167           0 :     db_plugin = NULL;
     168           0 :     return GNUNET_SYSERR;
     169             :   }
     170          20 :   return GNUNET_OK;
     171             : }
     172             : 
     173             : 
     174             : /**
     175             :  * Perform a database commit. If it fails, print a warning.
     176             :  *
     177             :  * @return status of commit
     178             :  */
     179             : static enum GNUNET_DB_QueryStatus
     180          51 : commit_or_warn (void)
     181             : {
     182             :   enum GNUNET_DB_QueryStatus qs;
     183             : 
     184          51 :   qs = db_plugin->commit (db_plugin->cls);
     185          51 :   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     186          51 :     return qs;
     187           0 :   GNUNET_log ((GNUNET_DB_STATUS_SOFT_ERROR == qs)
     188             :               ? GNUNET_ERROR_TYPE_INFO
     189             :               : GNUNET_ERROR_TYPE_ERROR,
     190             :               "Failed to commit database transaction!\n");
     191           0 :   return qs;
     192             : }
     193             : 
     194             : 
     195             : /**
     196             :  * Function called with details about expired reserves.
     197             :  * We trigger the reserve closure by inserting the respective
     198             :  * closing record and prewire instructions into the respective
     199             :  * tables.
     200             :  *
     201             :  * @param cls NULL
     202             :  * @param reserve_pub public key of the reserve
     203             :  * @param left amount left in the reserve
     204             :  * @param account_payto_uri information about the bank account that initially
     205             :  *        caused the reserve to be created
     206             :  * @param expiration_date when did the reserve expire
     207             :  * @return transaction status code
     208             :  */
     209             : static enum GNUNET_DB_QueryStatus
     210          29 : expired_reserve_cb (void *cls,
     211             :                     const struct TALER_ReservePublicKeyP *reserve_pub,
     212             :                     const struct TALER_Amount *left,
     213             :                     const char *account_payto_uri,
     214             :                     struct GNUNET_TIME_Absolute expiration_date)
     215             : {
     216             :   struct GNUNET_TIME_Absolute now;
     217             :   struct TALER_WireTransferIdentifierRawP wtid;
     218             :   struct TALER_Amount amount_without_fee;
     219             :   struct TALER_Amount closing_fee;
     220             :   int ret;
     221             :   enum GNUNET_DB_QueryStatus qs;
     222             :   const struct TALER_EXCHANGEDB_AccountInfo *wa;
     223             : 
     224             :   (void) cls;
     225             :   /* NOTE: potential optimization: use custom SQL API to not
     226             :      fetch this: */
     227          29 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     228             :               "Processing reserve closure at %s\n",
     229             :               GNUNET_STRINGS_absolute_time_to_string (expiration_date));
     230          29 :   now = GNUNET_TIME_absolute_get ();
     231          29 :   (void) GNUNET_TIME_round_abs (&now);
     232             : 
     233             :   /* lookup account we should use */
     234          29 :   wa = TALER_EXCHANGEDB_find_account_by_payto_uri (account_payto_uri);
     235          29 :   if (NULL == wa)
     236             :   {
     237           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     238             :                 "No wire account configured to deal with target URI `%s'\n",
     239             :                 account_payto_uri);
     240           0 :     global_ret = EXIT_FAILURE;
     241           0 :     GNUNET_SCHEDULER_shutdown ();
     242           0 :     return GNUNET_DB_STATUS_HARD_ERROR;
     243             :   }
     244             : 
     245             :   /* lookup `closing_fee` from time of actual reserve expiration
     246             :      (we may be lagging behind!) */
     247             :   {
     248             :     struct TALER_Amount wire_fee;
     249             :     struct GNUNET_TIME_Absolute start_date;
     250             :     struct GNUNET_TIME_Absolute end_date;
     251             :     struct TALER_MasterSignatureP master_sig;
     252             :     enum GNUNET_DB_QueryStatus qs;
     253             : 
     254          29 :     qs = db_plugin->get_wire_fee (db_plugin->cls,
     255             :                                   wa->method,
     256             :                                   expiration_date,
     257             :                                   &start_date,
     258             :                                   &end_date,
     259             :                                   &wire_fee,
     260             :                                   &closing_fee,
     261             :                                   &master_sig);
     262          29 :     if (0 >= qs)
     263             :     {
     264           0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     265             :                   "Could not get wire fees for %s at %s. Aborting run.\n",
     266             :                   wa->method,
     267             :                   GNUNET_STRINGS_absolute_time_to_string (expiration_date));
     268           0 :       return GNUNET_DB_STATUS_HARD_ERROR;
     269             :     }
     270             :   }
     271             : 
     272             :   /* calculate transfer amount */
     273          29 :   ret = TALER_amount_subtract (&amount_without_fee,
     274             :                                left,
     275             :                                &closing_fee);
     276          29 :   if ( (GNUNET_SYSERR == ret) ||
     277             :        (GNUNET_NO == ret) )
     278             :   {
     279             :     /* Closing fee higher than or equal to remaining balance, close
     280             :        without wire transfer. */
     281          22 :     closing_fee = *left;
     282          22 :     GNUNET_assert (GNUNET_OK ==
     283             :                    TALER_amount_set_zero (left->currency,
     284             :                                           &amount_without_fee));
     285             :   }
     286             :   /* round down to enable transfer */
     287          29 :   if (GNUNET_SYSERR ==
     288          29 :       TALER_amount_round_down (&amount_without_fee,
     289             :                                &currency_round_unit))
     290             :   {
     291           0 :     GNUNET_break (0);
     292           0 :     global_ret = EXIT_FAILURE;
     293           0 :     GNUNET_SCHEDULER_shutdown ();
     294           0 :     return GNUNET_DB_STATUS_HARD_ERROR;
     295             :   }
     296          29 :   if ( (0 == amount_without_fee.value) &&
     297          22 :        (0 == amount_without_fee.fraction) )
     298          22 :     ret = GNUNET_NO;
     299             : 
     300             :   /* NOTE: sizeof (*reserve_pub) == sizeof (wtid) right now, but to
     301             :      be future-compatible, we use the memset + min construction */
     302          29 :   memset (&wtid,
     303             :           0,
     304             :           sizeof (wtid));
     305          29 :   memcpy (&wtid,
     306             :           reserve_pub,
     307             :           GNUNET_MIN (sizeof (wtid),
     308             :                       sizeof (*reserve_pub)));
     309          29 :   if (GNUNET_SYSERR != ret)
     310          29 :     qs = db_plugin->insert_reserve_closed (db_plugin->cls,
     311             :                                            reserve_pub,
     312             :                                            now,
     313             :                                            account_payto_uri,
     314             :                                            &wtid,
     315             :                                            left,
     316             :                                            &closing_fee);
     317             :   else
     318           0 :     qs = GNUNET_DB_STATUS_HARD_ERROR;
     319          29 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     320             :               "Closing reserve %s over %s (%d, %d)\n",
     321             :               TALER_B2S (reserve_pub),
     322             :               TALER_amount2s (left),
     323             :               ret,
     324             :               qs);
     325             :   /* Check for hard failure */
     326          29 :   if ( (GNUNET_SYSERR == ret) ||
     327             :        (GNUNET_DB_STATUS_HARD_ERROR == qs) )
     328             :   {
     329           0 :     GNUNET_break (0);
     330           0 :     global_ret = EXIT_FAILURE;
     331           0 :     GNUNET_SCHEDULER_shutdown ();
     332           0 :     return GNUNET_DB_STATUS_HARD_ERROR;
     333             :   }
     334          29 :   if ( (GNUNET_OK != ret) ||
     335             :        (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) )
     336             :   {
     337             :     /* Reserve balance was almost zero OR soft error */
     338          22 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     339             :                 "Reserve was virtually empty, moving on\n");
     340          22 :     (void) commit_or_warn ();
     341          22 :     return qs;
     342             :   }
     343             : 
     344             :   /* success, perform wire transfer */
     345             :   {
     346             :     void *buf;
     347             :     size_t buf_size;
     348             : 
     349           7 :     TALER_BANK_prepare_transfer (account_payto_uri,
     350             :                                  &amount_without_fee,
     351             :                                  exchange_base_url,
     352             :                                  &wtid,
     353             :                                  &buf,
     354             :                                  &buf_size);
     355             :     /* Commit our intention to execute the wire transfer! */
     356           7 :     qs = db_plugin->wire_prepare_data_insert (db_plugin->cls,
     357             :                                               wa->method,
     358             :                                               buf,
     359             :                                               buf_size);
     360           7 :     GNUNET_free (buf);
     361             :   }
     362           7 :   if (GNUNET_DB_STATUS_HARD_ERROR == qs)
     363             :   {
     364           0 :     GNUNET_break (0);
     365           0 :     global_ret = EXIT_FAILURE;
     366           0 :     GNUNET_SCHEDULER_shutdown ();
     367           0 :     return GNUNET_DB_STATUS_HARD_ERROR;
     368             :   }
     369           7 :   if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     370             :   {
     371             :     /* start again */
     372           0 :     return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
     373             :   }
     374           7 :   return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
     375             : }
     376             : 
     377             : 
     378             : /**
     379             :  * Main work function that finds and triggers transfers for reserves
     380             :  * closures.
     381             :  *
     382             :  * @param cls closure
     383             :  */
     384             : static void
     385          49 : run_reserve_closures (void *cls)
     386             : {
     387             :   enum GNUNET_DB_QueryStatus qs;
     388             :   struct GNUNET_TIME_Absolute now;
     389             : 
     390             :   (void) cls;
     391          49 :   task = NULL;
     392          49 :   if (GNUNET_SYSERR ==
     393          49 :       db_plugin->preflight (db_plugin->cls))
     394             :   {
     395           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     396             :                 "Failed to obtain database connection!\n");
     397           0 :     global_ret = EXIT_FAILURE;
     398           0 :     GNUNET_SCHEDULER_shutdown ();
     399          49 :     return;
     400             :   }
     401             : 
     402          49 :   if (GNUNET_OK !=
     403          49 :       db_plugin->start (db_plugin->cls,
     404             :                         "aggregator reserve closures"))
     405             :   {
     406           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     407             :                 "Failed to start database transaction!\n");
     408           0 :     global_ret = EXIT_FAILURE;
     409           0 :     GNUNET_SCHEDULER_shutdown ();
     410           0 :     return;
     411             :   }
     412          49 :   now = GNUNET_TIME_absolute_get ();
     413          49 :   (void) GNUNET_TIME_round_abs (&now);
     414          49 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     415             :               "Checking for reserves to close by date %s\n",
     416             :               GNUNET_STRINGS_absolute_time_to_string (now));
     417          49 :   qs = db_plugin->get_expired_reserves (db_plugin->cls,
     418             :                                         now,
     419             :                                         &expired_reserve_cb,
     420             :                                         NULL);
     421          49 :   GNUNET_assert (1 >= qs);
     422          49 :   switch (qs)
     423             :   {
     424           0 :   case GNUNET_DB_STATUS_HARD_ERROR:
     425           0 :     GNUNET_break (0);
     426           0 :     db_plugin->rollback (db_plugin->cls);
     427           0 :     global_ret = EXIT_FAILURE;
     428           0 :     GNUNET_SCHEDULER_shutdown ();
     429           0 :     return;
     430           0 :   case GNUNET_DB_STATUS_SOFT_ERROR:
     431           0 :     db_plugin->rollback (db_plugin->cls);
     432           0 :     GNUNET_assert (NULL == task);
     433           0 :     task = GNUNET_SCHEDULER_add_now (&run_reserve_closures,
     434             :                                      NULL);
     435           0 :     return;
     436          20 :   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
     437          20 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     438             :                 "No more idle reserves to close, going to sleep.\n");
     439          20 :     db_plugin->rollback (db_plugin->cls);
     440          20 :     GNUNET_assert (NULL == task);
     441          20 :     if (GNUNET_YES == test_mode)
     442             :     {
     443          20 :       GNUNET_SCHEDULER_shutdown ();
     444             :     }
     445             :     else
     446             :     {
     447           0 :       task = GNUNET_SCHEDULER_add_delayed (aggregator_idle_sleep_interval,
     448             :                                            &run_reserve_closures,
     449             :                                            NULL);
     450             :     }
     451          20 :     return;
     452          29 :   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
     453          29 :     (void) commit_or_warn ();
     454          29 :     GNUNET_assert (NULL == task);
     455          29 :     task = GNUNET_SCHEDULER_add_now (&run_reserve_closures,
     456             :                                      NULL);
     457          29 :     return;
     458             :   }
     459             : }
     460             : 
     461             : 
     462             : /**
     463             :  * First task.  Parses the configuration and starts the
     464             :  * main loop of #run_reserve_closures(). Also schedules
     465             :  * the #shutdown_task() to clean up.
     466             :  *
     467             :  * @param cls closure, NULL
     468             :  * @param args remaining command-line arguments
     469             :  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
     470             :  * @param c configuration
     471             :  */
     472             : static void
     473          20 : run (void *cls,
     474             :      char *const *args,
     475             :      const char *cfgfile,
     476             :      const struct GNUNET_CONFIGURATION_Handle *c)
     477             : {
     478             :   (void) cls;
     479             :   (void) args;
     480             :   (void) cfgfile;
     481             : 
     482          20 :   cfg = c;
     483          20 :   if (GNUNET_OK != parse_wirewatch_config ())
     484             :   {
     485           0 :     cfg = NULL;
     486           0 :     global_ret = EXIT_NOTCONFIGURED;
     487           0 :     return;
     488             :   }
     489          20 :   GNUNET_assert (NULL == task);
     490          20 :   task = GNUNET_SCHEDULER_add_now (&run_reserve_closures,
     491             :                                    NULL);
     492          20 :   GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
     493             :                                  cls);
     494             : }
     495             : 
     496             : 
     497             : /**
     498             :  * The main function of the taler-exchange-closer.
     499             :  *
     500             :  * @param argc number of arguments from the command line
     501             :  * @param argv command line arguments
     502             :  * @return 0 ok, non-zero on error
     503             :  */
     504             : int
     505          20 : main (int argc,
     506             :       char *const *argv)
     507             : {
     508          20 :   struct GNUNET_GETOPT_CommandLineOption options[] = {
     509          20 :     GNUNET_GETOPT_option_timetravel ('T',
     510             :                                      "timetravel"),
     511          20 :     GNUNET_GETOPT_option_flag ('t',
     512             :                                "test",
     513             :                                "run in test mode and exit when idle",
     514             :                                &test_mode),
     515             :     GNUNET_GETOPT_OPTION_END
     516             :   };
     517             :   enum GNUNET_GenericReturnValue ret;
     518             : 
     519          20 :   if (GNUNET_OK !=
     520          20 :       GNUNET_STRINGS_get_utf8_args (argc, argv,
     521             :                                     &argc, &argv))
     522           0 :     return EXIT_INVALIDARGUMENT;
     523          20 :   TALER_OS_init ();
     524          20 :   ret = GNUNET_PROGRAM_run (
     525             :     argc, argv,
     526             :     "taler-exchange-closer",
     527             :     gettext_noop ("background process that closes expired reserves"),
     528             :     options,
     529             :     &run, NULL);
     530          20 :   GNUNET_free_nz ((void *) argv);
     531          20 :   if (GNUNET_SYSERR == ret)
     532           0 :     return EXIT_INVALIDARGUMENT;
     533          20 :   if (GNUNET_NO == ret)
     534           0 :     return EXIT_SUCCESS;
     535          20 :   return global_ret;
     536             : }
     537             : 
     538             : 
     539             : /* end of taler-exchange-closer.c */

Generated by: LCOV version 1.14