/* This file is part of TALER (C) 2014, 2015 Christian Grothoff (and other contributing authors) TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. TALER is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with TALER; see the file COPYING. If not, If not, see */ /** * @file merchant/plugin_merchantdb_postgres.c * @brief database helper functions for postgres used by the merchant * @author Sree Harsha Totakura */ #include "platform.h" #include #include #include #include #include "taler_merchantdb_plugin.h" /** * Type of the "cls" argument given to each of the functions in * our API. */ struct PostgresClosure { /** * Postgres connection handle. */ PGconn *conn; /** * Database connection string, as read from * the configuration. */ char *connection_cfg_str; }; #define PQSQL_strerror(kind, cmd, res) \ GNUNET_log_from (kind, "merchant-db", \ "SQL %s failed at %s:%u with error: %s", \ cmd, __FILE__, __LINE__, PQresultErrorMessage (res)); /** * Initialize merchant tables * * @param cls closure our `struct Plugin` * @param tmp #GNUNET_YES if the tables are to be made temporary i.e. their * contents are dropped when the database connection is closed * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure */ static int postgres_initialize (void *cls, int tmp) { struct PostgresClosure *pg = cls; const char *tmp_str = (1 == tmp) ? "TEMPORARY" : ""; char *sql; PGresult *res; ExecStatusType status; int ret; GNUNET_asprintf (&sql, "CREATE %1$s TABLE IF NOT EXISTS payments (" "h_contract BYTEA NOT NULL," "h_wire BYTEA NOT NULL," "transaction_id INT8 PRIMARY KEY," "timestamp INT8 NOT NULL," "refund_deadline INT8 NOT NULL," "amount_without_fee_val INT8 NOT NULL," "amount_without_fee_frac INT4 NOT NULL," "amount_without_fee_curr VARCHAR(" TALER_CURRENCY_LEN_STR ") NOT NULL," "coin_pub BYTEA NOT NULL," "mint_sig BYTEA NOT NULL);", tmp_str); ret = GNUNET_POSTGRES_exec (pg->conn, sql); GNUNET_free (sql); if (GNUNET_OK != ret) return ret; if ( (NULL == (res = PQprepare (pg->conn, "insert_payment", "INSERT INTO payments" "(h_contract" ",h_wire" ",transaction_id" ",timestamp" ",refund_deadline" ",amount_without_fee_val" ",amount_without_fee_frac" ",amount_without_fee_curr" ",coin_pub" ",mint_sig) VALUES " "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", 10, NULL))) || (PGRES_COMMAND_OK != (status = PQresultStatus(res))) ) { if (NULL != res) { PQSQL_strerror (GNUNET_ERROR_TYPE_ERROR, "PQprepare", res); PQclear (res); } return GNUNET_SYSERR; } PQclear (res); return GNUNET_OK; } /** * Insert payment confirmation from the mint into the database. * * @param cls our plugin handle * @param h_contract hash of the contract * @param h_wire hash of our wire details * @param transaction_id of the contract * @param timestamp time of the confirmation * @param refund refund deadline * @param amount_without_fee amount the mint will deposit * @param coin_pub public key of the coin * @param merchant_pub our public key * @return #GNUNET_OK on success, #GNUNET_SYSERR upon error */ static int postgres_store_payment (void *cls, const struct GNUNET_HashCode *h_contract, const struct GNUNET_HashCode *h_wire, uint64_t transaction_id, struct GNUNET_TIME_Absolute timestamp, struct GNUNET_TIME_Absolute refund, const struct TALER_Amount *amount_without_fee, const struct TALER_CoinSpendPublicKeyP *coin_pub, const struct TALER_MintSignatureP *mint_sig) { struct PostgresClosure *pg = cls; PGresult *res; ExecStatusType status; struct TALER_PQ_QueryParam params[] = { TALER_PQ_query_param_auto_from_type (h_contract), TALER_PQ_query_param_auto_from_type (h_wire), TALER_PQ_query_param_uint64 (&transaction_id), TALER_PQ_query_param_absolute_time (×tamp), TALER_PQ_query_param_absolute_time (&refund), TALER_PQ_query_param_amount (amount_without_fee), TALER_PQ_query_param_auto_from_type (coin_pub), TALER_PQ_query_param_auto_from_type (mint_sig), TALER_PQ_query_param_end }; res = TALER_PQ_exec_prepared (pg->conn, "insert_payment", params); status = PQresultStatus (res); if (PGRES_COMMAND_OK != status) { const char *sqlstate; sqlstate = PQresultErrorField (res, PG_DIAG_SQLSTATE); if (NULL == sqlstate) { /* very unexpected... */ GNUNET_break (0); PQclear (res); return GNUNET_SYSERR; } /* 40P01: deadlock, 40001: serialization failure */ if ( (0 == strcmp (sqlstate, "23505"))) { /* Primary key violation */ PQclear (res); return GNUNET_NO; } GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Database commit failure: %s\n", sqlstate); PQclear (res); return GNUNET_SYSERR; } PQclear (res); return GNUNET_OK; } /** * Initialize Postgres database subsystem. * * @param cls a configuration instance * @return NULL on error, otherwise a `struct TALER_MERCHANTDB_Plugin` */ void * libtaler_plugin_merchantdb_postgres_init (void *cls) { struct GNUNET_CONFIGURATION_Handle *cfg = cls; struct PostgresClosure *pg; struct TALER_MERCHANTDB_Plugin *plugin; pg = GNUNET_new (struct PostgresClosure); if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, "merchantdb-postgres", "CONFIG", &pg->connection_cfg_str)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "merchant", "db_conn_str"); return NULL; } pg->conn = GNUNET_POSTGRES_connect (cfg, "merchant-db"); plugin = GNUNET_new (struct TALER_MERCHANTDB_Plugin); plugin->cls = pg; plugin->initialize = &postgres_initialize; plugin->store_payment = &postgres_store_payment; return plugin; } /** * Shutdown Postgres database subsystem. * * @param cls a `struct TALER_MERCHANTDB_Plugin` * @return NULL (always) */ void * libtaler_plugin_merchantdb_postgres_done (void *cls) { struct TALER_MERCHANTDB_Plugin *plugin = cls; struct PostgresClosure *pg = plugin->cls; GNUNET_free (pg->connection_cfg_str); PQfinish (pg->conn); GNUNET_free (pg); GNUNET_free (plugin); return NULL; }