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 (12464B)


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