exchange

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

pg.c (9881B)


      1 /*
      2    This file is part of TALER
      3    Copyright (C) 2014--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 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 General Public License for more details.
     12 
     13    You should have received a copy of the GNU General Public License along with
     14    TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15  */
     16 
     17 /**
     18  * @file pg.c
     19  * @brief Low-level (statement-level) Postgres database access for the exchange
     20  * @author Florian Dold
     21  * @author Christian Grothoff
     22  * @author Sree Harsha Totakura
     23  * @author Marcello Stanisci
     24  * @author Özgür Kesim
     25  */
     26 #include <poll.h>
     27 #include <pthread.h>
     28 #include <libpq-fe.h>
     29 struct TALER_EXCHANGEDB_PostgresContext;
     30 #define GNUNET_PQ_RECONNECT_CALLBACK_CLOSURE \
     31         struct TALER_EXCHANGEDB_PostgresContext
     32 #include "helper.h"
     33 #include "exchangedb_lib.h"
     34 #include "exchange-database/preflight.h"
     35 
     36 /**
     37  * Set to 1 to enable Postgres auto_explain module. This will
     38  * slow down things a _lot_, but also provide extensive logging
     39  * in the Postgres database logger for performance analysis.
     40  */
     41 #define AUTO_EXPLAIN 0
     42 
     43 /**
     44  * Counts how often we have established a fresh @e conn
     45  * to the database. Used to re-prepare statements.
     46  */
     47 unsigned long long TEH_PG_prep_gen_;
     48 
     49 /**
     50  * Function called each time we connect or reconnect to the
     51  * database. Gives the application a chance to run some
     52  * per-connection initialization logic.
     53  *
     54  * @param pg database context of the exchange
     55  * @param pq database connection handle
     56  */
     57 static void
     58 reconnect_cb (struct TALER_EXCHANGEDB_PostgresContext *pg,
     59               struct GNUNET_PQ_Context *pq)
     60 {
     61 #if AUTO_EXPLAIN
     62   /* Enable verbose logging to see where queries do not
     63      properly use indices */
     64   struct GNUNET_PQ_ExecuteStatement es[] = {
     65     GNUNET_PQ_make_try_execute ("LOAD 'auto_explain';"),
     66     GNUNET_PQ_make_try_execute ("SET auto_explain.log_min_duration=50;"),
     67     GNUNET_PQ_make_try_execute ("SET auto_explain.log_timing=TRUE;"),
     68     GNUNET_PQ_make_try_execute ("SET auto_explain.log_analyze=TRUE;"),
     69     /* https://wiki.postgresql.org/wiki/Serializable suggests to really
     70        force the default to 'serializable' if SSI is to be used. */
     71     GNUNET_PQ_make_try_execute (
     72       "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;"),
     73     GNUNET_PQ_make_try_execute ("SET enable_sort=OFF;"),
     74     GNUNET_PQ_make_try_execute ("SET enable_seqscan=OFF;"),
     75     GNUNET_PQ_make_try_execute ("SET search_path TO exchange;"),
     76     /* Mergejoin causes issues, see Postgres #18380 */
     77     GNUNET_PQ_make_try_execute ("SET enable_mergejoin=OFF;"),
     78     GNUNET_PQ_EXECUTE_STATEMENT_END
     79   };
     80 #else
     81   struct GNUNET_PQ_ExecuteStatement es[] = {
     82     GNUNET_PQ_make_try_execute (
     83       "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;"),
     84     GNUNET_PQ_make_try_execute ("SET enable_sort=OFF;"),
     85     GNUNET_PQ_make_try_execute ("SET enable_seqscan=OFF;"),
     86     /* Mergejoin causes issues, see Postgres #18380 */
     87     GNUNET_PQ_make_try_execute ("SET enable_mergejoin=OFF;"),
     88     GNUNET_PQ_make_try_execute ("SET search_path TO exchange;"),
     89     GNUNET_PQ_EXECUTE_STATEMENT_END
     90   };
     91 #endif
     92 
     93   if (GNUNET_OK !=
     94       GNUNET_PQ_exec_statements (pq,
     95                                  es))
     96   {
     97     GNUNET_break (0);
     98     return;
     99   }
    100   {
    101     char *set_time;
    102     enum GNUNET_GenericReturnValue ret;
    103 
    104     GNUNET_asprintf (&set_time,
    105                      "SET taler.timetravel_us = '%lld'",
    106                      GNUNET_TIME_get_offset ());
    107     {
    108       struct GNUNET_PQ_ExecuteStatement time_es[] = {
    109         GNUNET_PQ_make_execute (set_time),
    110         GNUNET_PQ_EXECUTE_STATEMENT_END
    111       };
    112 
    113       ret = GNUNET_PQ_exec_statements (pq,
    114                                       time_es);
    115     }
    116     GNUNET_free (set_time);
    117     if (GNUNET_OK != ret)
    118     {
    119       GNUNET_break (0);
    120       return;
    121     }
    122   }
    123   TEH_PG_prep_gen_++;
    124 }
    125 
    126 
    127 /**
    128  * Connect to the db if the connection does not exist yet.
    129  *
    130  * @param[in,out] pg the database state
    131  * @return #GNUNET_OK on success
    132  */
    133 static enum GNUNET_GenericReturnValue
    134 internal_setup (struct TALER_EXCHANGEDB_PostgresContext *pg)
    135 {
    136   struct GNUNET_PQ_Context *db_conn;
    137 
    138   if (NULL != pg->conn)
    139     return GNUNET_OK;
    140   db_conn = GNUNET_PQ_init (pg->cfg,
    141                             "exchangedb-postgres",
    142                             &reconnect_cb,
    143                             pg);
    144   if (NULL == db_conn)
    145     return GNUNET_SYSERR;
    146   if (0 == TEH_PG_prep_gen_)
    147   {
    148     GNUNET_PQ_disconnect (db_conn);
    149     return GNUNET_SYSERR;
    150   }
    151   pg->conn = db_conn;
    152   return GNUNET_OK;
    153 }
    154 
    155 
    156 /**
    157  * Initialize the database connection.
    158  *
    159  * @param cfg configuration to use
    160  * @param check_current true to check if the database schema is current
    161  * @return NULL on failure
    162  */
    163 static struct TALER_EXCHANGEDB_PostgresContext *
    164 do_connect (const struct GNUNET_CONFIGURATION_Handle *cfg,
    165             bool check_current)
    166 {
    167   struct TALER_EXCHANGEDB_PostgresContext *pg;
    168   unsigned long long dpl;
    169 
    170   pg = GNUNET_new (struct TALER_EXCHANGEDB_PostgresContext);
    171   pg->cfg = cfg;
    172   if (GNUNET_OK !=
    173       GNUNET_CONFIGURATION_get_value_filename (cfg,
    174                                                "exchangedb-postgres",
    175                                                "SQL_DIR",
    176                                                &pg->sql_dir))
    177   {
    178     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    179                                "exchangedb-postgres",
    180                                "SQL_DIR");
    181     goto fail;
    182   }
    183   if (GNUNET_OK !=
    184       GNUNET_CONFIGURATION_get_value_string (cfg,
    185                                              "exchange",
    186                                              "BASE_URL",
    187                                              &pg->exchange_url))
    188   {
    189     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    190                                "exchange",
    191                                "BASE_URL");
    192     goto fail;
    193   }
    194   if (GNUNET_OK !=
    195       GNUNET_CONFIGURATION_get_value_time (cfg,
    196                                            "exchangedb",
    197                                            "IDLE_RESERVE_EXPIRATION_TIME",
    198                                            &pg->idle_reserve_expiration_time))
    199   {
    200     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    201                                "exchangedb",
    202                                "IDLE_RESERVE_EXPIRATION_TIME");
    203     goto fail;
    204   }
    205   if (GNUNET_OK !=
    206       GNUNET_CONFIGURATION_get_value_time (cfg,
    207                                            "exchangedb",
    208                                            "MAX_AML_PROGRAM_RUNTIME",
    209                                            &pg->max_aml_program_runtime))
    210   {
    211     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    212                                "exchangedb",
    213                                "MAX_AML_PROGRAM_RUNTIME");
    214     goto fail;
    215   }
    216   if (GNUNET_OK !=
    217       GNUNET_CONFIGURATION_get_value_time (cfg,
    218                                            "exchangedb",
    219                                            "LEGAL_RESERVE_EXPIRATION_TIME",
    220                                            &pg->legal_reserve_expiration_time))
    221   {
    222     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    223                                "exchangedb",
    224                                "LEGAL_RESERVE_EXPIRATION_TIME");
    225     goto fail;
    226   }
    227   if (GNUNET_OK !=
    228       GNUNET_CONFIGURATION_get_value_time (cfg,
    229                                            "exchangedb",
    230                                            "AGGREGATOR_SHIFT",
    231                                            &pg->aggregator_shift))
    232   {
    233     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
    234                                "exchangedb",
    235                                "AGGREGATOR_SHIFT");
    236   }
    237   if (GNUNET_OK !=
    238       GNUNET_CONFIGURATION_get_value_number (cfg,
    239                                              "exchangedb",
    240                                              "DEFAULT_PURSE_LIMIT",
    241                                              &dpl))
    242   {
    243     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
    244                                "exchangedb",
    245                                "DEFAULT_PURSE_LIMIT");
    246     pg->def_purse_limit = 1;
    247   }
    248   else
    249   {
    250     pg->def_purse_limit = (uint32_t) dpl;
    251   }
    252 
    253   if (GNUNET_OK !=
    254       TALER_config_get_currency (cfg,
    255                                  "exchange",
    256                                  &pg->currency))
    257   {
    258     goto fail;
    259   }
    260   if (GNUNET_OK !=
    261       internal_setup (pg))
    262   {
    263     goto fail;
    264   }
    265   if (check_current &&
    266       (GNUNET_OK !=
    267        GNUNET_PQ_check_current (pg->conn,
    268                                 "exchange-")) )
    269   {
    270     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    271                 "Database schema is not up-to-date. Try running taler-exchange-dbinit or taler-exchange-dbconfig!\n");
    272     goto fail;
    273   }
    274   return pg;
    275 
    276 fail:
    277   TALER_EXCHANGEDB_disconnect (pg);
    278   return NULL;
    279 }
    280 
    281 
    282 struct TALER_EXCHANGEDB_PostgresContext *
    283 TALER_EXCHANGEDB_connect (
    284   const struct GNUNET_CONFIGURATION_Handle *cfg)
    285 {
    286   return do_connect (cfg,
    287                      true);
    288 }
    289 
    290 
    291 struct TALER_EXCHANGEDB_PostgresContext *
    292 TALER_EXCHANGEDB_connect_admin (
    293   const struct GNUNET_CONFIGURATION_Handle *cfg)
    294 {
    295   return do_connect (cfg,
    296                      false);
    297 }
    298 
    299 
    300 void
    301 TALER_EXCHANGEDB_disconnect (struct TALER_EXCHANGEDB_PostgresContext *pg)
    302 {
    303   if (NULL == pg)
    304     return;
    305   if (NULL != pg->conn)
    306   {
    307     GNUNET_PQ_disconnect (pg->conn);
    308     pg->conn = NULL;
    309   }
    310   GNUNET_free (pg->exchange_url);
    311   GNUNET_free (pg->sql_dir);
    312   GNUNET_free (pg->currency);
    313   GNUNET_free (pg);
    314 }
    315 
    316 
    317 /* end of pg.c */