summaryrefslogtreecommitdiff
path: root/src/mintdb
diff options
context:
space:
mode:
Diffstat (limited to 'src/mintdb')
-rw-r--r--src/mintdb/Makefile.am68
-rw-r--r--src/mintdb/mintdb_keyio.c347
-rw-r--r--src/mintdb/mintdb_plugin.c149
-rw-r--r--src/mintdb/plugin_mintdb_common.c118
-rw-r--r--src/mintdb/plugin_mintdb_postgres.c2356
-rw-r--r--src/mintdb/test_mintdb.c393
-rw-r--r--src/mintdb/test_mintdb_deposits.c142
-rw-r--r--src/mintdb/test_mintdb_keyio.c86
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;
+}