exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

taler-exchange-drain.c (12152B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2022 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-exchange-drain.c
     18  * @brief Process that drains exchange profits from the escrow account
     19  *        and puts them into some regular account of the exchange.
     20  * @author Christian Grothoff
     21  */
     22 #include "taler/platform.h"
     23 #include <gnunet/gnunet_util_lib.h>
     24 #include <jansson.h>
     25 #include <pthread.h>
     26 #include "taler/taler_exchangedb_lib.h"
     27 #include "taler/taler_exchangedb_plugin.h"
     28 #include "taler/taler_json_lib.h"
     29 #include "taler/taler_bank_service.h"
     30 
     31 
     32 /**
     33  * The exchange's configuration.
     34  */
     35 static const struct GNUNET_CONFIGURATION_Handle *cfg;
     36 
     37 /**
     38  * Our database plugin.
     39  */
     40 static struct TALER_EXCHANGEDB_Plugin *db_plugin;
     41 
     42 /**
     43  * Our master public key.
     44  */
     45 static struct TALER_MasterPublicKeyP master_pub;
     46 
     47 /**
     48  * Next task to run, if any.
     49  */
     50 static struct GNUNET_SCHEDULER_Task *task;
     51 
     52 /**
     53  * Base URL of this exchange.
     54  */
     55 static char *exchange_base_url;
     56 
     57 /**
     58  * Value to return from main(). 0 on success, non-zero on errors.
     59  */
     60 static int global_ret;
     61 
     62 
     63 /**
     64  * We're being aborted with CTRL-C (or SIGTERM). Shut down.
     65  *
     66  * @param cls closure
     67  */
     68 static void
     69 shutdown_task (void *cls)
     70 {
     71   (void) cls;
     72   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     73               "Running shutdown\n");
     74   if (NULL != task)
     75   {
     76     GNUNET_SCHEDULER_cancel (task);
     77     task = NULL;
     78   }
     79   db_plugin->rollback (db_plugin->cls); /* just in case */
     80   TALER_EXCHANGEDB_plugin_unload (db_plugin);
     81   db_plugin = NULL;
     82   TALER_EXCHANGEDB_unload_accounts ();
     83   cfg = NULL;
     84 }
     85 
     86 
     87 /**
     88  * Parse the configuration for drain.
     89  *
     90  * @return #GNUNET_OK on success
     91  */
     92 static enum GNUNET_GenericReturnValue
     93 parse_drain_config (void)
     94 {
     95   if (GNUNET_OK !=
     96       GNUNET_CONFIGURATION_get_value_string (cfg,
     97                                              "exchange",
     98                                              "BASE_URL",
     99                                              &exchange_base_url))
    100   {
    101     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    102                                "exchange",
    103                                "BASE_URL");
    104     return GNUNET_SYSERR;
    105   }
    106 
    107   {
    108     char *master_public_key_str;
    109 
    110     if (GNUNET_OK !=
    111         GNUNET_CONFIGURATION_get_value_string (cfg,
    112                                                "exchange",
    113                                                "MASTER_PUBLIC_KEY",
    114                                                &master_public_key_str))
    115     {
    116       GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    117                                  "exchange",
    118                                  "MASTER_PUBLIC_KEY");
    119       return GNUNET_SYSERR;
    120     }
    121     if (GNUNET_OK !=
    122         GNUNET_CRYPTO_eddsa_public_key_from_string (master_public_key_str,
    123                                                     strlen (
    124                                                       master_public_key_str),
    125                                                     &master_pub.eddsa_pub))
    126     {
    127       GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    128                                  "exchange",
    129                                  "MASTER_PUBLIC_KEY",
    130                                  "invalid base32 encoding for a master public key");
    131       GNUNET_free (master_public_key_str);
    132       return GNUNET_SYSERR;
    133     }
    134     GNUNET_free (master_public_key_str);
    135   }
    136   if (NULL ==
    137       (db_plugin = TALER_EXCHANGEDB_plugin_load (cfg,
    138                                                  false)))
    139   {
    140     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    141                 "Failed to initialize DB subsystem\n");
    142     return GNUNET_SYSERR;
    143   }
    144   if (GNUNET_OK !=
    145       TALER_EXCHANGEDB_load_accounts (cfg,
    146                                       TALER_EXCHANGEDB_ALO_DEBIT
    147                                       | TALER_EXCHANGEDB_ALO_AUTHDATA))
    148   {
    149     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    150                 "No wire accounts configured for debit!\n");
    151     TALER_EXCHANGEDB_plugin_unload (db_plugin);
    152     db_plugin = NULL;
    153     return GNUNET_SYSERR;
    154   }
    155   return GNUNET_OK;
    156 }
    157 
    158 
    159 /**
    160  * Perform a database commit. If it fails, print a warning.
    161  *
    162  * @return status of commit
    163  */
    164 static enum GNUNET_DB_QueryStatus
    165 commit_or_warn (void)
    166 {
    167   enum GNUNET_DB_QueryStatus qs;
    168 
    169   qs = db_plugin->commit (db_plugin->cls);
    170   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    171     return qs;
    172   GNUNET_log ((GNUNET_DB_STATUS_SOFT_ERROR == qs)
    173               ? GNUNET_ERROR_TYPE_INFO
    174               : GNUNET_ERROR_TYPE_ERROR,
    175               "Failed to commit database transaction!\n");
    176   return qs;
    177 }
    178 
    179 
    180 /**
    181  * Execute a wire drain.
    182  *
    183  * @param cls NULL
    184  */
    185 static void
    186 run_drain (void *cls)
    187 {
    188   enum GNUNET_DB_QueryStatus qs;
    189   uint64_t serial;
    190   struct TALER_WireTransferIdentifierRawP wtid;
    191   char *account_section;
    192   struct TALER_FullPayto payto_uri;
    193   struct GNUNET_TIME_Timestamp request_timestamp;
    194   struct TALER_Amount amount;
    195   struct TALER_MasterSignatureP master_sig;
    196 
    197   (void) cls;
    198   task = NULL;
    199   if (GNUNET_OK !=
    200       db_plugin->start (db_plugin->cls,
    201                         "run drain"))
    202   {
    203     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    204                 "Failed to start database transaction!\n");
    205     global_ret = EXIT_FAILURE;
    206     GNUNET_SCHEDULER_shutdown ();
    207     return;
    208   }
    209   qs = db_plugin->profit_drains_get_pending (db_plugin->cls,
    210                                              &serial,
    211                                              &wtid,
    212                                              &account_section,
    213                                              &payto_uri,
    214                                              &request_timestamp,
    215                                              &amount,
    216                                              &master_sig);
    217   switch (qs)
    218   {
    219   case GNUNET_DB_STATUS_HARD_ERROR:
    220     db_plugin->rollback (db_plugin->cls);
    221     GNUNET_break (0);
    222     global_ret = EXIT_FAILURE;
    223     GNUNET_SCHEDULER_shutdown ();
    224     return;
    225   case GNUNET_DB_STATUS_SOFT_ERROR:
    226     db_plugin->rollback (db_plugin->cls);
    227     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    228                 "Serialization failure on simple SELECT!?\n");
    229     global_ret = EXIT_FAILURE;
    230     GNUNET_SCHEDULER_shutdown ();
    231     return;
    232   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    233     /* no profit drains, finished */
    234     db_plugin->rollback (db_plugin->cls);
    235     GNUNET_assert (NULL == task);
    236     GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
    237                 "No profit drains pending. Exiting.\n");
    238     GNUNET_SCHEDULER_shutdown ();
    239     return;
    240   default:
    241     /* continued below */
    242     break;
    243   }
    244   /* Check signature (again, this is a critical operation!) */
    245   if (GNUNET_OK !=
    246       TALER_exchange_offline_profit_drain_verify (
    247         &wtid,
    248         request_timestamp,
    249         &amount,
    250         account_section,
    251         payto_uri,
    252         &master_pub,
    253         &master_sig))
    254   {
    255     GNUNET_break (0);
    256     global_ret = EXIT_FAILURE;
    257     db_plugin->rollback (db_plugin->cls);
    258     GNUNET_assert (NULL == task);
    259     GNUNET_SCHEDULER_shutdown ();
    260     return;
    261   }
    262 
    263   /* Display data for manual human check */
    264   fprintf (stdout,
    265            "Critical operation. MANUAL CHECK REQUIRED.\n");
    266   fprintf (stdout,
    267            "We will wire %s to `%s'\n based on instructions from %s.\n",
    268            TALER_amount2s (&amount),
    269            payto_uri.full_payto,
    270            GNUNET_TIME_timestamp2s (request_timestamp));
    271   fprintf (stdout,
    272            "Press ENTER to confirm, CTRL-D to abort.\n");
    273   while (1)
    274   {
    275     int key;
    276 
    277     key = getchar ();
    278     if (EOF == key)
    279     {
    280       fprintf (stdout,
    281                "Transfer aborted.\n"
    282                "Re-run 'taler-exchange-drain' to try it again.\n"
    283                "Contact Taler Systems SA to cancel it for good.\n"
    284                "Exiting.\n");
    285       db_plugin->rollback (db_plugin->cls);
    286       GNUNET_free (payto_uri.full_payto);
    287       GNUNET_assert (NULL == task);
    288       GNUNET_SCHEDULER_shutdown ();
    289       global_ret = EXIT_FAILURE;
    290       return;
    291     }
    292     if ('\n' == key)
    293       break;
    294   }
    295 
    296   /* Note: account_section ignored for now, we
    297      might want to use it here in the future... */
    298   (void) account_section;
    299   {
    300     char *method;
    301     void *buf;
    302     size_t buf_size;
    303 
    304     TALER_BANK_prepare_transfer (payto_uri,
    305                                  &amount,
    306                                  exchange_base_url,
    307                                  &wtid,
    308                                  &buf,
    309                                  &buf_size);
    310     method = TALER_payto_get_method (payto_uri.full_payto);
    311     qs = db_plugin->wire_prepare_data_insert (db_plugin->cls,
    312                                               method,
    313                                               buf,
    314                                               buf_size);
    315     GNUNET_free (method);
    316     GNUNET_free (buf);
    317   }
    318   GNUNET_free (payto_uri.full_payto);
    319   qs = db_plugin->profit_drains_set_finished (db_plugin->cls,
    320                                               serial);
    321   switch (qs)
    322   {
    323   case GNUNET_DB_STATUS_HARD_ERROR:
    324     db_plugin->rollback (db_plugin->cls);
    325     GNUNET_break (0);
    326     global_ret = EXIT_FAILURE;
    327     GNUNET_SCHEDULER_shutdown ();
    328     return;
    329   case GNUNET_DB_STATUS_SOFT_ERROR:
    330     db_plugin->rollback (db_plugin->cls);
    331     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    332                 "Failed: database serialization issue\n");
    333     global_ret = EXIT_FAILURE;
    334     GNUNET_SCHEDULER_shutdown ();
    335     return;
    336   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    337     db_plugin->rollback (db_plugin->cls);
    338     GNUNET_assert (NULL == task);
    339     GNUNET_break (0);
    340     GNUNET_SCHEDULER_shutdown ();
    341     return;
    342   default:
    343     /* continued below */
    344     break;
    345   }
    346   /* commit transaction + report success + exit */
    347   if (0 >= commit_or_warn ())
    348     GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
    349                 "Profit drain triggered. Exiting.\n");
    350   GNUNET_SCHEDULER_shutdown ();
    351 }
    352 
    353 
    354 /**
    355  * First task.
    356  *
    357  * @param cls closure, NULL
    358  * @param args remaining command-line arguments
    359  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
    360  * @param c configuration
    361  */
    362 static void
    363 run (void *cls,
    364      char *const *args,
    365      const char *cfgfile,
    366      const struct GNUNET_CONFIGURATION_Handle *c)
    367 {
    368   (void) cls;
    369   (void) args;
    370   (void) cfgfile;
    371 
    372   cfg = c;
    373   if (GNUNET_OK != parse_drain_config ())
    374   {
    375     cfg = NULL;
    376     global_ret = EXIT_NOTCONFIGURED;
    377     return;
    378   }
    379   if (GNUNET_SYSERR ==
    380       db_plugin->preflight (db_plugin->cls))
    381   {
    382     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    383                 "Failed to obtain database connection!\n");
    384     global_ret = EXIT_FAILURE;
    385     GNUNET_SCHEDULER_shutdown ();
    386     return;
    387   }
    388   GNUNET_assert (NULL == task);
    389   task = GNUNET_SCHEDULER_add_now (&run_drain,
    390                                    NULL);
    391   GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
    392                                  cls);
    393 }
    394 
    395 
    396 /**
    397  * The main function of the taler-exchange-drain.
    398  *
    399  * @param argc number of arguments from the command line
    400  * @param argv command line arguments
    401  * @return 0 ok, 1 on error
    402  */
    403 int
    404 main (int argc,
    405       char *const *argv)
    406 {
    407   struct GNUNET_GETOPT_CommandLineOption options[] = {
    408     GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION),
    409     GNUNET_GETOPT_OPTION_END
    410   };
    411   enum GNUNET_GenericReturnValue ret;
    412 
    413   ret = GNUNET_PROGRAM_run (
    414     TALER_EXCHANGE_project_data (),
    415     argc, argv,
    416     "taler-exchange-drain",
    417     gettext_noop (
    418       "process that executes a single profit drain"),
    419     options,
    420     &run, NULL);
    421   if (GNUNET_SYSERR == ret)
    422     return EXIT_INVALIDARGUMENT;
    423   if (GNUNET_NO == ret)
    424     return EXIT_SUCCESS;
    425   return global_ret;
    426 }
    427 
    428 
    429 /* end of taler-exchange-drain.c */