diff options
Diffstat (limited to 'src/mintdb')
-rw-r--r-- | src/mintdb/Makefile.am | 68 | ||||
-rw-r--r-- | src/mintdb/mintdb_keyio.c | 347 | ||||
-rw-r--r-- | src/mintdb/mintdb_plugin.c | 149 | ||||
-rw-r--r-- | src/mintdb/plugin_mintdb_common.c | 118 | ||||
-rw-r--r-- | src/mintdb/plugin_mintdb_postgres.c | 2356 | ||||
-rw-r--r-- | src/mintdb/test_mintdb.c | 393 | ||||
-rw-r--r-- | src/mintdb/test_mintdb_deposits.c | 142 | ||||
-rw-r--r-- | src/mintdb/test_mintdb_keyio.c | 86 |
8 files changed, 3659 insertions, 0 deletions
diff --git a/src/mintdb/Makefile.am b/src/mintdb/Makefile.am new file mode 100644 index 000000000..ceaa2e210 --- /dev/null +++ b/src/mintdb/Makefile.am @@ -0,0 +1,68 @@ +# This Makefile.am is in the public domain +AM_CPPFLAGS = -I$(top_srcdir)/src/include -I$(top_srcdir)/src/pq/ $(POSTGRESQL_CPPFLAGS) + +plugindir = $(libdir)/taler + +plugin_LTLIBRARIES = \ + libtaler_plugin_mintdb_postgres.la + +EXTRA_DIST = plugin_mintdb_common.c + +libtaler_plugin_mintdb_postgres_la_SOURCES = \ + plugin_mintdb_postgres.c +libtaler_plugin_mintdb_postgres_la_LIBADD = \ + $(LTLIBINTL) +libtaler_plugin_mintdb_postgres_la_LDFLAGS = \ + $(TALER_PLUGIN_LDFLAGS) \ + -lpq \ + -lgnunetutil + +lib_LTLIBRARIES = \ + libtalermintdb.la + +libtalermintdb_la_SOURCES = \ + mintdb_keyio.c \ + mintdb_plugin.c + +libtalermintdb_la_LIBADD = \ + $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetutil + +libtalermintdb_la_LDFLAGS = \ + $(POSTGRESQL_LDFLAGS) \ + -version-info 0:0:0 \ + -no-undefined + + +check_PROGRAMS = \ + test-mintdb-deposits \ + test-mintdb-keyio \ + test-mintdb-postgres + +test_mintdb_deposits_SOURCES = \ + test_mintdb_deposits.c +test_mintdb_deposits_LDADD = \ + libtalermintdb.la \ + $(top_srcdir)/src/util/libtalerutil.la \ + $(top_srcdir)/src/pq/libtalerpq.la \ + -lgnunetutil \ + -ljansson \ + -lpq + +test_mintdb_keyio_SOURCES = \ + test_mintdb_keyio.c +test_mintdb_keyio_LDADD = \ + libtalermintdb.la \ + $(top_srcdir)/src/util/libtalerutil.la \ + $(top_srcdir)/src/pq/libtalerpq.la \ + -lgnunetutil + +test_mintdb_postgres_SOURCES = \ + test_mintdb.c +test_mintdb_postgres_LDADD = \ + libtalermintdb.la \ + $(top_srcdir)/src/util/libtalerutil.la \ + $(top_srcdir)/src/pq/libtalerpq.la \ + -lgnunetutil -ljansson +EXTRA_test_mintdb_postgres_DEPENDENCIES = \ + libtaler_plugin_mintdb_postgres.la diff --git a/src/mintdb/mintdb_keyio.c b/src/mintdb/mintdb_keyio.c new file mode 100644 index 000000000..321b890c3 --- /dev/null +++ b/src/mintdb/mintdb_keyio.c @@ -0,0 +1,347 @@ +/* + This file is part of TALER + Copyright (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 <http://www.gnu.org/licenses/> +*/ +/** + * @file mintdb/mintdb_keyio.c + * @brief I/O operations for the Mint's private keys + * @author Florian Dold + * @author Benedikt Mueller + * @author Sree Harsha Totakura + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_mintdb_lib.h" + + +/** + * Closure for the #signkeys_iterate_dir_iter(). + */ +struct SignkeysIterateContext +{ + + /** + * Function to call on each signing key. + */ + TALER_MINT_SignkeyIterator it; + + /** + * Closure for @e it. + */ + void *it_cls; +}; + + +/** + * Function called on each file in the directory with + * our signing keys. Parses the file and calls the + * iterator from @a cls. + * + * @param cls the `struct SignkeysIterateContext *` + * @param filename name of the file to parse + * @return #GNUNET_OK to continue, + * #GNUNET_NO to stop iteration without error, + * #GNUNET_SYSERR to stop iteration with error + */ +static int +signkeys_iterate_dir_iter (void *cls, + const char *filename) +{ + struct SignkeysIterateContext *skc = cls; + ssize_t nread; + struct TALER_MintSigningKeyValidityPSPriv issue; + + nread = GNUNET_DISK_fn_read (filename, + &issue, + sizeof (struct TALER_MintSigningKeyValidityPSPriv)); + if (nread != sizeof (struct TALER_MintSigningKeyValidityPSPriv)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Invalid signkey file `%s': wrong size\n", + filename); + return GNUNET_OK; + } + return skc->it (skc->it_cls, + filename, + &issue); +} + + +/** + * Call @a it for each signing key found in the @a mint_base_dir. + * + * @param mint_base_dir base directory for the mint, + * the signing keys must be in the #DIR_SIGNKEYS + * subdirectory + * @param it function to call on each signing key + * @param it_cls closure for @a it + * @return number of files found (may not match + * number of keys given to @a it as malformed + * files are simply skipped), -1 on error + */ +int +TALER_MINT_signkeys_iterate (const char *mint_base_dir, + TALER_MINT_SignkeyIterator it, + void *it_cls) +{ + char *signkey_dir; + struct SignkeysIterateContext skc; + int ret; + + GNUNET_asprintf (&signkey_dir, + "%s" DIR_SEPARATOR_STR DIR_SIGNKEYS, + mint_base_dir); + skc.it = it; + skc.it_cls = it_cls; + ret = GNUNET_DISK_directory_scan (signkey_dir, + &signkeys_iterate_dir_iter, + &skc); + GNUNET_free (signkey_dir); + return ret; +} + + +/** + * Import a denomination key from the given file. + * + * @param filename the file to import the key from + * @param[OUT] dki set to the imported denomination key + * @return #GNUNET_OK upon success; + * #GNUNET_SYSERR upon failure + */ +int +TALER_MINT_read_denom_key (const char *filename, + struct TALER_DenominationKeyIssueInformation *dki) +{ + uint64_t size; + size_t offset; + void *data; + struct GNUNET_CRYPTO_rsa_PrivateKey *priv; + + if (GNUNET_OK != GNUNET_DISK_file_size (filename, + &size, + GNUNET_YES, + GNUNET_YES)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Skipping inaccessable denomination key file `%s'\n", + filename); + return GNUNET_SYSERR; + } + offset = sizeof (struct TALER_DenominationKeyValidityPS); + if (size <= offset) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + data = GNUNET_malloc (size); + if (size != + GNUNET_DISK_fn_read (filename, + data, + size)) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, + "read", + filename); + GNUNET_free (data); + return GNUNET_SYSERR; + } + if (NULL == + (priv = GNUNET_CRYPTO_rsa_private_key_decode (data + offset, + size - offset))) + { + GNUNET_free (data); + return GNUNET_SYSERR; + } + dki->denom_priv.rsa_private_key = priv; + dki->denom_pub.rsa_public_key + = GNUNET_CRYPTO_rsa_private_key_get_public (priv); + memcpy (&dki->issue, + data, + offset); + GNUNET_free (data); + return GNUNET_OK; +} + + +/** + * Exports a denomination key to the given file. + * + * @param filename the file where to write the denomination key + * @param dki the denomination key + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure. + */ +int +TALER_MINT_write_denom_key (const char *filename, + const struct TALER_DenominationKeyIssueInformation *dki) +{ + char *priv_enc; + size_t priv_enc_size; + struct GNUNET_DISK_FileHandle *fh; + ssize_t wrote; + size_t wsize; + int ret; + + fh = NULL; + priv_enc_size + = GNUNET_CRYPTO_rsa_private_key_encode (dki->denom_priv.rsa_private_key, + &priv_enc); + ret = GNUNET_SYSERR; + if (NULL == (fh = GNUNET_DISK_file_open + (filename, + GNUNET_DISK_OPEN_WRITE | GNUNET_DISK_OPEN_CREATE | GNUNET_DISK_OPEN_TRUNCATE, + GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE))) + goto cleanup; + wsize = sizeof (struct TALER_DenominationKeyValidityPS); + if (GNUNET_SYSERR == (wrote = GNUNET_DISK_file_write (fh, + &dki->issue.signature, + wsize))) + goto cleanup; + if (wrote != wsize) + goto cleanup; + if (GNUNET_SYSERR == + (wrote = GNUNET_DISK_file_write (fh, + priv_enc, + priv_enc_size))) + goto cleanup; + if (wrote != priv_enc_size) + goto cleanup; + ret = GNUNET_OK; + cleanup: + GNUNET_free_non_null (priv_enc); + if (NULL != fh) + (void) GNUNET_DISK_file_close (fh); + return ret; +} + + +/** + * Closure for #denomkeys_iterate_keydir_iter() and + * #denomkeys_iterate_topdir_iter(). + */ +struct DenomkeysIterateContext +{ + + /** + * Set to the name of the directory below the top-level directory + * during the call to #denomkeys_iterate_keydir_iter(). + */ + const char *alias; + + /** + * Function to call on each denomination key. + */ + TALER_MINT_DenomkeyIterator it; + + /** + * Closure for @e it. + */ + void *it_cls; +}; + + +/** + * Decode the denomination key in the given file @a filename and call + * the callback in @a cls with the information. + * + * @param cls the `struct DenomkeysIterateContext *` + * @param filename name of a file that should contain + * a denomination key + * @return #GNUNET_OK to continue to iterate + * #GNUNET_NO to abort iteration with success + * #GNUNET_SYSERR to abort iteration with failure + */ +static int +denomkeys_iterate_keydir_iter (void *cls, + const char *filename) +{ + struct DenomkeysIterateContext *dic = cls; + struct TALER_DenominationKeyIssueInformation issue; + + if (GNUNET_OK != + TALER_MINT_read_denom_key (filename, + &issue)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Invalid denomkey file: '%s'\n", + filename); + return GNUNET_OK; + } + return dic->it (dic->it_cls, + dic->alias, + &issue); +} + + +/** + * Function called on each subdirectory in the #DIR_DENOMKEYS. Will + * call the #denomkeys_iterate_keydir_iter() on each file in the + * subdirectory. + * + * @param cls the `struct DenomkeysIterateContext *` + * @param filename name of the subdirectory to scan + * @return #GNUNET_OK on success, + * #GNUNET_SYSERR if we need to abort + */ +static int +denomkeys_iterate_topdir_iter (void *cls, + const char *filename) +{ + struct DenomkeysIterateContext *dic = cls; + + dic->alias = GNUNET_STRINGS_get_short_name (filename); + if (0 > GNUNET_DISK_directory_scan (filename, + &denomkeys_iterate_keydir_iter, + dic)) + return GNUNET_SYSERR; + return GNUNET_OK; +} + + +/** + * Call @a it for each denomination key found in the @a mint_base_dir. + * + * @param mint_base_dir base directory for the mint, + * the signing keys must be in the #DIR_DENOMKEYS + * subdirectory + * @param it function to call on each denomination key found + * @param it_cls closure for @a it + * @return -1 on error, 0 if no files were found, otherwise + * a positive number (however, even with a positive + * number it is possible that @a it was never called + * as maybe none of the files were well-formed) + */ +int +TALER_MINT_denomkeys_iterate (const char *mint_base_dir, + TALER_MINT_DenomkeyIterator it, + void *it_cls) +{ + char *dir; + struct DenomkeysIterateContext dic; + int ret; + + GNUNET_asprintf (&dir, + "%s" DIR_SEPARATOR_STR DIR_DENOMKEYS, + mint_base_dir); + dic.it = it; + dic.it_cls = it_cls; + ret = GNUNET_DISK_directory_scan (dir, + &denomkeys_iterate_topdir_iter, + &dic); + GNUNET_free (dir); + return ret; +} + + +/* end of key_io.c */ diff --git a/src/mintdb/mintdb_plugin.c b/src/mintdb/mintdb_plugin.c new file mode 100644 index 000000000..b109ff3d1 --- /dev/null +++ b/src/mintdb/mintdb_plugin.c @@ -0,0 +1,149 @@ +/* + This file is part of TALER + Copyright (C) 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 <http://www.gnu.org/licenses/> +*/ +/** + * @file mintdb/mintdb_plugin.c + * @brief Logic to load database plugin + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_mintdb_lib.h" +#include "taler_mintdb_plugin.h" +#include <ltdl.h> + + +/** + * Libtool search path before we started. + */ +static char *old_dlsearchpath; + + +/** + * Initialize the plugin. + * + * @param cfg configuration to use + * @return #GNUNET_OK on success + */ +struct TALER_MINTDB_Plugin * +TALER_MINT_plugin_load (const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + char *plugin_name; + char *lib_name; + struct GNUNET_CONFIGURATION_Handle *cfg_dup; + struct TALER_MINTDB_Plugin *plugin; + + if (GNUNET_SYSERR == + GNUNET_CONFIGURATION_get_value_string (cfg, + "mint", + "db", + &plugin_name)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "mint", + "db"); + return NULL; + } + (void) GNUNET_asprintf (&lib_name, + "libtaler_plugin_mintdb_%s", + plugin_name); + GNUNET_free (plugin_name); + cfg_dup = GNUNET_CONFIGURATION_dup (cfg); + plugin = GNUNET_PLUGIN_load (lib_name, cfg_dup); + if (NULL != plugin) + plugin->library_name = lib_name; + else + GNUNET_free (lib_name); + GNUNET_CONFIGURATION_destroy (cfg_dup); + return plugin; +} + + +/** + * Shutdown the plugin. + * + * @param plugin the plugin to unload + */ +void +TALER_MINT_plugin_unload (struct TALER_MINTDB_Plugin *plugin) +{ + char *lib_name; + + if (NULL == plugin) + return; + lib_name = plugin->library_name; + GNUNET_assert (NULL == GNUNET_PLUGIN_unload (lib_name, + plugin)); + GNUNET_free (lib_name); +} + + +/** + * Setup libtool paths. + */ +void __attribute__ ((constructor)) +plugin_init () +{ + int err; + const char *opath; + char *path; + char *cpath; + + err = lt_dlinit (); + if (err > 0) + { + FPRINTF (stderr, + _("Initialization of plugin mechanism failed: %s!\n"), + lt_dlerror ()); + return; + } + opath = lt_dlgetsearchpath (); + if (NULL != opath) + old_dlsearchpath = GNUNET_strdup (opath); + path = TALER_os_installation_get_path (GNUNET_OS_IPK_LIBDIR); + if (NULL != path) + { + if (NULL != opath) + { + GNUNET_asprintf (&cpath, "%s:%s", opath, path); + lt_dlsetsearchpath (cpath); + GNUNET_free (path); + GNUNET_free (cpath); + } + else + { + lt_dlsetsearchpath (path); + GNUNET_free (path); + } + } +} + + +/** + * Shutdown libtool. + */ +void __attribute__ ((destructor)) +plugin_fini () +{ + lt_dlsetsearchpath (old_dlsearchpath); + if (NULL != old_dlsearchpath) + { + GNUNET_free (old_dlsearchpath); + old_dlsearchpath = NULL; + } + lt_dlexit (); +} + + +/* end of plugin.c */ diff --git a/src/mintdb/plugin_mintdb_common.c b/src/mintdb/plugin_mintdb_common.c new file mode 100644 index 000000000..a95cf4be2 --- /dev/null +++ b/src/mintdb/plugin_mintdb_common.c @@ -0,0 +1,118 @@ +/* + This file is part of TALER + Copyright (C) 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 <http://www.gnu.org/licenses/> +*/ +/** + * @file mint/plugin_mintdb_common.c + * @brief Functions shared across plugins, this file is meant to be + * #include-d in each plugin. + * @author Christian Grothoff + */ + +/** + * Free memory associated with the given reserve history. + * + * @param cls the @e cls of this struct with the plugin-specific state (unused) + * @param rh history to free. + */ +static void +common_free_reserve_history (void *cls, + struct ReserveHistory *rh) +{ + struct BankTransfer *bt; + struct CollectableBlindcoin *cbc; + struct ReserveHistory *backref; + + while (NULL != rh) + { + switch(rh->type) + { + case TALER_MINT_DB_RO_BANK_TO_MINT: + bt = rh->details.bank; + if (NULL != bt->wire) + json_decref (bt->wire); + GNUNET_free (bt); + break; + case TALER_MINT_DB_RO_WITHDRAW_COIN: + cbc = rh->details.withdraw; + GNUNET_CRYPTO_rsa_signature_free (cbc->sig.rsa_signature); + GNUNET_CRYPTO_rsa_public_key_free (cbc->denom_pub.rsa_public_key); + GNUNET_free (cbc); + break; + } + backref = rh; + rh = rh->next; + GNUNET_free (backref); + } +} + + +/** + * Free memory of the link data list. + * + * @param cls the @e cls of this struct with the plugin-specific state (unused) + * @param ldl link data list to release + */ +static void +common_free_link_data_list (void *cls, + struct LinkDataList *ldl) +{ + struct LinkDataList *next; + + while (NULL != ldl) + { + next = ldl->next; + GNUNET_free (ldl->link_data_enc); + GNUNET_free (ldl); + ldl = next; + } +} + + +/** + * Free linked list of transactions. + * + * @param cls the @e cls of this struct with the plugin-specific state (unused) + * @param list list to free + */ +static void +common_free_coin_transaction_list (void *cls, + struct TALER_MINT_DB_TransactionList *list) +{ + struct TALER_MINT_DB_TransactionList *next; + + while (NULL != list) + { + next = list->next; + + switch (list->type) + { + case TALER_MINT_DB_TT_DEPOSIT: + json_decref (list->details.deposit->wire); + GNUNET_free (list->details.deposit); + break; + case TALER_MINT_DB_TT_REFRESH_MELT: + GNUNET_free (list->details.melt); + break; + case TALER_MINT_DB_TT_LOCK: + GNUNET_free (list->details.lock); + /* FIXME: look at this again once locking is implemented (#3625) */ + break; + } + GNUNET_free (list); + list = next; + } +} + +/* end of plugin_mintdb_common.c */ diff --git a/src/mintdb/plugin_mintdb_postgres.c b/src/mintdb/plugin_mintdb_postgres.c new file mode 100644 index 000000000..fa2c19db0 --- /dev/null +++ b/src/mintdb/plugin_mintdb_postgres.c @@ -0,0 +1,2356 @@ +/* + This file is part of TALER + Copyright (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 <http://www.gnu.org/licenses/> +*/ + +/** + * @file plugin_mintdb_postgres.c + * @brief Low-level (statement-level) Postgres database access for the mint + * @author Florian Dold + * @author Christian Grothoff + * @author Sree Harsha Totakura + */ +#include "platform.h" +#include "taler_pq_lib.h" +#include "taler_signatures.h" +#include "taler_mintdb_plugin.h" +#include <pthread.h> +#include <libpq-fe.h> + +#include "plugin_mintdb_common.c" + +#define TALER_TEMP_SCHEMA_NAME "taler_temporary" + +#define QUERY_ERR(result) \ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Query failed at %s:%u: %s\n", __FILE__, __LINE__, PQresultErrorMessage (result)) + + +#define BREAK_DB_ERR(result) do { \ + GNUNET_break(0); \ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Database failure: %s\n", PQresultErrorMessage (result)); \ + } while (0) + +/** + * Shorthand for exit jumps. + */ +#define EXITIF(cond) \ + do { \ + if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ + } while (0) + + +#define SQLEXEC_(conn, sql, result) \ + do { \ + result = PQexec (conn, sql); \ + if (PGRES_COMMAND_OK != PQresultStatus (result)) \ + { \ + BREAK_DB_ERR (result); \ + PQclear (result); result = NULL; \ + goto SQLEXEC_fail; \ + } \ + PQclear (result); result = NULL; \ + } while (0) + +/** + * This the length of the currency strings (without 0-termination) we use. Note + * that we need to use this at the DB layer instead of TALER_CURRENCY_LEN as the + * DB only needs to store 3 bytes instead of 8 bytes. + */ +#define TALER_PQ_CURRENCY_LEN 3 + + +/** + * Handle for a database session (per-thread, for transactions). + */ +struct TALER_MINTDB_Session +{ + /** + * Postgres connection handle. + */ + PGconn *conn; +}; + + +/** + * Type of the "cls" argument given to each of the functions in + * our API. + */ +struct PostgresClosure +{ + + /** + * Thread-local database connection. + * Contains a pointer to PGconn or NULL. + */ + pthread_key_t db_conn_threadlocal; + + /** + * Database connection string, as read from + * the configuration. + */ + char *connection_cfg_str; +}; + + + +/** + * Set the given connection to use a temporary schema + * + * @param db the database connection + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon error + */ +static int +set_temporary_schema (PGconn *db) +{ + PGresult *result; + + SQLEXEC_(db, + "CREATE SCHEMA IF NOT EXISTS " TALER_TEMP_SCHEMA_NAME ";" + "SET search_path to " TALER_TEMP_SCHEMA_NAME ";", + result); + return GNUNET_OK; + SQLEXEC_fail: + return GNUNET_SYSERR; +} + + +/** + * Drop the temporary taler schema. This is only useful for testcases + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ +static int +postgres_drop_temporary (void *cls, + struct TALER_MINTDB_Session *session) +{ + PGresult *result; + + SQLEXEC_ (session->conn, + "DROP SCHEMA " TALER_TEMP_SCHEMA_NAME " CASCADE;", + result); + return GNUNET_OK; + SQLEXEC_fail: + return GNUNET_SYSERR; +} + + +/** + * Create the necessary tables if they are not present + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param temporary should we use a temporary schema + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ +static int +postgres_create_tables (void *cls, + int temporary) +{ + struct PostgresClosure *pc = cls; + PGresult *result; + PGconn *conn; + + result = NULL; + conn = PQconnectdb (pc->connection_cfg_str); + if (CONNECTION_OK != PQstatus (conn)) + { + TALER_LOG_ERROR ("Database connection failed: %s\n", + PQerrorMessage (conn)); + GNUNET_break (0); + return GNUNET_SYSERR; + } + if ( (GNUNET_YES == temporary) && + (GNUNET_SYSERR == set_temporary_schema (conn))) + { + PQfinish (conn); + return GNUNET_SYSERR; + } +#define SQLEXEC(sql) SQLEXEC_(conn, sql, result); + /* reserves table is for summarization of a reserve. It is updated when new + funds are added and existing funds are withdrawn */ + SQLEXEC ("CREATE TABLE IF NOT EXISTS reserves" + "(" + " reserve_pub BYTEA PRIMARY KEY" + ",current_balance_value INT8 NOT NULL" + ",current_balance_fraction INT4 NOT NULL" + ",balance_currency VARCHAR(4) NOT NULL" + ",expiration_date INT8 NOT NULL" + ")"); + /* reserves_in table collects the transactions which transfer funds into the + reserve. The amount and expiration date for the corresponding reserve are + updated when new transfer funds are added. The rows of this table + correspond to each incoming transaction. */ + SQLEXEC("CREATE TABLE IF NOT EXISTS reserves_in" + "(" + " reserve_pub BYTEA REFERENCES reserves (reserve_pub) ON DELETE CASCADE" + ",balance_value INT8 NOT NULL" + ",balance_fraction INT4 NOT NULL" + ",balance_currency VARCHAR(4) NOT NULL" + ",expiration_date INT8 NOT NULL" + ");"); + /* Create an index on the foreign key as it is not created automatically by PSQL */ + SQLEXEC ("CREATE INDEX reserves_in_reserve_pub_index" + " ON reserves_in (reserve_pub);"); + SQLEXEC ("CREATE TABLE IF NOT EXISTS collectable_blindcoins" + "(" + "blind_ev BYTEA PRIMARY KEY" + ",denom_pub BYTEA NOT NULL" /* FIXME: Make this a foreign key? */ + ",denom_sig BYTEA NOT NULL" + ",reserve_pub BYTEA REFERENCES reserves (reserve_pub) ON DELETE CASCADE" + ",reserve_sig BYTEA NOT NULL" + ");"); + SQLEXEC ("CREATE INDEX collectable_blindcoins_reserve_pub_index ON" + " collectable_blindcoins (reserve_pub)"); + SQLEXEC("CREATE TABLE IF NOT EXISTS known_coins " + "(" + " coin_pub BYTEA NOT NULL PRIMARY KEY" + ",denom_pub BYTEA NOT NULL" + ",denom_sig BYTEA NOT NULL" + ",expended_value INT8 NOT NULL" + ",expended_fraction INT4 NOT NULL" + ",expended_currency VARCHAR(4) NOT NULL" + ",refresh_session_hash BYTEA" + ")"); + SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_sessions " + "(" + " session_hash BYTEA PRIMARY KEY CHECK (length(session_hash) = 32)" + ",session_melt_sig BYTEA" + ",session_commit_sig BYTEA" + ",noreveal_index INT2 NOT NULL" + // non-zero if all reveals were ok + // and the new coin signatures are ready + ",reveal_ok BOOLEAN NOT NULL DEFAULT false" + ") "); + SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_order " + "( " + " session_hash BYTEA NOT NULL REFERENCES refresh_sessions (session_hash)" + ",newcoin_index INT2 NOT NULL " + ",denom_pub BYTEA NOT NULL " + ",PRIMARY KEY (session_hash, newcoin_index)" + ") "); + SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_commit_link" + "(" + " session_hash BYTEA NOT NULL REFERENCES refresh_sessions (session_hash)" + ",transfer_pub BYTEA NOT NULL" + ",link_secret_enc BYTEA NOT NULL" + // index of the old coin in the customer's request + ",oldcoin_index INT2 NOT NULL" + // index for cut and choose, + // ranges from 0 to #TALER_CNC_KAPPA-1 + ",cnc_index INT2 NOT NULL" + ")"); + SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_commit_coin" + "(" + " session_hash BYTEA NOT NULL REFERENCES refresh_sessions (session_hash) " + ",link_vector_enc BYTEA NOT NULL" + // index of the new coin in the customer's request + ",newcoin_index INT2 NOT NULL" + // index for cut and choose, + ",cnc_index INT2 NOT NULL" + ",coin_ev BYTEA NOT NULL" + ")"); + SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_melt" + "(" + " session_hash BYTEA NOT NULL REFERENCES refresh_sessions (session_hash) " + ",coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) " + ",denom_pub BYTEA NOT NULL " + ",oldcoin_index INT2 NOT NULL" + ")"); + SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_collectable" + "(" + " session_hash BYTEA NOT NULL REFERENCES refresh_sessions (session_hash) " + ",ev_sig BYTEA NOT NULL" + ",newcoin_index INT2 NOT NULL" + ")"); + SQLEXEC("CREATE TABLE IF NOT EXISTS deposits " + "( " + " coin_pub BYTEA NOT NULL PRIMARY KEY CHECK (length(coin_pub)=32)" + ",denom_pub BYTEA NOT NULL" /* FIXME: Link this as a foreign key? */ + ",denom_sig BYTEA NOT NULL" + ",transaction_id INT8 NOT NULL" + ",amount_currency VARCHAR(4) NOT NULL" + ",amount_value INT8 NOT NULL" + ",amount_fraction INT4 NOT NULL" + ",merchant_pub BYTEA NOT NULL CHECK (length(merchant_pub)=32)" + ",h_contract BYTEA NOT NULL CHECK (length(h_contract)=64)" + ",h_wire BYTEA NOT NULL CHECK (length(h_wire)=64)" + ",coin_sig BYTEA NOT NULL CHECK (length(coin_sig)=64)" + ",wire TEXT NOT NULL" + ")"); +#undef SQLEXEC + + PQfinish (conn); + return GNUNET_OK; + + SQLEXEC_fail: + PQfinish (conn); + return GNUNET_SYSERR; +} + + +/** + * Setup prepared statements. + * + * @param db_conn connection handle to initialize + * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure + */ +static int +postgres_prepare (PGconn *db_conn) +{ + PGresult *result; + +#define PREPARE(name, sql, ...) \ + do { \ + result = PQprepare (db_conn, name, sql, __VA_ARGS__); \ + if (PGRES_COMMAND_OK != PQresultStatus (result)) \ + { \ + BREAK_DB_ERR (result); \ + PQclear (result); result = NULL; \ + return GNUNET_SYSERR; \ + } \ + PQclear (result); result = NULL; \ + } while (0); + + PREPARE ("get_reserve", + "SELECT " + "current_balance_value" + ",current_balance_fraction" + ",balance_currency " + ",expiration_date " + "FROM reserves " + "WHERE reserve_pub=$1 " + "LIMIT 1; ", + 1, NULL); + PREPARE ("create_reserve", + "INSERT INTO reserves (" + " reserve_pub," + " current_balance_value," + " current_balance_fraction," + " balance_currency," + " expiration_date) VALUES (" + "$1, $2, $3, $4, $5);", + 5, NULL); + PREPARE ("update_reserve", + "UPDATE reserves " + "SET" + " current_balance_value=$2 " + ",current_balance_fraction=$3 " + ",expiration_date=$4 " + "WHERE reserve_pub=$1 ", + 4, NULL); + PREPARE ("create_reserves_in_transaction", + "INSERT INTO reserves_in (" + " reserve_pub," + " balance_value," + " balance_fraction," + " balance_currency," + " expiration_date) VALUES (" + " $1, $2, $3, $4, $5);", + 5, NULL); + PREPARE ("get_reserves_in_transactions", + "SELECT" + " balance_value" + ",balance_fraction" + ",balance_currency" + ",expiration_date" + " FROM reserves_in WHERE reserve_pub=$1", + 1, NULL); + PREPARE ("insert_collectable_blindcoin", + "INSERT INTO collectable_blindcoins ( " + " blind_ev" + ",denom_pub, denom_sig" + ",reserve_pub, reserve_sig) " + "VALUES ($1, $2, $3, $4, $5)", + 5, NULL); + PREPARE ("get_collectable_blindcoin", + "SELECT " + " denom_pub, denom_sig" + ",reserve_sig, reserve_pub " + "FROM collectable_blindcoins " + "WHERE blind_ev = $1", + 1, NULL); + PREPARE ("get_reserves_blindcoins", + "select" + " blind_ev" + ",denom_pub, denom_sig" + ",reserve_sig" + " FROM collectable_blindcoins" + " WHERE reserve_pub=$1;", + 1, NULL); + + /* FIXME: does it make sense to store these computed values in the DB? */ +#if 0 + PREPARE ("get_refresh_session", + "SELECT " + " (SELECT count(*) FROM refresh_melt WHERE session_hash = $1)::INT2 as num_oldcoins " + ",(SELECT count(*) FROM refresh_blind_session_keys " + " WHERE session_hash = $1 and cnc_index = 0)::INT2 as num_newcoins " + ",(SELECT count(*) FROM refresh_blind_session_keys " + " WHERE session_hash = $1 and newcoin_index = 0)::INT2 as kappa " + ",noreveal_index" + ",session_commit_sig " + ",reveal_ok " + "FROM refresh_sessions " + "WHERE session_hash = $1", + 1, NULL); +#endif + + PREPARE ("get_known_coin", + "SELECT " + " coin_pub, denom_pub, denom_sig " + ",expended_value, expended_fraction, expended_currency " + ",refresh_session_hash " + "FROM known_coins " + "WHERE coin_pub = $1", + 1, NULL); + PREPARE ("update_known_coin", + "UPDATE known_coins " + "SET " + " denom_pub = $2 " + ",denom_sig = $3 " + ",expended_value = $4 " + ",expended_fraction = $5 " + ",expended_currency = $6 " + ",refresh_session_hash = $7 " + "WHERE " + " coin_pub = $1 ", + 7, NULL); + PREPARE ("insert_known_coin", + "INSERT INTO known_coins (" + " coin_pub" + ",denom_pub" + ",denom_sig" + ",expended_value" + ",expended_fraction" + ",expended_currency" + ",refresh_session_hash" + ")" + "VALUES ($1,$2,$3,$4,$5,$6,$7)", + 7, NULL); + PREPARE ("get_refresh_commit_link", + "SELECT " + " transfer_pub " + ",link_secret_enc " + "FROM refresh_commit_link " + "WHERE session_hash = $1 AND cnc_index = $2 AND oldcoin_index = $3", + 3, NULL); + PREPARE ("get_refresh_commit_coin", + "SELECT " + " link_vector_enc " + ",coin_ev " + "FROM refresh_commit_coin " + "WHERE session_hash = $1 AND cnc_index = $2 AND newcoin_index = $3", + 3, NULL); + PREPARE ("insert_refresh_order", + "INSERT INTO refresh_order ( " + " newcoin_index " + ",session_hash " + ",denom_pub " + ") " + "VALUES ($1, $2, $3) ", + 3, NULL); + PREPARE ("insert_refresh_melt", + "INSERT INTO refresh_melt ( " + " session_hash " + ",oldcoin_index " + ",coin_pub " + ",denom_pub " + ") " + "VALUES ($1, $2, $3, $4) ", + 3, NULL); + PREPARE ("get_refresh_order", + "SELECT denom_pub " + "FROM refresh_order " + "WHERE session_hash = $1 AND newcoin_index = $2", + 2, NULL); + PREPARE ("get_refresh_collectable", + "SELECT ev_sig " + "FROM refresh_collectable " + "WHERE session_hash = $1 AND newcoin_index = $2", + 2, NULL); + PREPARE ("get_refresh_melt", + "SELECT coin_pub " + "FROM refresh_melt " + "WHERE session_hash = $1 AND oldcoin_index = $2", + 2, NULL); + PREPARE ("insert_refresh_session", + "INSERT INTO refresh_sessions ( " + " session_hash " + ",noreveal_index " + ") " + "VALUES ($1, $2) ", + 2, NULL); + PREPARE ("insert_refresh_commit_link", + "INSERT INTO refresh_commit_link ( " + " session_hash " + ",transfer_pub " + ",cnc_index " + ",oldcoin_index " + ",link_secret_enc " + ") " + "VALUES ($1, $2, $3, $4, $5) ", + 5, NULL); + PREPARE ("insert_refresh_commit_coin", + "INSERT INTO refresh_commit_coin ( " + " session_hash " + ",coin_ev " + ",cnc_index " + ",newcoin_index " + ",link_vector_enc " + ") " + "VALUES ($1, $2, $3, $4, $5) ", + 5, NULL); + PREPARE ("insert_refresh_collectable", + "INSERT INTO refresh_collectable ( " + " session_hash " + ",newcoin_index " + ",ev_sig " + ") " + "VALUES ($1, $2, $3) ", + 3, NULL); + PREPARE ("set_reveal_ok", + "UPDATE refresh_sessions " + "SET reveal_ok = TRUE " + "WHERE session_hash = $1 ", + 1, NULL); + PREPARE ("get_link", + "SELECT link_vector_enc, ro.denom_pub, ev_sig " + "FROM refresh_melt rm " + " JOIN refresh_order ro USING (session_hash) " + " JOIN refresh_commit_coin rcc USING (session_hash) " + " JOIN refresh_sessions rs USING (session_hash) " + " JOIN refresh_collectable rc USING (session_hash) " + "WHERE rm.coin_pub = $1 " + "AND ro.newcoin_index = rcc.newcoin_index " + "AND ro.newcoin_index = rc.newcoin_index " + "AND rcc.cnc_index = rs.noreveal_index % ( " + " SELECT count(*) FROM refresh_commit_coin rcc2 " + " WHERE rcc2.newcoin_index = 0 AND rcc2.session_hash = rs.session_hash " + " ) ", + 1, NULL); + PREPARE ("get_transfer", + "SELECT transfer_pub, link_secret_enc " + "FROM refresh_melt rm " + " JOIN refresh_commit_link rcl USING (session_hash) " + " JOIN refresh_sessions rs USING (session_hash) " + "WHERE rm.coin_pub = $1 " + "AND rm.oldcoin_index = rcl.oldcoin_index " + "AND rcl.cnc_index = rs.noreveal_index % ( " + " SELECT count(*) FROM refresh_commit_coin rcc2 " + " WHERE newcoin_index = 0 AND rcc2.session_hash = rm.session_hash " + " ) ", + 1, NULL); + PREPARE ("insert_deposit", + "INSERT INTO deposits (" + "coin_pub," + "denom_pub," + "denom_sig," + "transaction_id," + "amount_value," + "amount_fraction," + "amount_currency," + "merchant_pub," + "h_contract," + "h_wire," + "coin_sig," + "wire" + ") VALUES (" + "$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12" + ")", + 12, NULL); + PREPARE ("get_deposit", + "SELECT " + "coin_pub," + "denom_pub," + "transaction_id," + "amount_value," + "amount_fraction," + "amount_currency," + "merchant_pub," + "h_contract," + "h_wire," + "coin_sig" + " FROM deposits WHERE (" + "(coin_pub = $1) AND" + "(transaction_id = $2) AND" + "(merchant_pub = $3)" + ")", + 3, NULL); + return GNUNET_OK; +#undef PREPARE +} + + +/** + * Close thread-local database connection when a thread is destroyed. + * + * @param closure we get from pthreads (the db handle) + */ +static void +db_conn_destroy (void *cls) +{ + PGconn *db_conn = cls; + + if (NULL != db_conn) + PQfinish (db_conn); +} + + +/** + * Get the thread-local database-handle. + * Connect to the db if the connection does not exist yet. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param temporary #GNUNET_YES to use a temporary schema; #GNUNET_NO to use the + * database default one + * @return the database connection, or NULL on error + */ +static struct TALER_MINTDB_Session * +postgres_get_session (void *cls, + int temporary) +{ + struct PostgresClosure *pc = cls; + PGconn *db_conn; + struct TALER_MINTDB_Session *session; + + if (NULL != (session = pthread_getspecific (pc->db_conn_threadlocal))) + return session; + db_conn = PQconnectdb (pc->connection_cfg_str); + if (CONNECTION_OK != + PQstatus (db_conn)) + { + TALER_LOG_ERROR ("Database connection failed: %s\n", + PQerrorMessage (db_conn)); + GNUNET_break (0); + return NULL; + } + if ((GNUNET_YES == temporary) + && (GNUNET_SYSERR == set_temporary_schema(db_conn))) + { + GNUNET_break (0); + return NULL; + } + if (GNUNET_OK != + postgres_prepare (db_conn)) + { + GNUNET_break (0); + return NULL; + } + session = GNUNET_new (struct TALER_MINTDB_Session); + session->conn = db_conn; + if (0 != pthread_setspecific (pc->db_conn_threadlocal, + session)) + { + GNUNET_break (0); + // FIXME: close db_conn! + GNUNET_free (session); + return NULL; + } + return session; +} + + +/** + * Start a transaction. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session the database connection + * @return #GNUNET_OK on success + */ +static int +postgres_start (void *cls, + struct TALER_MINTDB_Session *session) +{ + PGresult *result; + + result = PQexec (session->conn, + "BEGIN"); + if (PGRES_COMMAND_OK != + PQresultStatus (result)) + { + TALER_LOG_ERROR ("Failed to start transaction: %s\n", + PQresultErrorMessage (result)); + GNUNET_break (0); + PQclear (result); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +/** + * Roll back the current transaction of a database connection. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session the database connection + * @return #GNUNET_OK on success + */ +static void +postgres_rollback (void *cls, + struct TALER_MINTDB_Session *session) +{ + PGresult *result; + + result = PQexec (session->conn, + "ROLLBACK"); + GNUNET_break (PGRES_COMMAND_OK == + PQresultStatus (result)); + PQclear (result); +} + + +/** + * Commit the current transaction of a database connection. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session the database connection + * @return #GNUNET_OK on success + */ +static int +postgres_commit (void *cls, + struct TALER_MINTDB_Session *session) +{ + PGresult *result; + + result = PQexec (session->conn, + "COMMIT"); + if (PGRES_COMMAND_OK != + PQresultStatus (result)) + { + GNUNET_break (0); + PQclear (result); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +/** + * Get the summary of a reserve. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session the database connection handle + * @param reserve the reserve data. The public key of the reserve should be set + * in this structure; it is used to query the database. The balance + * and expiration are then filled accordingly. + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ +static int +postgres_reserve_get (void *cls, + struct TALER_MINTDB_Session *session, + struct Reserve *reserve) +{ + PGresult *result; + uint64_t expiration_date_nbo; + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR(&reserve->pub), + TALER_PQ_QUERY_PARAM_END + }; + + result = TALER_PQ_exec_prepared (session->conn, + "get_reserve", + params); + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + QUERY_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + if (0 == PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } + struct TALER_PQ_ResultSpec rs[] = { + TALER_PQ_RESULT_SPEC("expiration_date", &expiration_date_nbo), + TALER_PQ_RESULT_SPEC_END + }; + EXITIF (GNUNET_OK != TALER_PQ_extract_result (result, rs, 0)); + EXITIF (GNUNET_OK != + TALER_PQ_extract_amount (result, 0, + "current_balance_value", + "current_balance_fraction", + "balance_currency", + &reserve->balance)); + reserve->expiry.abs_value_us = GNUNET_ntohll (expiration_date_nbo); + PQclear (result); + return GNUNET_OK; + + EXITIF_exit: + PQclear (result); + return GNUNET_SYSERR; +} + + +/** + * Updates a reserve with the data from the given reserve structure. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session the database connection + * @param reserve the reserve structure whose data will be used to update the + * corresponding record in the database. + * @return #GNUNET_OK upon successful update; #GNUNET_SYSERR upon any error + */ +static int +postgres_reserves_update (void *cls, + struct TALER_MINTDB_Session *session, + struct Reserve *reserve) +{ + PGresult *result; + struct TALER_AmountNBO balance_nbo; + struct GNUNET_TIME_AbsoluteNBO expiry_nbo; + int ret; + + if (NULL == reserve) + return GNUNET_SYSERR; + ret = GNUNET_OK; + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR (&reserve->pub), + TALER_PQ_QUERY_PARAM_PTR (&balance_nbo.value), + TALER_PQ_QUERY_PARAM_PTR (&balance_nbo.fraction), + TALER_PQ_QUERY_PARAM_PTR (&expiry_nbo), + TALER_PQ_QUERY_PARAM_END + }; + TALER_amount_hton (&balance_nbo, + &reserve->balance); + expiry_nbo = GNUNET_TIME_absolute_hton (reserve->expiry); + result = TALER_PQ_exec_prepared (session->conn, + "update_reserve", + params); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + QUERY_ERR (result); + ret = GNUNET_SYSERR; + } + PQclear (result); + return ret; +} + + +/** + * Insert a incoming transaction into reserves. New reserves are also created + * through this function. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session the database connection handle + * @param reserve the reserve structure. The public key of the reserve should + * be set here. Upon successful execution of this function, the + * balance and expiration of the reserve will be updated. + * @param balance the amount that has to be added to the reserve + * @param expiry the new expiration time for the reserve + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failures + */ +static int +postgres_reserves_in_insert (void *cls, + struct TALER_MINTDB_Session *session, + struct Reserve *reserve, + const struct TALER_Amount *balance, + const struct GNUNET_TIME_Absolute expiry) +{ + struct TALER_AmountNBO balance_nbo; + struct GNUNET_TIME_AbsoluteNBO expiry_nbo; + PGresult *result; + int reserve_exists; + + result = NULL; + if (NULL == reserve) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != postgres_start (cls, + session)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + reserve_exists = postgres_reserve_get (cls, + session, + reserve); + if (GNUNET_SYSERR == reserve_exists) + { + postgres_rollback (cls, + session); + return GNUNET_SYSERR; + } + TALER_amount_hton (&balance_nbo, + balance); + expiry_nbo = GNUNET_TIME_absolute_hton (expiry); + if (GNUNET_NO == reserve_exists) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Reserve does not exist; creating a new one\n"); + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR (&reserve->pub), + TALER_PQ_QUERY_PARAM_PTR (&balance_nbo.value), + TALER_PQ_QUERY_PARAM_PTR (&balance_nbo.fraction), + TALER_PQ_QUERY_PARAM_PTR_SIZED (balance_nbo.currency, + TALER_PQ_CURRENCY_LEN), + TALER_PQ_QUERY_PARAM_PTR (&expiry_nbo), + TALER_PQ_QUERY_PARAM_END + }; + result = TALER_PQ_exec_prepared (session->conn, + "create_reserve", + params); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + QUERY_ERR (result); + goto rollback; + } + } + if (NULL != result) + PQclear (result); + result = NULL; + /* create new incoming transaction */ + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR (&reserve->pub), + TALER_PQ_QUERY_PARAM_PTR (&balance_nbo.value), + TALER_PQ_QUERY_PARAM_PTR (&balance_nbo.fraction), + TALER_PQ_QUERY_PARAM_PTR_SIZED (&balance_nbo.currency, + TALER_PQ_CURRENCY_LEN), + TALER_PQ_QUERY_PARAM_PTR (&expiry_nbo), + TALER_PQ_QUERY_PARAM_END + }; + result = TALER_PQ_exec_prepared (session->conn, + "create_reserves_in_transaction", + params); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + QUERY_ERR (result); + goto rollback; + } + PQclear (result); + result = NULL; + if (GNUNET_NO == reserve_exists) + { + if (GNUNET_OK != postgres_commit (cls, + session)) + return GNUNET_SYSERR; + reserve->balance = *balance; + reserve->expiry = expiry; + return GNUNET_OK; + } + /* Update reserve */ + struct Reserve updated_reserve; + updated_reserve.pub = reserve->pub; + + if (GNUNET_OK != + TALER_amount_add (&updated_reserve.balance, + &reserve->balance, + balance)) + { + return GNUNET_SYSERR; + } + updated_reserve.expiry = GNUNET_TIME_absolute_max (expiry, reserve->expiry); + if (GNUNET_OK != postgres_reserves_update (cls, + session, + &updated_reserve)) + goto rollback; + if (GNUNET_OK != postgres_commit (cls, + session)) + return GNUNET_SYSERR; + reserve->balance = updated_reserve.balance; + reserve->expiry = updated_reserve.expiry; + return GNUNET_OK; + + rollback: + PQclear (result); + postgres_rollback (cls, + session); + return GNUNET_SYSERR; +} + + +/** + * Locate the response for a /withdraw request under the + * key of the hash of the blinded message. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection to use + * @param h_blind hash of the blinded message + * @param collectable corresponding collectable coin (blind signature) + * if a coin is found + * @return #GNUNET_SYSERR on internal error + * #GNUNET_NO if the collectable was not found + * #GNUNET_YES on success + */ +static int +postgres_get_collectable_blindcoin (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_HashCode *h_blind, + struct CollectableBlindcoin *collectable) +{ + PGresult *result; + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR (h_blind), + TALER_PQ_QUERY_PARAM_END + }; + struct GNUNET_CRYPTO_rsa_PublicKey *denom_pub; + struct GNUNET_CRYPTO_rsa_Signature *denom_sig; + char *denom_pub_enc; + char *denom_sig_enc; + size_t denom_pub_enc_size; + size_t denom_sig_enc_size; + int ret; + + ret = GNUNET_SYSERR; + denom_pub = NULL; + denom_pub_enc = NULL; + denom_sig_enc = NULL; + result = TALER_PQ_exec_prepared (session->conn, + "get_collectable_blindcoin", + params); + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + QUERY_ERR (result); + goto cleanup; + } + if (0 == PQntuples (result)) + { + ret = GNUNET_NO; + goto cleanup; + } + struct TALER_PQ_ResultSpec rs[] = { + TALER_PQ_RESULT_SPEC_VAR("denom_pub", &denom_pub_enc, &denom_pub_enc_size), + TALER_PQ_RESULT_SPEC_VAR("denom_sig", &denom_sig_enc, &denom_sig_enc_size), + TALER_PQ_RESULT_SPEC("reserve_sig", &collectable->reserve_sig), + TALER_PQ_RESULT_SPEC("reserve_pub", &collectable->reserve_pub), + TALER_PQ_RESULT_SPEC_END + }; + + if (GNUNET_OK != TALER_PQ_extract_result (result, rs, 0)) + { + GNUNET_break (0); + goto cleanup; + } + denom_pub = GNUNET_CRYPTO_rsa_public_key_decode (denom_pub_enc, + denom_pub_enc_size); + denom_sig = GNUNET_CRYPTO_rsa_signature_decode (denom_sig_enc, + denom_sig_enc_size); + if ((NULL == denom_pub) || (NULL == denom_sig)) + { + GNUNET_break (0); + goto cleanup; + } + collectable->denom_pub.rsa_public_key = denom_pub; + collectable->sig.rsa_signature = denom_sig; + ret = GNUNET_YES; + + cleanup: + PQclear (result); + GNUNET_free_non_null (denom_pub_enc); + GNUNET_free_non_null (denom_sig_enc); + if (GNUNET_YES != ret) + { if (NULL != denom_pub) + GNUNET_CRYPTO_rsa_public_key_free (denom_pub); + if (NULL != denom_sig) + GNUNET_CRYPTO_rsa_signature_free (denom_sig); + } + return ret; +} + + +/** + * Store collectable bit coin under the corresponding + * hash of the blinded message. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection to use + * @param h_blind hash of the blinded message + * @param withdraw amount by which the reserve will be withdrawn with this + * transaction + * @param collectable corresponding collectable coin (blind signature) + * if a coin is found + * @return #GNUNET_SYSERR on internal error + * #GNUNET_NO if the collectable was not found + * #GNUNET_YES on success + */ +static int +postgres_insert_collectable_blindcoin (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_HashCode *h_blind, + struct TALER_Amount withdraw, + const struct CollectableBlindcoin *collectable) +{ + PGresult *result; + struct Reserve reserve; + char *denom_pub_enc = NULL; + char *denom_sig_enc = NULL; + size_t denom_pub_enc_size; + size_t denom_sig_enc_size; + int ret; + + ret = GNUNET_SYSERR; + denom_pub_enc_size = + GNUNET_CRYPTO_rsa_public_key_encode (collectable->denom_pub.rsa_public_key, + &denom_pub_enc); + denom_sig_enc_size = + GNUNET_CRYPTO_rsa_signature_encode (collectable->sig.rsa_signature, + &denom_sig_enc); + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR (h_blind), + TALER_PQ_QUERY_PARAM_PTR_SIZED (denom_pub_enc, denom_pub_enc_size - 1), + TALER_PQ_QUERY_PARAM_PTR_SIZED (denom_sig_enc, denom_sig_enc_size - 1), /* DB doesn't like the trailing \0 */ + TALER_PQ_QUERY_PARAM_PTR (&collectable->reserve_pub), + TALER_PQ_QUERY_PARAM_PTR (&collectable->reserve_sig), + TALER_PQ_QUERY_PARAM_END + }; + if (GNUNET_OK != postgres_start (cls, + session)) + goto cleanup; + result = TALER_PQ_exec_prepared (session->conn, + "insert_collectable_blindcoin", + params); + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + QUERY_ERR (result); + goto rollback; + } + reserve.pub = collectable->reserve_pub; + if (GNUNET_OK != postgres_reserve_get (cls, + session, + &reserve)) + goto rollback; + if (GNUNET_SYSERR == + TALER_amount_subtract (&reserve.balance, + &reserve.balance, + &withdraw)) + goto rollback; + if (GNUNET_OK != postgres_reserves_update (cls, + session, + &reserve)) + goto rollback; + if (GNUNET_OK == postgres_commit (cls, + session)) + { + ret = GNUNET_OK; + goto cleanup; + } + + rollback: + postgres_rollback (cls, + session); + cleanup: + PQclear (result); + GNUNET_free_non_null (denom_pub_enc); + GNUNET_free_non_null (denom_sig_enc); + return ret; +} + + +/** + * Get all of the transaction history associated with the specified + * reserve. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session connection to use + * @param reserve_pub public key of the reserve + * @return known transaction history (NULL if reserve is unknown) + */ +static struct ReserveHistory * +postgres_get_reserve_history (void *cls, + struct TALER_MINTDB_Session *session, + const struct TALER_ReservePublicKeyP *reserve_pub) +{ + PGresult *result; + struct ReserveHistory *rh; + struct ReserveHistory *rh_head; + int rows; + int ret; + + result = NULL; + rh = NULL; + rh_head = NULL; + ret = GNUNET_SYSERR; + { + struct BankTransfer *bt; + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR (reserve_pub), + TALER_PQ_QUERY_PARAM_END + }; + + result = TALER_PQ_exec_prepared (session->conn, + "get_reserves_in_transactions", + params); + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + QUERY_ERR (result); + goto cleanup; + } + if (0 == (rows = PQntuples (result))) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Asked to fetch history for an unknown reserve.\n"); + goto cleanup; + } + while (0 < rows) + { + bt = GNUNET_new (struct BankTransfer); + if (GNUNET_OK != TALER_PQ_extract_amount (result, + --rows, + "balance_value", + "balance_fraction", + "balance_currency", + &bt->amount)) + { + GNUNET_free (bt); + GNUNET_break (0); + goto cleanup; + } + bt->reserve_pub = *reserve_pub; + if (NULL != rh_head) + { + rh_head->next = GNUNET_new (struct ReserveHistory); + rh_head = rh_head->next; + } + else + { + rh_head = GNUNET_new (struct ReserveHistory); + rh = rh_head; + } + rh_head->type = TALER_MINT_DB_RO_BANK_TO_MINT; + rh_head->details.bank = bt; + } + } + PQclear (result); + result = NULL; + { + struct GNUNET_HashCode blind_ev; + struct TALER_ReserveSignatureP reserve_sig; + struct CollectableBlindcoin *cbc; + char *denom_pub_enc; + char *denom_sig_enc; + size_t denom_pub_enc_size; + size_t denom_sig_enc_size; + + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR (reserve_pub), + TALER_PQ_QUERY_PARAM_END + }; + result = TALER_PQ_exec_prepared (session->conn, + "get_reserves_blindcoins", + params); + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + QUERY_ERR (result); + goto cleanup; + } + if (0 == (rows = PQntuples (result))) + { + ret = GNUNET_OK; /* Its OK if there are no withdrawls yet */ + goto cleanup; + } + struct TALER_PQ_ResultSpec rs[] = { + TALER_PQ_RESULT_SPEC ("blind_ev", &blind_ev), + TALER_PQ_RESULT_SPEC_VAR ("denom_pub", &denom_pub_enc, &denom_pub_enc_size), + TALER_PQ_RESULT_SPEC_VAR ("denom_sig", &denom_sig_enc, &denom_sig_enc_size), + TALER_PQ_RESULT_SPEC ("reserve_sig", &reserve_sig), + TALER_PQ_RESULT_SPEC_END + }; + GNUNET_assert (NULL != rh); + GNUNET_assert (NULL != rh_head); + GNUNET_assert (NULL == rh_head->next); + while (0 < rows) + { + if (GNUNET_YES != TALER_PQ_extract_result (result, rs, --rows)) + { + GNUNET_break (0); + goto cleanup; + } + cbc = GNUNET_new (struct CollectableBlindcoin); + cbc->sig.rsa_signature + = GNUNET_CRYPTO_rsa_signature_decode (denom_sig_enc, + denom_sig_enc_size); + GNUNET_free (denom_sig_enc); + denom_sig_enc = NULL; + cbc->denom_pub.rsa_public_key + = GNUNET_CRYPTO_rsa_public_key_decode (denom_pub_enc, + denom_pub_enc_size); + GNUNET_free (denom_pub_enc); + denom_pub_enc = NULL; + if ( (NULL == cbc->sig.rsa_signature) || + (NULL == cbc->denom_pub.rsa_public_key) ) + { + if (NULL != cbc->sig.rsa_signature) + GNUNET_CRYPTO_rsa_signature_free (cbc->sig.rsa_signature); + if (NULL != cbc->denom_pub.rsa_public_key) + GNUNET_CRYPTO_rsa_public_key_free (cbc->denom_pub.rsa_public_key); + GNUNET_free (cbc); + GNUNET_break (0); + goto cleanup; + } + (void) memcpy (&cbc->h_coin_envelope, &blind_ev, sizeof (blind_ev)); + (void) memcpy (&cbc->reserve_pub, reserve_pub, sizeof (cbc->reserve_pub)); + (void) memcpy (&cbc->reserve_sig, &reserve_sig, sizeof (cbc->reserve_sig)); + rh_head->next = GNUNET_new (struct ReserveHistory); + rh_head = rh_head->next; + rh_head->type = TALER_MINT_DB_RO_WITHDRAW_COIN; + rh_head->details.withdraw = cbc; + } + } + ret = GNUNET_OK; + + cleanup: + if (NULL != result) + PQclear (result); + if (GNUNET_SYSERR == ret) + { + common_free_reserve_history (cls, + rh); + rh = NULL; + } + return rh; +} + + +/** + * Check if we have the specified deposit already in the database. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param deposit deposit to search for + * @return #GNUNET_YES if we know this operation, + * #GNUNET_NO if this deposit is unknown to us + */ +static int +postgres_have_deposit (void *cls, + struct TALER_MINTDB_Session *session, + const struct Deposit *deposit) +{ + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR (&deposit->coin.coin_pub), + TALER_PQ_QUERY_PARAM_PTR (&deposit->transaction_id), + TALER_PQ_QUERY_PARAM_PTR (&deposit->merchant_pub), + TALER_PQ_QUERY_PARAM_END + }; + PGresult *result; + int ret; + + ret = GNUNET_SYSERR; + result = TALER_PQ_exec_prepared (session->conn, + "get_deposit", + params); + if (PGRES_TUPLES_OK != + PQresultStatus (result)) + { + BREAK_DB_ERR (result); + goto cleanup; + } + + if (0 == PQntuples (result)) + { + ret = GNUNET_NO; + goto cleanup; + } + ret = GNUNET_YES; + + cleanup: + PQclear (result); + return ret; +} + + +/** + * Insert information about deposited coin into the + * database. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session connection to the database + * @param deposit deposit information to store + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +static int +postgres_insert_deposit (void *cls, + struct TALER_MINTDB_Session *session, + const struct Deposit *deposit) +{ + char *denom_pub_enc; + char *denom_sig_enc; + char *json_wire_enc; + PGresult *result; + struct TALER_AmountNBO amount_nbo; + size_t denom_pub_enc_size; + size_t denom_sig_enc_size; + int ret; + + ret = GNUNET_SYSERR; + denom_pub_enc_size = + GNUNET_CRYPTO_rsa_public_key_encode (deposit->coin.denom_pub.rsa_public_key, + &denom_pub_enc); + denom_sig_enc_size = + GNUNET_CRYPTO_rsa_signature_encode (deposit->coin.denom_sig.rsa_signature, + &denom_sig_enc); + json_wire_enc = json_dumps (deposit->wire, JSON_COMPACT); + TALER_amount_hton (&amount_nbo, + &deposit->amount_with_fee); + struct TALER_PQ_QueryParam params[]= { + TALER_PQ_QUERY_PARAM_PTR (&deposit->coin.coin_pub), + TALER_PQ_QUERY_PARAM_PTR_SIZED (denom_pub_enc, denom_pub_enc_size), + TALER_PQ_QUERY_PARAM_PTR_SIZED (denom_sig_enc, denom_sig_enc_size), + TALER_PQ_QUERY_PARAM_PTR (&deposit->transaction_id), + TALER_PQ_QUERY_PARAM_PTR (&amount_nbo.value), + TALER_PQ_QUERY_PARAM_PTR (&amount_nbo.fraction), + TALER_PQ_QUERY_PARAM_PTR_SIZED (amount_nbo.currency, + TALER_CURRENCY_LEN - 1), + TALER_PQ_QUERY_PARAM_PTR (&deposit->merchant_pub), + TALER_PQ_QUERY_PARAM_PTR (&deposit->h_contract), + TALER_PQ_QUERY_PARAM_PTR (&deposit->h_wire), + TALER_PQ_QUERY_PARAM_PTR (&deposit->csig), + TALER_PQ_QUERY_PARAM_PTR_SIZED (json_wire_enc, + strlen (json_wire_enc)), + TALER_PQ_QUERY_PARAM_END + }; + result = TALER_PQ_exec_prepared (session->conn, "insert_deposit", params); + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + goto cleanup; + } + ret = GNUNET_OK; + + cleanup: + PQclear (result); + GNUNET_free_non_null (denom_pub_enc); + GNUNET_free_non_null (denom_sig_enc); + GNUNET_free_non_null (json_wire_enc); + return ret; +} + + +/** + * Lookup refresh session data under the given @a session_hash. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database handle to use + * @param session_hash hash over the melt to use to locate the session + * @param refresh_session[OUT] where to store the result + * @return #GNUNET_YES on success, + * #GNUNET_NO if not found, + * #GNUNET_SYSERR on DB failure + */ +static int +postgres_get_refresh_session (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_HashCode *session_hash, + struct RefreshSession *refresh_session) +{ + // FIXME: check logic! + int res; + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR(session_hash), + TALER_PQ_QUERY_PARAM_END + }; + + PGresult *result = TALER_PQ_exec_prepared (session->conn, + "get_refresh_session", + params); + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Query failed: %s\n", + PQresultErrorMessage (result)); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 == PQntuples (result)) + return GNUNET_NO; + + GNUNET_assert (1 == PQntuples (result)); + + /* We're done if the caller is only interested in + * whether the session exists or not */ + + if (NULL == refresh_session) + return GNUNET_YES; + + memset (session, 0, sizeof (struct RefreshSession)); + + struct TALER_PQ_ResultSpec rs[] = { + TALER_PQ_RESULT_SPEC("num_oldcoins", &refresh_session->num_oldcoins), + TALER_PQ_RESULT_SPEC("num_newcoins", &refresh_session->num_newcoins), + TALER_PQ_RESULT_SPEC("noreveal_index", &refresh_session->noreveal_index), + TALER_PQ_RESULT_SPEC_END + }; + + res = TALER_PQ_extract_result (result, rs, 0); + + if (GNUNET_OK != res) + { + GNUNET_break (0); + PQclear (result); + return GNUNET_SYSERR; + } + + refresh_session->num_oldcoins = ntohs (refresh_session->num_oldcoins); + refresh_session->num_newcoins = ntohs (refresh_session->num_newcoins); + refresh_session->noreveal_index = ntohs (refresh_session->noreveal_index); + + PQclear (result); + return GNUNET_YES; +} + + +/** + * Store new refresh session data under the given @a session_hash. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database handle to use + * @param session_hash hash over the melt to use to locate the session + * @param refresh_session session data to store + * @return #GNUNET_YES on success, + * #GNUNET_SYSERR on DB failure + */ +static int +postgres_create_refresh_session (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_HashCode *session_hash, + const struct RefreshSession *refresh_session) +{ + // FIXME: actually store session data! + uint16_t noreveal_index; + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR(session_hash), + TALER_PQ_QUERY_PARAM_PTR(&noreveal_index), + TALER_PQ_QUERY_PARAM_END + }; + + noreveal_index = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, 1<<15); + noreveal_index = htonl (noreveal_index); + + PGresult *result = TALER_PQ_exec_prepared (session->conn, + "insert_refresh_session", + params); + + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +/** + * Store the given /refresh/melt request in the database. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param oldcoin_index index of the coin to store + * @param melt melt operation details to store; includes + * the session hash of the melt + * @return #GNUNET_OK on success + * #GNUNET_SYSERR on internal error + */ +static int +postgres_insert_refresh_melt (void *cls, + struct TALER_MINTDB_Session *session, + uint16_t oldcoin_index, + const struct RefreshMelt *melt) +{ + // FIXME: check logic! + uint16_t oldcoin_index_nbo = htons (oldcoin_index); + char *buf; + size_t buf_size; + PGresult *result; + + buf_size = GNUNET_CRYPTO_rsa_public_key_encode (melt->coin.denom_pub.rsa_public_key, + &buf); + { + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR(&melt->session_hash), + TALER_PQ_QUERY_PARAM_PTR(&oldcoin_index_nbo), + TALER_PQ_QUERY_PARAM_PTR(&melt->coin.coin_pub), + TALER_PQ_QUERY_PARAM_PTR_SIZED(buf, buf_size), + TALER_PQ_QUERY_PARAM_END + }; + result = TALER_PQ_exec_prepared (session->conn, + "insert_refresh_melt", + params); + } + GNUNET_free (buf); + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + return GNUNET_OK; +} + + +/** + * Get information about melted coin details from the database. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param refresh_session session key of the melt operation + * @param oldcoin_index index of the coin to retrieve + * @param melt melt data to fill in + * @return #GNUNET_OK on success + * #GNUNET_SYSERR on internal error + */ +static int +postgres_get_refresh_melt (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_HashCode *session_hash, + uint16_t oldcoin_index, + struct RefreshMelt *melt) +{ + // FIXME: check logic! + GNUNET_break (0); + return GNUNET_SYSERR; +} + + +/** + * Store in the database which coin(s) we want to create + * in a given refresh operation. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param session_hash hash to identify refresh session + * @param num_newcoins number of coins to generate, size of the @a denom_pubs array + * @param denom_pubs array denominations of the coins to create + * @return #GNUNET_OK on success + * #GNUNET_SYSERR on internal error + */ +static int +postgres_insert_refresh_order (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_HashCode *session_hash, + uint16_t num_newcoins, + const struct TALER_DenominationPublicKey *denom_pubs) +{ + // FIXME: check logic: was written for just one COIN! + uint16_t newcoin_index_nbo = htons (num_newcoins); + char *buf; + size_t buf_size; + PGresult *result; + + buf_size = GNUNET_CRYPTO_rsa_public_key_encode (denom_pubs->rsa_public_key, + &buf); + + { + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR (&newcoin_index_nbo), + TALER_PQ_QUERY_PARAM_PTR (session_hash), + TALER_PQ_QUERY_PARAM_PTR_SIZED (buf, buf_size), + TALER_PQ_QUERY_PARAM_END + }; + result = TALER_PQ_exec_prepared (session->conn, + "insert_refresh_order", + params); + } + GNUNET_free (buf); + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + if (0 != strcmp ("1", PQcmdTuples (result))) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + PQclear (result); + return GNUNET_OK; +} + + +/** + * Lookup in the database the coins that we want to + * create in the given refresh operation. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param session_hash hash to identify refresh session + * @param newcoin_index array of the @a denom_pubs array + * @param denom_pubs where to store the deomination keys + * @return #GNUNET_OK on success + * #GNUNET_SYSERR on internal error + */ +static int +postgres_get_refresh_order (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_HashCode *session_hash, + uint16_t num_newcoins, + struct TALER_DenominationPublicKey *denom_pubs) +{ + // FIXME: check logic -- was written for just one coin! + char *buf; + size_t buf_size; + uint16_t newcoin_index_nbo = htons (num_newcoins); + + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR(session_hash), + TALER_PQ_QUERY_PARAM_PTR(&newcoin_index_nbo), + TALER_PQ_QUERY_PARAM_END + }; + + PGresult *result = TALER_PQ_exec_prepared (session->conn, + "get_refresh_order", params); + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 == PQntuples (result)) + { + PQclear (result); + /* FIXME: may want to distinguish between different error cases! */ + return GNUNET_SYSERR; + } + GNUNET_assert (1 == PQntuples (result)); + struct TALER_PQ_ResultSpec rs[] = { + TALER_PQ_RESULT_SPEC_VAR ("denom_pub", &buf, &buf_size), + TALER_PQ_RESULT_SPEC_END + }; + if (GNUNET_OK != TALER_PQ_extract_result (result, rs, 0)) + { + PQclear (result); + GNUNET_break (0); + return GNUNET_SYSERR; + } + PQclear (result); + denom_pubs->rsa_public_key + = GNUNET_CRYPTO_rsa_public_key_decode (buf, + buf_size); + GNUNET_free (buf); + return GNUNET_OK; +} + + + +/** + * Store information about the commitment of the + * given coin for the given refresh session in the database. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection to use + * @param session_hash hash to identify refresh session + * @param i set index (1st dimension) + * @param num_newcoins coin index size of the @a commit_coins array + * @param commit_coins array of coin commitments to store + * @return #GNUNET_OK on success + * #GNUNET_SYSERR on error + */ +static int +postgres_insert_refresh_commit_coins (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_HashCode *session_hash, + unsigned int i, + unsigned int num_newcoins, + const struct RefreshCommitCoin *commit_coins) +{ + // FIXME: check logic! -- was written for single commit_coin! + uint16_t cnc_index_nbo = htons (i); + uint16_t newcoin_index_nbo = htons (num_newcoins); + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR(session_hash), + TALER_PQ_QUERY_PARAM_PTR_SIZED(commit_coins->coin_ev, commit_coins->coin_ev_size), + TALER_PQ_QUERY_PARAM_PTR(&cnc_index_nbo), + TALER_PQ_QUERY_PARAM_PTR(&newcoin_index_nbo), + TALER_PQ_QUERY_PARAM_PTR_SIZED (commit_coins->refresh_link->coin_priv_enc, + commit_coins->refresh_link->blinding_key_enc_size + + sizeof (union TALER_CoinSpendPrivateKeyP)), + TALER_PQ_QUERY_PARAM_END + }; + + PGresult *result = TALER_PQ_exec_prepared (session->conn, + "insert_refresh_commit_coin", + params); + + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 != strcmp ("1", PQcmdTuples (result))) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +/** + * Obtain information about the commitment of the + * given coin of the given refresh session from the database. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection to use + * @param session_hash hash to identify refresh session + * @param i set index (1st dimension) + * @param j coin index (2nd dimension), corresponds to refreshed (new) coins + * @param commit_coin[OUT] coin commitment to return + * @return #GNUNET_OK on success + * #GNUNET_NO if not found + * #GNUNET_SYSERR on error + */ +static int +postgres_get_refresh_commit_coins (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_HashCode *session_hash, + unsigned int cnc_index, + unsigned int newcoin_index, + struct RefreshCommitCoin *cc) +{ + // FIXME: check logic! + uint16_t cnc_index_nbo = htons (cnc_index); + uint16_t newcoin_index_nbo = htons (newcoin_index); + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR(session_hash), + TALER_PQ_QUERY_PARAM_PTR(&cnc_index_nbo), + TALER_PQ_QUERY_PARAM_PTR(&newcoin_index_nbo), + TALER_PQ_QUERY_PARAM_END + }; + char *c_buf; + size_t c_buf_size; + char *rl_buf; + size_t rl_buf_size; + struct TALER_RefreshLinkEncrypted *rl; + + PGresult *result = TALER_PQ_exec_prepared (session->conn, + "get_refresh_commit_coin", + params); + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 == PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } + + struct TALER_PQ_ResultSpec rs[] = { + TALER_PQ_RESULT_SPEC_VAR("coin_ev", &c_buf, &c_buf_size), + TALER_PQ_RESULT_SPEC_VAR("link_vector_enc", &rl_buf, &rl_buf_size), + TALER_PQ_RESULT_SPEC_END + }; + if (GNUNET_YES != TALER_PQ_extract_result (result, rs, 0)) + { + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + if (rl_buf_size < sizeof (union TALER_CoinSpendPrivateKeyP)) + { + GNUNET_free (c_buf); + GNUNET_free (rl_buf); + return GNUNET_SYSERR; + } + rl = TALER_refresh_link_encrypted_decode (rl_buf, + rl_buf_size); + GNUNET_free (rl_buf); + cc->refresh_link = rl; + cc->coin_ev = c_buf; + cc->coin_ev_size = c_buf_size; + return GNUNET_YES; +} + + +/** + * Store the commitment to the given (encrypted) refresh link data + * for the given refresh session. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection to use + * @param session_hash hash to identify refresh session + * @param i set index (1st dimension) + * @param j coin index (2nd dimension), corresponds to melted (old) coins + * @param commit_link link information to store + * @return #GNUNET_SYSERR on internal error, #GNUNET_OK on success + */ +static int +postgres_insert_refresh_commit_links (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_HashCode *session_hash, + unsigned int i, + unsigned int j, + const struct RefreshCommitLink *commit_link) +{ + // FIXME: check logic! + uint16_t cnc_index_nbo = htons (i); + uint16_t oldcoin_index_nbo = htons (j); + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR(session_hash), + TALER_PQ_QUERY_PARAM_PTR(&commit_link->transfer_pub), + TALER_PQ_QUERY_PARAM_PTR(&cnc_index_nbo), + TALER_PQ_QUERY_PARAM_PTR(&oldcoin_index_nbo), + TALER_PQ_QUERY_PARAM_PTR(&commit_link->shared_secret_enc), + TALER_PQ_QUERY_PARAM_END + }; + + PGresult *result = TALER_PQ_exec_prepared (session->conn, + "insert_refresh_commit_link", + params); + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 != strcmp ("1", PQcmdTuples (result))) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +/** + * Obtain the commited (encrypted) refresh link data + * for the given refresh session. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection to use + * @param session_hash hash to identify refresh session + * @param i set index (1st dimension) + * @param num_links size of the @a commit_link array + * @param links[OUT] array of link information to return + * @return #GNUNET_SYSERR on internal error, + * #GNUNET_NO if commitment was not found + * #GNUNET_OK on success + */ +static int +postgres_get_refresh_commit_links (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_HashCode *session_hash, + unsigned int i, + unsigned int num_links, + struct RefreshCommitLink *links) +{ + // FIXME: check logic: was written for a single link! + uint16_t cnc_index_nbo = htons (i); + uint16_t oldcoin_index_nbo = htons (num_links); + + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR(session_hash), + TALER_PQ_QUERY_PARAM_PTR(&cnc_index_nbo), + TALER_PQ_QUERY_PARAM_PTR(&oldcoin_index_nbo), + TALER_PQ_QUERY_PARAM_END + }; + + PGresult *result = TALER_PQ_exec_prepared (session->conn, + "get_refresh_commit_link", + params); + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 == PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } + + struct TALER_PQ_ResultSpec rs[] = { + TALER_PQ_RESULT_SPEC("transfer_pub", &links->transfer_pub), + TALER_PQ_RESULT_SPEC("link_secret_enc", &links->shared_secret_enc), + TALER_PQ_RESULT_SPEC_END + }; + + if (GNUNET_YES != TALER_PQ_extract_result (result, rs, 0)) + { + PQclear (result); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +/** + * Insert signature of a new coin generated during refresh into + * the database indexed by the refresh session and the index + * of the coin. This data is later used should an old coin + * be used to try to obtain the private keys during "/refresh/link". + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param session_hash hash to identify refresh session + * @param newcoin_index coin index + * @param ev_sig coin signature + * @return #GNUNET_OK on success + */ +static int +postgres_insert_refresh_collectable (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_HashCode *session_hash, + uint16_t newcoin_index, + const struct TALER_DenominationSignature *ev_sig) +{ + // FIXME: check logic! + uint16_t newcoin_index_nbo = htons (newcoin_index); + char *buf; + size_t buf_size; + PGresult *result; + + buf_size = GNUNET_CRYPTO_rsa_signature_encode (ev_sig->rsa_signature, + &buf); + { + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR(session_hash), + TALER_PQ_QUERY_PARAM_PTR(&newcoin_index_nbo), + TALER_PQ_QUERY_PARAM_PTR_SIZED(buf, buf_size), + TALER_PQ_QUERY_PARAM_END + }; + result = TALER_PQ_exec_prepared (session->conn, + "insert_refresh_collectable", + params); + } + GNUNET_free (buf); + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + return GNUNET_OK; +} + + +/** + * Obtain the link data of a coin, that is the encrypted link + * information, the denomination keys and the signatures. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param coin_pub public key to use to retrieve linkage data + * @return all known link data for the coin + */ +static struct LinkDataList * +postgres_get_link_data_list (void *cls, + struct TALER_MINTDB_Session *session, + const union TALER_CoinSpendPublicKeyP *coin_pub) +{ + // FIXME: check logic! + struct LinkDataList *ldl; + struct LinkDataList *pos; + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR(coin_pub), + TALER_PQ_QUERY_PARAM_END + }; + PGresult *result = TALER_PQ_exec_prepared (session->conn, "get_link", params); + + ldl = NULL; + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return NULL; + } + + if (0 == PQntuples (result)) + { + PQclear (result); + return NULL; + } + + + int i = 0; + + for (i = 0; i < PQntuples (result); i++) + { + struct TALER_RefreshLinkEncrypted *link_enc; + struct GNUNET_CRYPTO_rsa_PublicKey *denom_pub; + struct GNUNET_CRYPTO_rsa_Signature *sig; + char *ld_buf; + size_t ld_buf_size; + char *pk_buf; + size_t pk_buf_size; + char *sig_buf; + size_t sig_buf_size; + struct TALER_PQ_ResultSpec rs[] = { + TALER_PQ_RESULT_SPEC_VAR("link_vector_enc", &ld_buf, &ld_buf_size), + TALER_PQ_RESULT_SPEC_VAR("denom_pub", &pk_buf, &pk_buf_size), + TALER_PQ_RESULT_SPEC_VAR("ev_sig", &sig_buf, &sig_buf_size), + TALER_PQ_RESULT_SPEC_END + }; + + if (GNUNET_OK != TALER_PQ_extract_result (result, rs, i)) + { + PQclear (result); + GNUNET_break (0); + common_free_link_data_list (cls, + ldl); + return NULL; + } + if (ld_buf_size < sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey)) + { + PQclear (result); + GNUNET_free (pk_buf); + GNUNET_free (sig_buf); + GNUNET_free (ld_buf); + common_free_link_data_list (cls, + ldl); + return NULL; + } + // FIXME: use util API for this! + link_enc = GNUNET_malloc (sizeof (struct TALER_RefreshLinkEncrypted) + + ld_buf_size - sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey)); + link_enc->blinding_key_enc = (const char *) &link_enc[1]; + link_enc->blinding_key_enc_size = ld_buf_size - sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey); + memcpy (link_enc->coin_priv_enc, + ld_buf, + ld_buf_size); + + sig + = GNUNET_CRYPTO_rsa_signature_decode (sig_buf, + sig_buf_size); + denom_pub + = GNUNET_CRYPTO_rsa_public_key_decode (pk_buf, + pk_buf_size); + GNUNET_free (pk_buf); + GNUNET_free (sig_buf); + GNUNET_free (ld_buf); + if ( (NULL == sig) || + (NULL == denom_pub) ) + { + if (NULL != denom_pub) + GNUNET_CRYPTO_rsa_public_key_free (denom_pub); + if (NULL != sig) + GNUNET_CRYPTO_rsa_signature_free (sig); + GNUNET_free (link_enc); + GNUNET_break (0); + PQclear (result); + common_free_link_data_list (cls, + ldl); + return NULL; + } + pos = GNUNET_new (struct LinkDataList); + pos->next = ldl; + pos->link_data_enc = link_enc; + pos->denom_pub.rsa_public_key = denom_pub; + pos->ev_sig.rsa_signature = sig; + ldl = pos; + } + return ldl; +} + + +/** + * Obtain shared secret and transfer public key from the public key of + * the coin. This information and the link information returned by + * #postgres_get_link_data_list() enable the owner of an old coin to + * determine the private keys of the new coins after the melt. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param coin_pub public key of the coin + * @param transfer_pub[OUT] public transfer key + * @param shared_secret_enc[OUT] set to shared secret + * @return #GNUNET_OK on success, + * #GNUNET_NO on failure (not found) + * #GNUNET_SYSERR on internal failure (database issue) + */ +static int +postgres_get_transfer (void *cls, + struct TALER_MINTDB_Session *session, + const union TALER_CoinSpendPublicKeyP *coin_pub, + struct TALER_TransferPublicKeyP *transfer_pub, + struct TALER_EncryptedLinkSecretP *shared_secret_enc) +{ + // FIXME: check logic! + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR(coin_pub), + TALER_PQ_QUERY_PARAM_END + }; + + PGresult *result = TALER_PQ_exec_prepared (session->conn, "get_transfer", params); + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 == PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } + + if (1 != PQntuples (result)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "got %d tuples for get_transfer\n", + PQntuples (result)); + GNUNET_break (0); + return GNUNET_SYSERR; + } + + struct TALER_PQ_ResultSpec rs[] = { + TALER_PQ_RESULT_SPEC("transfer_pub", transfer_pub), + TALER_PQ_RESULT_SPEC("link_secret_enc", shared_secret_enc), + TALER_PQ_RESULT_SPEC_END + }; + + if (GNUNET_OK != TALER_PQ_extract_result (result, rs, 0)) + { + PQclear (result); + GNUNET_break (0); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +/** + * Compile a list of all (historic) transactions performed + * with the given coin (/refresh/melt and /deposit operations). + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param coin_pub coin to investigate + * @return list of transactions, NULL if coin is fresh + */ +static struct TALER_MINT_DB_TransactionList * +postgres_get_coin_transactions (void *cls, + struct TALER_MINTDB_Session *session, + const union TALER_CoinSpendPublicKeyP *coin_pub) +{ + // FIXME: check logic! + GNUNET_break (0); // FIXME: implement! + return NULL; +} + + + +/** + * Initialize Postgres database subsystem. + * + * @param cls a configuration instance + * @return NULL on error, otherwise a `struct TALER_MINTDB_Plugin` + */ +void * +libtaler_plugin_mintdb_postgres_init (void *cls) +{ + struct GNUNET_CONFIGURATION_Handle *cfg = cls; + struct PostgresClosure *pg; + struct TALER_MINTDB_Plugin *plugin; + + pg = GNUNET_new (struct PostgresClosure); + + if (0 != pthread_key_create (&pg->db_conn_threadlocal, + &db_conn_destroy)) + { + TALER_LOG_ERROR ("Cannnot create pthread key.\n"); + return NULL; + } + /* FIXME: use configuration section with "postgres" in its name... */ + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "mint", "db_conn_str", + &pg->connection_cfg_str)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "mint", + "db_conn_str"); + return NULL; + } + plugin = GNUNET_new (struct TALER_MINTDB_Plugin); + plugin->cls = pg; + plugin->get_session = &postgres_get_session; + plugin->drop_temporary = &postgres_drop_temporary; + plugin->create_tables = &postgres_create_tables; + plugin->start = &postgres_start; + plugin->commit = &postgres_commit; + plugin->rollback = &postgres_rollback; + plugin->reserve_get = &postgres_reserve_get; + plugin->reserves_in_insert = &postgres_reserves_in_insert; + plugin->get_collectable_blindcoin = &postgres_get_collectable_blindcoin; + plugin->insert_collectable_blindcoin = &postgres_insert_collectable_blindcoin; + plugin->get_reserve_history = &postgres_get_reserve_history; + plugin->free_reserve_history = &common_free_reserve_history; + plugin->have_deposit = &postgres_have_deposit; + plugin->insert_deposit = &postgres_insert_deposit; + plugin->get_refresh_session = &postgres_get_refresh_session; + plugin->create_refresh_session = &postgres_create_refresh_session; + plugin->insert_refresh_melt = &postgres_insert_refresh_melt; + plugin->get_refresh_melt = &postgres_get_refresh_melt; + plugin->insert_refresh_order = &postgres_insert_refresh_order; + plugin->get_refresh_order = &postgres_get_refresh_order; + plugin->insert_refresh_commit_coins = &postgres_insert_refresh_commit_coins; + plugin->get_refresh_commit_coins = &postgres_get_refresh_commit_coins; + plugin->insert_refresh_commit_links = &postgres_insert_refresh_commit_links; + plugin->get_refresh_commit_links = &postgres_get_refresh_commit_links; + plugin->insert_refresh_collectable = &postgres_insert_refresh_collectable; + plugin->get_link_data_list = &postgres_get_link_data_list; + plugin->free_link_data_list = &common_free_link_data_list; + plugin->get_transfer = &postgres_get_transfer; + // plugin->have_lock = &postgres_have_lock; + // plugin->insert_lock = &postgres_insert_lock; + plugin->get_coin_transactions = &postgres_get_coin_transactions; + plugin->free_coin_transaction_list = &common_free_coin_transaction_list; + return plugin; +} + + +/** + * Shutdown Postgres database subsystem. + * + * @param cls a `struct TALER_MINTDB_Plugin` + * @return NULL (always) + */ +void * +libtaler_plugin_mintdb_postgres_done (void *cls) +{ + struct TALER_MINTDB_Plugin *plugin = cls; + struct PostgresClosure *pg = plugin->cls; + + GNUNET_free (pg->connection_cfg_str); + GNUNET_free (pg); + GNUNET_free (plugin); + return NULL; +} + +/* end of plugin_mintdb_postgres.c */ diff --git a/src/mintdb/test_mintdb.c b/src/mintdb/test_mintdb.c new file mode 100644 index 000000000..a82094075 --- /dev/null +++ b/src/mintdb/test_mintdb.c @@ -0,0 +1,393 @@ +/* + This file is part of TALER + Copyright (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 <http://www.gnu.org/licenses/> +*/ +/** + * @file mint/test_mint_db.c + * @brief test cases for DB interaction functions + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + */ +#include "platform.h" +#include "plugin.h" + +static int result; + +#define FAILIF(cond) \ + do { \ + if (!(cond)){ break;} \ + GNUNET_break (0); \ + goto drop; \ + } while (0) + + +#define RND_BLK(ptr) \ + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, ptr, sizeof (*ptr)) + +#define ZR_BLK(ptr) \ + memset (ptr, 0, sizeof (*ptr)) + + +#define CURRENCY "EUR" + +/** + * Checks if the given reserve has the given amount of balance and expiry + * + * @param session the database connection + * @param pub the public key of the reserve + * @param value balance value + * @param fraction balance fraction + * @param currency currency of the reserve + * @param expiry expiration of the reserve + * @return #GNUNET_OK if the given reserve has the same balance and expiration + * as the given parameters; #GNUNET_SYSERR if not + */ +static int +check_reserve (struct TALER_MINTDB_Session *session, + const struct TALER_ReservePublicKeyP *pub, + uint64_t value, + uint32_t fraction, + const char *currency, + uint64_t expiry) +{ + struct Reserve reserve; + + reserve.pub = *pub; + + FAILIF (GNUNET_OK != + plugin->reserve_get (plugin->cls, + session, + &reserve)); + FAILIF (value != reserve.balance.value); + FAILIF (fraction != reserve.balance.fraction); + FAILIF (0 != strcmp (currency, reserve.balance.currency)); + FAILIF (expiry != reserve.expiry.abs_value_us); + + return GNUNET_OK; + drop: + return GNUNET_SYSERR; +} + + +struct DenomKeyPair +{ + struct TALER_DenominationPrivateKey priv; + struct TALER_DenominationPublicKey pub; +}; + + +static struct DenomKeyPair * +create_denom_key_pair (unsigned int size) +{ + struct DenomKeyPair *dkp; + + dkp = GNUNET_new (struct DenomKeyPair); + dkp->priv.rsa_private_key = GNUNET_CRYPTO_rsa_private_key_create (size); + GNUNET_assert (NULL != dkp->priv.rsa_private_key); + dkp->pub.rsa_public_key + = GNUNET_CRYPTO_rsa_private_key_get_public (dkp->priv.rsa_private_key); + return dkp; +} + + +static void +destroy_denon_key_pair (struct DenomKeyPair *dkp) +{ + GNUNET_CRYPTO_rsa_public_key_free (dkp->pub.rsa_public_key); + GNUNET_CRYPTO_rsa_private_key_free (dkp->priv.rsa_private_key); + GNUNET_free (dkp); +} + +/** + * Main function that will be run by the scheduler. + * + * @param cls closure + * @param args remaining command-line arguments + * @param cfgfile name of the configuration file used (for saving, can be NULL!) + * @param cfg configuration + */ +static void +run (void *cls, + char *const *args, + const char *cfgfile, + const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + struct TALER_MINTDB_Session *session; + struct TALER_ReservePublicKeyP reserve_pub; + struct Reserve reserve; + struct GNUNET_TIME_Absolute expiry; + struct TALER_Amount amount; + struct DenomKeyPair *dkp; + struct GNUNET_HashCode h_blind; + struct CollectableBlindcoin cbc; + struct CollectableBlindcoin cbc2; + struct ReserveHistory *rh; + struct ReserveHistory *rh_head; + struct BankTransfer *bt; + struct CollectableBlindcoin *withdraw; + struct Deposit deposit; + struct Deposit deposit2; + struct json_t *wire; + const char * const json_wire_str = + "{ \"type\":\"SEPA\", \ +\"IBAN\":\"DE67830654080004822650\", \ +\"name\":\"GNUnet e.V.\", \ +\"bic\":\"GENODEF1SLR\", \ +\"edate\":\"1449930207000\", \ +\"r\":123456789, \ +\"address\": \"foobar\"}"; + unsigned int cnt; + + dkp = NULL; + rh = NULL; + wire = NULL; + session = NULL; + ZR_BLK (&cbc); + ZR_BLK (&cbc2); + if (GNUNET_OK != + TALER_MINT_plugin_load (cfg)) + { + result = 1; + return; + } + if (GNUNET_OK != + plugin->create_tables (plugin->cls, + GNUNET_YES)) + { + result = 2; + goto drop; + } + if (NULL == + (session = plugin->get_session (plugin->cls, + GNUNET_YES))) + { + result = 3; + goto drop; + } + RND_BLK (&reserve_pub); + reserve.pub = reserve_pub; + amount.value = 1; + amount.fraction = 1; + strcpy (amount.currency, CURRENCY); + expiry = GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get (), + GNUNET_TIME_UNIT_HOURS); + result = 4; + FAILIF (GNUNET_OK != + plugin->reserves_in_insert (plugin->cls, + session, + &reserve, + &amount, + expiry)); + FAILIF (GNUNET_OK != + check_reserve (session, + &reserve_pub, + amount.value, + amount.fraction, + amount.currency, + expiry.abs_value_us)); + FAILIF (GNUNET_OK != + plugin->reserves_in_insert (plugin->cls, + session, + &reserve, + &amount, + expiry)); + FAILIF (GNUNET_OK != + check_reserve (session, + &reserve_pub, + ++amount.value, + ++amount.fraction, + amount.currency, + expiry.abs_value_us)); + dkp = create_denom_key_pair (1024); + RND_BLK(&h_blind); + RND_BLK(&cbc.reserve_sig); + cbc.denom_pub = dkp->pub; + cbc.sig.rsa_signature + = GNUNET_CRYPTO_rsa_sign (dkp->priv.rsa_private_key, + &h_blind, + sizeof (h_blind)); + (void) memcpy (&cbc.reserve_pub, + &reserve_pub, + sizeof (reserve_pub)); + amount.value--; + amount.fraction--; + FAILIF (GNUNET_OK != + plugin->insert_collectable_blindcoin (plugin->cls, + session, + &h_blind, + amount, + &cbc)); + FAILIF (GNUNET_OK != + check_reserve (session, + &reserve_pub, + amount.value, + amount.fraction, + amount.currency, + expiry.abs_value_us)); + FAILIF (GNUNET_YES != + plugin->get_collectable_blindcoin (plugin->cls, + session, + &h_blind, + &cbc2)); + FAILIF (NULL == cbc2.denom_pub.rsa_public_key); + FAILIF (0 != memcmp (&cbc2.reserve_sig, + &cbc.reserve_sig, + sizeof (cbc2.reserve_sig))); + FAILIF (0 != memcmp (&cbc2.reserve_pub, + &cbc.reserve_pub, + sizeof (cbc2.reserve_pub))); + FAILIF (GNUNET_OK != + GNUNET_CRYPTO_rsa_verify (&h_blind, + cbc2.sig.rsa_signature, + dkp->pub.rsa_public_key)); + rh = plugin->get_reserve_history (plugin->cls, + session, + &reserve_pub); + FAILIF (NULL == rh); + rh_head = rh; + for (cnt=0; NULL != rh_head; rh_head=rh_head->next, cnt++) + { + switch (rh_head->type) + { + case TALER_MINT_DB_RO_BANK_TO_MINT: + bt = rh_head->details.bank; + FAILIF (0 != memcmp (&bt->reserve_pub, + &reserve_pub, + sizeof (reserve_pub))); + FAILIF (1 != bt->amount.value); + FAILIF (1 != bt->amount.fraction); + FAILIF (0 != strcmp (CURRENCY, bt->amount.currency)); + FAILIF (NULL != bt->wire); /* FIXME: write wire details to db */ + break; + case TALER_MINT_DB_RO_WITHDRAW_COIN: + withdraw = rh_head->details.withdraw; + FAILIF (0 != memcmp (&withdraw->reserve_pub, + &reserve_pub, + sizeof (reserve_pub))); + FAILIF (0 != memcmp (&withdraw->h_coin_envelope, + &h_blind, sizeof (h_blind))); + break; + } + } + FAILIF (3 != cnt); + /* Tests for deposits */ + RND_BLK (&deposit.coin.coin_pub); + deposit.coin.denom_pub = dkp->pub; + deposit.coin.denom_sig = cbc.sig; + RND_BLK (&deposit.csig); + RND_BLK (&deposit.merchant_pub); + RND_BLK (&deposit.h_contract); + RND_BLK (&deposit.h_wire); + wire = json_loads (json_wire_str, 0, NULL); + deposit.wire = wire; + deposit.transaction_id = + GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK, UINT64_MAX); + deposit.amount_with_fee = amount; + FAILIF (GNUNET_OK != + plugin->insert_deposit (plugin->cls, + session, &deposit)); + FAILIF (GNUNET_YES != + plugin->have_deposit (plugin->cls, + session, + &deposit)); + (void) memcpy (&deposit2, + &deposit, + sizeof (deposit)); + deposit2.transaction_id++; /* should fail if transaction id is different */ + FAILIF (GNUNET_NO != + plugin->have_deposit (plugin->cls, + session, + &deposit2)); + deposit2.transaction_id = deposit.transaction_id; + RND_BLK (&deposit2.merchant_pub); /* should fail if merchant is different */ + FAILIF (GNUNET_NO != + plugin->have_deposit (plugin->cls, + session, + &deposit2)); + (void) memcpy (&deposit2.merchant_pub, + &deposit.merchant_pub, + sizeof (deposit.merchant_pub)); + RND_BLK (&deposit2.coin.coin_pub); /* should fail if coin is different */ + FAILIF (GNUNET_NO != + plugin->have_deposit (plugin->cls, + session, + &deposit2)); + result = 0; + + drop: + if (NULL != wire) + json_decref (wire); + if (NULL != rh) + plugin->free_reserve_history (plugin->cls, + rh); + rh = NULL; + if (NULL != session) + GNUNET_break (GNUNET_OK == + plugin->drop_temporary (plugin->cls, + session)); + if (NULL != dkp) + destroy_denon_key_pair (dkp); + if (NULL != cbc.sig.rsa_signature) + GNUNET_CRYPTO_rsa_signature_free (cbc.sig.rsa_signature); + if (NULL != cbc2.denom_pub.rsa_public_key) + GNUNET_CRYPTO_rsa_public_key_free (cbc2.denom_pub.rsa_public_key); + if (NULL != cbc2.sig.rsa_signature) + GNUNET_CRYPTO_rsa_signature_free (cbc2.sig.rsa_signature); + dkp = NULL; + TALER_MINT_plugin_unload (); +} + + +int +main (int argc, + char *const argv[]) +{ + static const struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_OPTION_END + }; + char *argv2[] = { + "test-mint-db-<plugin_name>", /* will be replaced later */ + "-c", "test-mint-db-<plugin_name>.conf", /* will be replaced later */ + NULL, + }; + const char *plugin_name; + char *config_filename; + char *testname; + + result = -1; + if (NULL == (plugin_name = strrchr (argv[0], (int) '-'))) + { + GNUNET_break (0); + return -1; + } + plugin_name++; + (void) GNUNET_asprintf (&testname, + "test-mint-db-%s", plugin_name); + (void) GNUNET_asprintf (&config_filename, + "%s.conf", testname); + argv2[0] = argv[0]; + argv2[2] = config_filename; + if (GNUNET_OK != + GNUNET_PROGRAM_run ((sizeof (argv2)/sizeof (char *)) - 1, argv2, + testname, + "Test cases for mint database helper functions.", + options, &run, NULL)) + { + GNUNET_free (config_filename); + GNUNET_free (testname); + return 3; + } + GNUNET_free (config_filename); + GNUNET_free (testname); + return result; +} diff --git a/src/mintdb/test_mintdb_deposits.c b/src/mintdb/test_mintdb_deposits.c new file mode 100644 index 000000000..dbe12e88d --- /dev/null +++ b/src/mintdb/test_mintdb_deposits.c @@ -0,0 +1,142 @@ +/* + This file is part of TALER + Copyright (C) 2014 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 <http://www.gnu.org/licenses/> +*/ +/** + * @file mint/test_mint_deposits.c + * @brief testcase for mint deposits + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + */ +#include "platform.h" +#include <libpq-fe.h> +#include <gnunet/gnunet_util_lib.h> +#include "plugin.h" +#include "taler_pq_lib.h" +#include "taler-mint-httpd.h" + +#define DB_URI "postgres:///taler" + +#define break_db_err(result) do { \ + GNUNET_break(0); \ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Database failure: %s\n", PQresultErrorMessage (result)); \ + } while (0) + +/** + * Shorthand for exit jumps. + */ +#define EXITIF(cond) \ + do { \ + if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ + } while (0) + + +/** + * Should we not interact with a temporary table? + */ +static int persistent; + +/** + * Testcase result + */ +static int result; + + +/** + * Main function that will be run by the scheduler. + * + * @param cls closure + * @param args remaining command-line arguments + * @param cfgfile name of the configuration file used (for saving, can be NULL!) + * @param cfg configuration + */ +static void +run (void *cls, + char *const *args, + const char *cfgfile, + const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + static const char wire[] = "{" + "\"type\":\"SEPA\"," + "\"IBAN\":\"DE67830654080004822650\"," + "\"NAME\":\"GNUNET E.V\"," + "\"BIC\":\"GENODEF1SRL\"" + "}"; + struct Deposit *deposit; + uint64_t transaction_id; + struct TALER_MINTDB_Session *session; + + deposit = NULL; + EXITIF (GNUNET_OK != TALER_MINT_plugin_load (cfg)); + EXITIF (GNUNET_OK != + plugin->create_tables (plugin->cls, + ! persistent)); + session = plugin->get_session (plugin->cls, + ! persistent); + EXITIF (NULL == session); + deposit = GNUNET_malloc (sizeof (struct Deposit) + sizeof (wire)); + /* Makeup a random coin public key */ + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, + deposit, + sizeof (struct Deposit)); + /* Makeup a random 64bit transaction ID */ + transaction_id = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK, + UINT64_MAX); + deposit->transaction_id = GNUNET_htonll (transaction_id); + /* Random amount */ + deposit->amount_with_fee.value = + htonl (GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, UINT32_MAX)); + deposit->amount_with_fee.fraction = + htonl (GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, UINT32_MAX)); + GNUNET_assert (strlen (TMH_MINT_CURRENCY) < sizeof (deposit->amount_with_fee.currency)); + strcpy (deposit->amount_with_fee.currency, TMH_MINT_CURRENCY); + /* Copy wireformat */ + deposit->wire = json_loads (wire, 0, NULL); + EXITIF (GNUNET_OK != + plugin->insert_deposit (plugin->cls, + session, + deposit)); + EXITIF (GNUNET_OK != + plugin->have_deposit (plugin->cls, + session, + deposit)); + result = GNUNET_OK; + + EXITIF_exit: + GNUNET_free_non_null (deposit); + return; +} + + +int +main (int argc, + char *const argv[]) +{ + static const struct GNUNET_GETOPT_CommandLineOption options[] = { + {'T', "persist", NULL, + gettext_noop ("Use a persistent database table instead of a temporary one"), + GNUNET_NO, &GNUNET_GETOPT_set_one, &persistent}, + GNUNET_GETOPT_OPTION_END + }; + + + persistent = GNUNET_NO; + result = GNUNET_SYSERR; + if (GNUNET_OK != + GNUNET_PROGRAM_run (argc, argv, + "test-mint-deposits", + "testcase for mint deposits", + options, &run, NULL)) + return 3; + return (GNUNET_OK == result) ? 0 : 1; +} diff --git a/src/mintdb/test_mintdb_keyio.c b/src/mintdb/test_mintdb_keyio.c new file mode 100644 index 000000000..83df25046 --- /dev/null +++ b/src/mintdb/test_mintdb_keyio.c @@ -0,0 +1,86 @@ +/* + This file is part of TALER + Copyright (C) 2014 GNUnet e. V. (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 <http://www.gnu.org/licenses/> +*/ +/** + * @file mint/test_mint_common.c + * @brief test cases for some functions in mint/mint_common.c + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + */ +#include "platform.h" +#include "gnunet/gnunet_util_lib.h" +#include "taler_signatures.h" +#include "key_io.h" + +#define RSA_KEY_SIZE 1024 + + +#define EXITIF(cond) \ + do { \ + if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ + } while (0) + + +int +main (int argc, + const char *const argv[]) +{ + struct TALER_DenominationKeyIssueInformation dki; + char *enc; + size_t enc_size; + struct TALER_DenominationKeyIssueInformation dki_read; + char *enc_read; + size_t enc_read_size; + char *tmpfile; + int ret; + + ret = 1; + enc = NULL; + enc_read = NULL; + tmpfile = NULL; + dki.denom_priv.rsa_private_key = NULL; + dki_read.denom_priv.rsa_private_key = NULL; + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, + &dki.issue.signature, + sizeof (dki) - offsetof (struct TALER_DenominationKeyValidityPS, + signature)); + dki.denom_priv.rsa_private_key + = GNUNET_CRYPTO_rsa_private_key_create (RSA_KEY_SIZE); + enc_size = GNUNET_CRYPTO_rsa_private_key_encode (dki.denom_priv.rsa_private_key, + &enc); + EXITIF (NULL == (tmpfile = GNUNET_DISK_mktemp ("test_mint_common"))); + EXITIF (GNUNET_OK != TALER_MINT_write_denom_key (tmpfile, &dki)); + EXITIF (GNUNET_OK != TALER_MINT_read_denom_key (tmpfile, &dki_read)); + enc_read_size = GNUNET_CRYPTO_rsa_private_key_encode (dki_read.denom_priv.rsa_private_key, + &enc_read); + EXITIF (enc_size != enc_read_size); + EXITIF (0 != memcmp (enc, + enc_read, + enc_size)); + ret = 0; + + EXITIF_exit: + GNUNET_free_non_null (enc); + if (NULL != tmpfile) + { + (void) unlink (tmpfile); + GNUNET_free (tmpfile); + } + GNUNET_free_non_null (enc_read); + if (NULL != dki.denom_priv.rsa_private_key) + GNUNET_CRYPTO_rsa_private_key_free (dki.denom_priv.rsa_private_key); + if (NULL != dki_read.denom_priv.rsa_private_key) + GNUNET_CRYPTO_rsa_private_key_free (dki_read.denom_priv.rsa_private_key); + return ret; +} |