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


      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   {
    145     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    146                 "Failed to initialize DB subsystem\n");
    147     return GNUNET_SYSERR;
    148   }
    149   if (GNUNET_OK !=
    150       TALER_EXCHANGEDB_load_accounts (cfg,
    151                                       TALER_EXCHANGEDB_ALO_DEBIT
    152                                       | TALER_EXCHANGEDB_ALO_AUTHDATA))
    153   {
    154     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    155                 "No wire accounts configured for debit!\n");
    156     TALER_EXCHANGEDB_disconnect (pg);
    157     pg = NULL;
    158     return GNUNET_SYSERR;
    159   }
    160   return GNUNET_OK;
    161 }
    162 
    163 
    164 /**
    165  * Perform a database commit. If it fails, print a warning.
    166  *
    167  * @return status of commit
    168  */
    169 static enum GNUNET_DB_QueryStatus
    170 commit_or_warn (void)
    171 {
    172   enum GNUNET_DB_QueryStatus qs;
    173 
    174   qs = TALER_EXCHANGEDB_commit (pg);
    175   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    176     return qs;
    177   GNUNET_log ((GNUNET_DB_STATUS_SOFT_ERROR == qs)
    178               ? GNUNET_ERROR_TYPE_INFO
    179               : GNUNET_ERROR_TYPE_ERROR,
    180               "Failed to commit database transaction!\n");
    181   return qs;
    182 }
    183 
    184 
    185 /**
    186  * Execute a wire drain.
    187  *
    188  * @param cls NULL
    189  */
    190 static void
    191 run_drain (void *cls)
    192 {
    193   enum GNUNET_DB_QueryStatus qs;
    194   uint64_t serial;
    195   struct TALER_WireTransferIdentifierRawP wtid;
    196   char *account_section;
    197   struct TALER_FullPayto payto_uri;
    198   struct GNUNET_TIME_Timestamp request_timestamp;
    199   struct TALER_Amount amount;
    200   struct TALER_MasterSignatureP master_sig;
    201 
    202   (void) cls;
    203   task = NULL;
    204   if (GNUNET_OK !=
    205       TALER_EXCHANGEDB_start (pg,
    206                               "run drain"))
    207   {
    208     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    209                 "Failed to start database transaction!\n");
    210     global_ret = EXIT_FAILURE;
    211     GNUNET_SCHEDULER_shutdown ();
    212     return;
    213   }
    214   qs = TALER_EXCHANGEDB_profit_drains_get_pending (pg,
    215                                                    &serial,
    216                                                    &wtid,
    217                                                    &account_section,
    218                                                    &payto_uri,
    219                                                    &request_timestamp,
    220                                                    &amount,
    221                                                    &master_sig);
    222   switch (qs)
    223   {
    224   case GNUNET_DB_STATUS_HARD_ERROR:
    225     TALER_EXCHANGEDB_rollback (pg);
    226     GNUNET_break (0);
    227     global_ret = EXIT_FAILURE;
    228     GNUNET_SCHEDULER_shutdown ();
    229     return;
    230   case GNUNET_DB_STATUS_SOFT_ERROR:
    231     TALER_EXCHANGEDB_rollback (pg);
    232     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    233                 "Serialization failure on simple SELECT!?\n");
    234     global_ret = EXIT_FAILURE;
    235     GNUNET_SCHEDULER_shutdown ();
    236     return;
    237   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    238     /* no profit drains, finished */
    239     TALER_EXCHANGEDB_rollback (pg);
    240     GNUNET_assert (NULL == task);
    241     GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
    242                 "No profit drains pending. Exiting.\n");
    243     GNUNET_SCHEDULER_shutdown ();
    244     return;
    245   default:
    246     /* continued below */
    247     break;
    248   }
    249   /* Check signature (again, this is a critical operation!) */
    250   if (GNUNET_OK !=
    251       TALER_exchange_offline_profit_drain_verify (
    252         &wtid,
    253         request_timestamp,
    254         &amount,
    255         account_section,
    256         payto_uri,
    257         &master_pub,
    258         &master_sig))
    259   {
    260     GNUNET_break (0);
    261     global_ret = EXIT_FAILURE;
    262     TALER_EXCHANGEDB_rollback (pg);
    263     GNUNET_assert (NULL == task);
    264     GNUNET_SCHEDULER_shutdown ();
    265     return;
    266   }
    267 
    268   /* Display data for manual human check */
    269   fprintf (stdout,
    270            "Critical operation. MANUAL CHECK REQUIRED.\n");
    271   fprintf (stdout,
    272            "We will wire %s to `%s'\n based on instructions from %s.\n",
    273            TALER_amount2s (&amount),
    274            payto_uri.full_payto,
    275            GNUNET_TIME_timestamp2s (request_timestamp));
    276   fprintf (stdout,
    277            "Press ENTER to confirm, CTRL-D to abort.\n");
    278   while (1)
    279   {
    280     int key;
    281 
    282     key = getchar ();
    283     if (EOF == key)
    284     {
    285       fprintf (stdout,
    286                "Transfer aborted.\n"
    287                "Re-run 'taler-exchange-drain' to try it again.\n"
    288                "Contact Taler Systems SA to cancel it for good.\n"
    289                "Exiting.\n");
    290       TALER_EXCHANGEDB_rollback (pg);
    291       GNUNET_free (payto_uri.full_payto);
    292       GNUNET_assert (NULL == task);
    293       GNUNET_SCHEDULER_shutdown ();
    294       global_ret = EXIT_FAILURE;
    295       return;
    296     }
    297     if ('\n' == key)
    298       break;
    299   }
    300 
    301   /* Note: account_section ignored for now, we
    302      might want to use it here in the future... */
    303   (void) account_section;
    304   {
    305     char *method;
    306     void *buf;
    307     size_t buf_size;
    308 
    309     TALER_BANK_prepare_transfer (payto_uri,
    310                                  &amount,
    311                                  exchange_base_url,
    312                                  &wtid,
    313                                  NULL, /* no extra metadata */
    314                                  &buf,
    315                                  &buf_size);
    316     method = TALER_payto_get_method (payto_uri.full_payto);
    317     qs = TALER_EXCHANGEDB_wire_prepare_data_insert (pg,
    318                                                     method,
    319                                                     buf,
    320                                                     buf_size);
    321     GNUNET_free (method);
    322     GNUNET_free (buf);
    323   }
    324   GNUNET_free (payto_uri.full_payto);
    325   qs = TALER_EXCHANGEDB_profit_drains_set_finished (pg,
    326                                                     serial);
    327   switch (qs)
    328   {
    329   case GNUNET_DB_STATUS_HARD_ERROR:
    330     TALER_EXCHANGEDB_rollback (pg);
    331     GNUNET_break (0);
    332     global_ret = EXIT_FAILURE;
    333     GNUNET_SCHEDULER_shutdown ();
    334     return;
    335   case GNUNET_DB_STATUS_SOFT_ERROR:
    336     TALER_EXCHANGEDB_rollback (pg);
    337     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    338                 "Failed: database serialization issue\n");
    339     global_ret = EXIT_FAILURE;
    340     GNUNET_SCHEDULER_shutdown ();
    341     return;
    342   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    343     TALER_EXCHANGEDB_rollback (pg);
    344     GNUNET_assert (NULL == task);
    345     GNUNET_break (0);
    346     GNUNET_SCHEDULER_shutdown ();
    347     return;
    348   default:
    349     /* continued below */
    350     break;
    351   }
    352   /* commit transaction + report success + exit */
    353   if (0 >= commit_or_warn ())
    354     GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
    355                 "Profit drain triggered. Exiting.\n");
    356   GNUNET_SCHEDULER_shutdown ();
    357 }
    358 
    359 
    360 /**
    361  * First task.
    362  *
    363  * @param cls closure, NULL
    364  * @param args remaining command-line arguments
    365  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
    366  * @param c configuration
    367  */
    368 static void
    369 run (void *cls,
    370      char *const *args,
    371      const char *cfgfile,
    372      const struct GNUNET_CONFIGURATION_Handle *c)
    373 {
    374   (void) cls;
    375   (void) args;
    376   (void) cfgfile;
    377 
    378   cfg = c;
    379   if (GNUNET_OK != parse_drain_config ())
    380   {
    381     cfg = NULL;
    382     global_ret = EXIT_NOTCONFIGURED;
    383     return;
    384   }
    385   if (GNUNET_SYSERR ==
    386       TALER_EXCHANGEDB_preflight (pg))
    387   {
    388     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    389                 "Failed to obtain database connection!\n");
    390     global_ret = EXIT_FAILURE;
    391     GNUNET_SCHEDULER_shutdown ();
    392     return;
    393   }
    394   GNUNET_assert (NULL == task);
    395   task = GNUNET_SCHEDULER_add_now (&run_drain,
    396                                    NULL);
    397   GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
    398                                  cls);
    399 }
    400 
    401 
    402 /**
    403  * The main function of the taler-exchange-drain.
    404  *
    405  * @param argc number of arguments from the command line
    406  * @param argv command line arguments
    407  * @return 0 ok, 1 on error
    408  */
    409 int
    410 main (int argc,
    411       char *const *argv)
    412 {
    413   struct GNUNET_GETOPT_CommandLineOption options[] = {
    414     GNUNET_GETOPT_option_version (VERSION),
    415     GNUNET_GETOPT_OPTION_END
    416   };
    417   enum GNUNET_GenericReturnValue ret;
    418 
    419   ret = GNUNET_PROGRAM_run (
    420     TALER_EXCHANGE_project_data (),
    421     argc, argv,
    422     "taler-exchange-drain",
    423     gettext_noop (
    424       "process that executes a single profit drain"),
    425     options,
    426     &run, NULL);
    427   if (GNUNET_SYSERR == ret)
    428     return EXIT_INVALIDARGUMENT;
    429   if (GNUNET_NO == ret)
    430     return EXIT_SUCCESS;
    431   return global_ret;
    432 }
    433 
    434 
    435 /* end of taler-exchange-drain.c */