aboutsummaryrefslogtreecommitdiff
path: root/src/exchangedb
diff options
context:
space:
mode:
Diffstat (limited to 'src/exchangedb')
-rw-r--r--src/exchangedb/Makefile.am123
-rw-r--r--src/exchangedb/exchangedb-postgres.conf2
-rw-r--r--src/exchangedb/exchangedb.conf7
-rw-r--r--src/exchangedb/exchangedb_keyio.c609
-rw-r--r--src/exchangedb/exchangedb_plugin.c87
-rw-r--r--src/exchangedb/perf_taler_exchangedb.c358
-rw-r--r--src/exchangedb/perf_taler_exchangedb_init.c625
-rw-r--r--src/exchangedb/perf_taler_exchangedb_init.h257
-rw-r--r--src/exchangedb/perf_taler_exchangedb_interpreter.c1997
-rw-r--r--src/exchangedb/perf_taler_exchangedb_interpreter.h1319
-rw-r--r--src/exchangedb/perf_taler_exchangedb_values.h25
-rw-r--r--src/exchangedb/plugin_exchangedb_common.c162
-rw-r--r--src/exchangedb/plugin_exchangedb_postgres.c4295
-rw-r--r--src/exchangedb/test-exchange-db-postgres.conf8
-rw-r--r--src/exchangedb/test_exchangedb.c980
-rw-r--r--src/exchangedb/test_exchangedb_deposits.c152
-rw-r--r--src/exchangedb/test_exchangedb_keyio.c85
-rw-r--r--src/exchangedb/test_perf_taler_exchangedb.c182
18 files changed, 11273 insertions, 0 deletions
diff --git a/src/exchangedb/Makefile.am b/src/exchangedb/Makefile.am
new file mode 100644
index 000000000..0c6a73138
--- /dev/null
+++ b/src/exchangedb/Makefile.am
@@ -0,0 +1,123 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include -I$(top_srcdir)/src/pq/ $(POSTGRESQL_CPPFLAGS)
+
+if USE_COVERAGE
+ AM_CFLAGS = --coverage -O0
+ XLIB = -lgcov
+endif
+
+pkgcfgdir = $(prefix)/share/taler/config.d/
+
+pkgcfg_DATA = \
+ exchangedb.conf \
+ exchangedb-postgres.conf
+
+EXTRA_DIST = \
+ exchangedb.conf \
+ exchangedb-postgres.conf
+
+
+plugindir = $(libdir)/taler
+
+if HAVE_POSTGRESQL
+plugin_LTLIBRARIES = \
+ libtaler_plugin_exchangedb_postgres.la
+endif
+
+EXTRA_DIST = \
+ plugin_exchangedb_common.c \
+ test-exchange-db-postgres.conf
+
+libtaler_plugin_exchangedb_postgres_la_SOURCES = \
+ plugin_exchangedb_postgres.c
+libtaler_plugin_exchangedb_postgres_la_LIBADD = \
+ $(LTLIBINTL)
+libtaler_plugin_exchangedb_postgres_la_LDFLAGS = \
+ $(TALER_PLUGIN_LDFLAGS) \
+ $(top_builddir)/src/pq/libtalerpq.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ -lpq \
+ -lgnunetpq \
+ -lgnunetutil $(XLIB)
+
+lib_LTLIBRARIES = \
+ libtalerexchangedb.la
+
+libtalerexchangedb_la_SOURCES = \
+ exchangedb_keyio.c \
+ exchangedb_plugin.c
+
+libtalerexchangedb_la_LIBADD = \
+ $(top_builddir)/src/util/libtalerutil.la \
+ -lgnunetutil $(XLIB)
+
+libtalerexchangedb_la_LDFLAGS = \
+ $(POSTGRESQL_LDFLAGS) \
+ -version-info 0:0:0 \
+ -no-undefined
+
+
+check_PROGRAMS = \
+ test-exchangedb-deposits \
+ test-exchangedb-keyio \
+ test-exchangedb-postgres \
+ test-perf-taler-exchangedb \
+ perf-exchangedb
+
+AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export PATH=$${TALER_PREFIX:-@prefix@}/bin:$$PATH;
+TESTS = \
+ test-exchangedb-postgres \
+ test-perf-taler-exchangedb
+
+test_exchangedb_deposits_SOURCES = \
+ test_exchangedb_deposits.c
+test_exchangedb_deposits_LDADD = \
+ libtalerexchangedb.la \
+ $(top_srcdir)/src/util/libtalerutil.la \
+ $(top_srcdir)/src/pq/libtalerpq.la \
+ -lgnunetutil \
+ -ljansson \
+ -lpq
+
+test_exchangedb_keyio_SOURCES = \
+ test_exchangedb_keyio.c
+test_exchangedb_keyio_LDADD = \
+ libtalerexchangedb.la \
+ $(top_srcdir)/src/util/libtalerutil.la \
+ $(top_srcdir)/src/pq/libtalerpq.la \
+ -lgnunetutil
+
+test_exchangedb_postgres_SOURCES = \
+ test_exchangedb.c
+test_exchangedb_postgres_LDADD = \
+ libtalerexchangedb.la \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_srcdir)/src/util/libtalerutil.la \
+ $(top_srcdir)/src/pq/libtalerpq.la \
+ -lgnunetutil -ljansson
+
+test_perf_taler_exchangedb_SOURCES = \
+ test_perf_taler_exchangedb.c \
+ perf_taler_exchangedb_init.c perf_taler_exchangedb_init.h \
+ perf_taler_exchangedb_interpreter.c perf_taler_exchangedb_interpreter.h
+test_perf_taler_exchangedb_LDADD = \
+ libtalerexchangedb.la \
+ $(top_srcdir)/src/util/libtalerutil.la \
+ $(top_srcdir)/src/pq/libtalerpq.la \
+ -ljansson \
+ -lgnunetutil
+
+perf_exchangedb_SOURCES = \
+ perf_taler_exchangedb.c \
+ perf_taler_exchangedb_init.c perf_taler_exchangedb_init.h \
+ perf_taler_exchangedb_interpreter.c perf_taler_exchangedb_interpreter.h
+perf_exchangedb_LDADD = \
+ libtalerexchangedb.la \
+ $(top_srcdir)/src/util/libtalerutil.la \
+ $(top_srcdir)/src/pq/libtalerpq.la \
+ -ljansson \
+ -lgnunetutil
+
+
+EXTRA_test_exchangedb_postgres_DEPENDENCIES = \
+ libtaler_plugin_exchangedb_postgres.la
diff --git a/src/exchangedb/exchangedb-postgres.conf b/src/exchangedb/exchangedb-postgres.conf
new file mode 100644
index 000000000..3de7474ff
--- /dev/null
+++ b/src/exchangedb/exchangedb-postgres.conf
@@ -0,0 +1,2 @@
+[exchangedb-postgres]
+DB_CONN_STR = "postgres:///taler"
diff --git a/src/exchangedb/exchangedb.conf b/src/exchangedb/exchangedb.conf
new file mode 100644
index 000000000..19277ed23
--- /dev/null
+++ b/src/exchangedb/exchangedb.conf
@@ -0,0 +1,7 @@
+# This file is in the public domain.
+#
+# Database-backend independent specification for the exchangedb module.
+#
+[exchangedb]
+# Where do we expect to find information about auditors?
+AUDITOR_BASE_DIR = ${TALER_DATA_HOME}/auditors/
diff --git a/src/exchangedb/exchangedb_keyio.c b/src/exchangedb/exchangedb_keyio.c
new file mode 100644
index 000000000..9f170f645
--- /dev/null
+++ b/src/exchangedb/exchangedb_keyio.c
@@ -0,0 +1,609 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015, 2016 Inria & GNUnet e.V.
+
+ 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 exchangedb/exchangedb_keyio.c
+ * @brief I/O operations for the Exchange's private keys
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Sree Harsha Totakura
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_exchangedb_lib.h"
+
+
+/**
+ * Closure for the #signkeys_iterate_dir_iter().
+ */
+struct SignkeysIterateContext
+{
+
+ /**
+ * Function to call on each signing key.
+ */
+ TALER_EXCHANGEDB_SigningKeyIterator 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_EXCHANGEDB_PrivateSigningKeyInformationP issue;
+
+ nread = GNUNET_DISK_fn_read (filename,
+ &issue,
+ sizeof (struct TALER_EXCHANGEDB_PrivateSigningKeyInformationP));
+ if (nread != sizeof (struct TALER_EXCHANGEDB_PrivateSigningKeyInformationP))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Invalid signkey file `%s': wrong size (%d, expected %u)\n",
+ filename,
+ (int) nread,
+ sizeof (struct TALER_EXCHANGEDB_PrivateSigningKeyInformationP));
+ return GNUNET_OK;
+ }
+ return skc->it (skc->it_cls,
+ filename,
+ &issue);
+}
+
+
+/**
+ * Call @a it for each signing key found in the @a exchange_base_dir.
+ *
+ * @param exchange_base_dir base directory for the exchange,
+ * the signing keys must be in the #TALER_EXCHANGEDB_DIR_SIGNING_KEYS
+ * 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_EXCHANGEDB_signing_keys_iterate (const char *exchange_base_dir,
+ TALER_EXCHANGEDB_SigningKeyIterator it,
+ void *it_cls)
+{
+ char *signkey_dir;
+ struct SignkeysIterateContext skc;
+ int ret;
+
+ GNUNET_asprintf (&signkey_dir,
+ "%s" DIR_SEPARATOR_STR TALER_EXCHANGEDB_DIR_SIGNING_KEYS,
+ exchange_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_EXCHANGEDB_denomination_key_read (const char *filename,
+ struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki)
+{
+ uint64_t size;
+ size_t offset;
+ void *data;
+ struct GNUNET_CRYPTO_RsaPrivateKey *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_EXCHANGEDB_DenominationKeyInformationP);
+ 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;
+ }
+ GNUNET_assert (NULL == dki->denom_priv.rsa_private_key);
+ 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_EXCHANGEDB_denomination_key_write (const char *filename,
+ const struct TALER_EXCHANGEDB_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_EXCHANGEDB_DenominationKeyInformationP);
+ if (GNUNET_SYSERR == (wrote = GNUNET_DISK_file_write (fh,
+ &dki->issue,
+ 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_EXCHANGEDB_DenominationKeyIterator 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_EXCHANGEDB_DenominationKeyIssueInformation issue;
+ int ret;
+
+ memset (&issue, 0, sizeof (issue));
+ if (GNUNET_OK !=
+ TALER_EXCHANGEDB_denomination_key_read (filename,
+ &issue))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Invalid denomkey file: '%s'\n",
+ filename);
+ return GNUNET_OK;
+ }
+ ret = dic->it (dic->it_cls,
+ dic->alias,
+ &issue);
+ GNUNET_CRYPTO_rsa_private_key_free (issue.denom_priv.rsa_private_key);
+ GNUNET_CRYPTO_rsa_public_key_free (issue.denom_pub.rsa_public_key);
+ return ret;
+}
+
+
+/**
+ * Function called on each subdirectory in the #TALER_EXCHANGEDB_DIR_DENOMINATION_KEYS. 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 exchange_base_dir.
+ *
+ * @param exchange_base_dir base directory for the exchange,
+ * the signing keys must be in the #TALER_EXCHANGEDB_DIR_DENOMINATION_KEYS
+ * 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_EXCHANGEDB_denomination_keys_iterate (const char *exchange_base_dir,
+ TALER_EXCHANGEDB_DenominationKeyIterator it,
+ void *it_cls)
+{
+ char *dir;
+ struct DenomkeysIterateContext dic;
+ int ret;
+
+ GNUNET_asprintf (&dir,
+ "%s" DIR_SEPARATOR_STR TALER_EXCHANGEDB_DIR_DENOMINATION_KEYS,
+ exchange_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;
+}
+
+
+/**
+ * Closure for #auditor_iter() and
+ */
+struct AuditorIterateContext
+{
+
+ /**
+ * Function to call with the information for each auditor.
+ */
+ TALER_EXCHANGEDB_AuditorIterator it;
+
+ /**
+ * Closure for @e it.
+ */
+ void *it_cls;
+};
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Header of a file with auditing information.
+ */
+struct AuditorFileHeaderP
+{
+
+ /**
+ * Public key of the auditor.
+ */
+ struct TALER_AuditorPublicKeyP apub;
+
+ /**
+ * Master public key of the exchange the auditor is signing
+ * information for.
+ */
+ struct TALER_MasterPublicKeyP mpub;
+
+ /**
+ * Number of signatures and DKI entries in this file.
+ */
+ uint32_t dki_len;
+
+};
+GNUNET_NETWORK_STRUCT_END
+
+
+/**
+ * Load the auditor signature and the information signed by the
+ * auditor and call the callback in @a cls with the information.
+ *
+ * @param cls the `struct AuditorIterateContext *`
+ * @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
+auditor_iter (void *cls,
+ const char *filename)
+{
+ struct AuditorIterateContext *aic = cls;
+ uint64_t size;
+ struct AuditorFileHeaderP *af;
+ const struct TALER_AuditorSignatureP *sigs;
+ const struct TALER_DenominationKeyValidityPS *dki;
+ const char *auditor_url;
+ unsigned int dki_len;
+ size_t url_len;
+ int ret;
+
+ if (GNUNET_OK != GNUNET_DISK_file_size (filename,
+ &size,
+ GNUNET_YES,
+ GNUNET_YES))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Skipping inaccessable auditor information file `%s'\n",
+ filename);
+ return GNUNET_SYSERR;
+ }
+ if (size < sizeof (struct AuditorFileHeaderP))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ af = GNUNET_malloc (size);
+ if (size !=
+ GNUNET_DISK_fn_read (filename,
+ af,
+ size))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+ "read",
+ filename);
+ GNUNET_free (af);
+ return GNUNET_SYSERR;
+ }
+ dki_len = ntohl (af->dki_len);
+ if (0 == dki_len)
+ {
+ GNUNET_break_op (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "No signed keys in %s\n",
+ filename);
+ GNUNET_free (af);
+ return GNUNET_SYSERR;
+ }
+ if ( (size - sizeof (struct AuditorFileHeaderP)) / dki_len <
+ (sizeof (struct TALER_DenominationKeyValidityPS) +
+ sizeof (struct TALER_AuditorSignatureP)) )
+ {
+ GNUNET_break_op (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Malformed key file %s\n",
+ filename);
+ GNUNET_free (af);
+ return GNUNET_SYSERR;
+ }
+ url_len = size
+ - sizeof (struct AuditorFileHeaderP)
+ - dki_len * (sizeof (struct TALER_DenominationKeyValidityPS) +
+ sizeof (struct TALER_AuditorSignatureP));
+ sigs = (const struct TALER_AuditorSignatureP *) &af[1];
+ dki = (const struct TALER_DenominationKeyValidityPS *) &sigs[dki_len];
+ auditor_url = (const char *) &dki[dki_len];
+ if ( (0 == url_len) ||
+ ('\0' != auditor_url[url_len - 1]) )
+ {
+ GNUNET_break_op (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Malformed key file %s\n",
+ filename);
+ GNUNET_free (af);
+ return GNUNET_SYSERR;
+ }
+ ret = aic->it (aic->it_cls,
+ &af->apub,
+ auditor_url,
+ &af->mpub,
+ dki_len,
+ sigs,
+ dki);
+ GNUNET_free (af);
+ return ret;
+}
+
+
+/**
+ * Call @a it with information for each auditor found in the @a exchange_base_dir.
+ *
+ * @param cfg configuration to use
+ * @param it function to call with auditor information
+ * @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_EXCHANGEDB_auditor_iterate (const struct GNUNET_CONFIGURATION_Handle *cfg,
+ TALER_EXCHANGEDB_AuditorIterator it,
+ void *it_cls)
+{
+ struct AuditorIterateContext aic;
+ int ret;
+ char *auditor_base_dir;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_filename (cfg,
+ "exchangedb",
+ "AUDITOR_BASE_DIR",
+ &auditor_base_dir))
+ return -1;
+ aic.it = it;
+ aic.it_cls = it_cls;
+ ret = GNUNET_DISK_directory_scan (auditor_base_dir,
+ &auditor_iter,
+ &aic);
+ GNUNET_free (auditor_base_dir);
+ return ret;
+}
+
+
+/**
+ * Write auditor information to the given file.
+ *
+ * @param filename the file where to write the auditor information to
+ * @param apub the auditor's public key
+ * @param auditor_url the URL of the auditor
+ * @param asigs the auditor's signatures, array of length @a dki_len
+ * @param mpub the exchange's public key (as expected by the auditor)
+ * @param dki_len length of @a dki
+ * @param dki array of denomination coin data signed by the auditor
+ * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure.
+ */
+int
+TALER_EXCHANGEDB_auditor_write (const char *filename,
+ const struct TALER_AuditorPublicKeyP *apub,
+ const char *auditor_url,
+ const struct TALER_AuditorSignatureP *asigs,
+ const struct TALER_MasterPublicKeyP *mpub,
+ unsigned int dki_len,
+ const struct TALER_DenominationKeyValidityPS *dki)
+{
+ struct AuditorFileHeaderP af;
+ struct GNUNET_DISK_FileHandle *fh;
+ ssize_t wrote;
+ size_t wsize;
+ int ret;
+ int eno;
+
+ af.apub = *apub;
+ af.mpub = *mpub;
+ af.dki_len = htonl ((uint32_t) dki_len);
+ 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 AuditorFileHeaderP);
+ if (GNUNET_SYSERR == (wrote = GNUNET_DISK_file_write (fh,
+ &af,
+ wsize)))
+ goto cleanup;
+ if (wrote != wsize)
+ goto cleanup;
+ wsize = dki_len * sizeof (struct TALER_AuditorSignatureP);
+ if (wsize ==
+ GNUNET_DISK_file_write (fh,
+ asigs,
+ wsize))
+ ret = GNUNET_OK;
+ wsize = dki_len * sizeof (struct TALER_DenominationKeyValidityPS);
+ if (wsize ==
+ GNUNET_DISK_file_write (fh,
+ dki,
+ wsize))
+ ret = GNUNET_OK;
+ wsize = strlen (auditor_url) + 1;
+ if (wsize ==
+ GNUNET_DISK_file_write (fh,
+ auditor_url,
+ wsize))
+ ret = GNUNET_OK;
+ cleanup:
+ eno = errno;
+ if (NULL != fh)
+ (void) GNUNET_DISK_file_close (fh);
+ errno = eno;
+ return ret;
+}
+
+
+/* end of exchangedb_keyio.c */
diff --git a/src/exchangedb/exchangedb_plugin.c b/src/exchangedb/exchangedb_plugin.c
new file mode 100644
index 000000000..ebaef9cc0
--- /dev/null
+++ b/src/exchangedb/exchangedb_plugin.c
@@ -0,0 +1,87 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2015 GNUnet e.V.
+
+ 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 exchangedb/exchangedb_plugin.c
+ * @brief Logic to load database plugin
+ * @author Christian Grothoff
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ */
+#include "platform.h"
+#include "taler_exchangedb_plugin.h"
+#include <ltdl.h>
+
+
+/**
+ * Initialize the plugin.
+ *
+ * @param cfg configuration to use
+ * @return #GNUNET_OK on success
+ */
+struct TALER_EXCHANGEDB_Plugin *
+TALER_EXCHANGEDB_plugin_load (const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ char *plugin_name;
+ char *lib_name;
+ struct GNUNET_CONFIGURATION_Handle *cfg_dup;
+ struct TALER_EXCHANGEDB_Plugin *plugin;
+
+ if (GNUNET_SYSERR ==
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "exchange",
+ "db",
+ &plugin_name))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchange",
+ "db");
+ return NULL;
+ }
+ (void) GNUNET_asprintf (&lib_name,
+ "libtaler_plugin_exchangedb_%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_EXCHANGEDB_plugin_unload (struct TALER_EXCHANGEDB_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);
+}
+
+
+
+/* end of exchangedb_plugin.c */
diff --git a/src/exchangedb/perf_taler_exchangedb.c b/src/exchangedb/perf_taler_exchangedb.c
new file mode 100644
index 000000000..6ff7f5331
--- /dev/null
+++ b/src/exchangedb/perf_taler_exchangedb.c
@@ -0,0 +1,358 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015 GNUnet e.V.
+
+ 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 exchangedb/perf_taler_exchangedb.c
+ * @brief Exchange database performance analysis
+ * @author Nicolas Fournier
+ */
+#include "platform.h"
+#include "perf_taler_exchangedb_interpreter.h"
+
+
+#define NB_DENOMINATION_INIT 15
+#define NB_DENOMINATION_SAVE 15
+
+#define SMALL 1000
+#define BIG 10000
+#define BIGGER 100000
+
+#define NB_RESERVE_INIT BIGGER
+#define NB_RESERVE_SAVE BIG
+
+#define NB_DEPOSIT_INIT BIGGER
+#define NB_DEPOSIT_SAVE BIG
+
+#define NB_WITHDRAW_INIT BIGGER
+#define NB_WITHDRAW_SAVE BIG
+
+#define NB_REFRESH_INIT BIGGER
+#define NB_REFRESH_SAVE BIG
+
+#define NB_MELT_INIT BIG
+#define NB_MELT_SAVE SMALL
+
+/**
+ * Runs the performances tests for the exchange database
+ * and logs the results using Gauger
+ */
+int
+main (int argc, char ** argv)
+{
+ int ret;
+ struct PERF_TALER_EXCHANGEDB_Cmd benchmark[] =
+ {
+ /* Denomination used to create coins */
+ PERF_TALER_EXCHANGEDB_INIT_CMD_DEBUG ("Initializing database"),
+
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("01 - denomination loop",
+ NB_DENOMINATION_INIT),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_START_TRANSACTION (""),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_DENOMINATION ("01 - denomination"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_DENOMINATION ("01 - insert",
+ "01 - denomination"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_COMMIT_TRANSACTION (""),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_SAVE_ARRAY ("01 - save denomination",
+ "01 - denomination loop",
+ "01 - denomination",
+ NB_DENOMINATION_SAVE),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("01 - end",
+ "01 - denomination loop"),
+ /* End of initialization */
+ /* Reserve initialization */
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("02 - init reserve loop",
+ NB_RESERVE_INIT),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_RESERVE ("02 - reserve"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_RESERVE ("02 - insert",
+ "02 - reserve"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_SAVE_ARRAY ("02 - save reserve",
+ "02 - init reserve loop",
+ "02 - reserve",
+ NB_RESERVE_SAVE),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("02 - end",
+ "02 - init reserve loop"),
+ /* End reserve init */
+ /* Withdrawal initialization */
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("03 - init withdraw loop",
+ NB_WITHDRAW_INIT),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_START_TRANSACTION (""),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY ("03 - denomination load",
+ "03 - init withdraw loop",
+ "01 - save denomination"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY ("03 - reserve load",
+ "03 - init withdraw loop",
+ "02 - save reserve"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_WITHDRAW ("03 - withdraw",
+ "03 - denomination load",
+ "03 - reserve load"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_WITHDRAW ("03 - insert",
+ "03 - withdraw"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_COMMIT_TRANSACTION (""),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_SAVE_ARRAY ("03 - save coin",
+ "03 - init withdraw loop",
+ "03 - withdraw",
+ NB_WITHDRAW_SAVE),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("03 - end",
+ "03 - init withdraw loop"),
+ /*End of withdrawal initialization */
+ /*Deposit initialization */
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("04 - deposit init loop",
+ NB_DEPOSIT_INIT),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_START_TRANSACTION (""),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY ("04 - coin load",
+ "04 - deposit init loop",
+ "03 - save coin"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_DEPOSIT ("04 - deposit",
+ "04 - coin load"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_DEPOSIT ("04 - insert",
+ "04 - deposit"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_COMMIT_TRANSACTION (""),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_SAVE_ARRAY ("04 - deposit array",
+ "04 - deposit init loop",
+ "04 - deposit",
+ NB_DEPOSIT_SAVE),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("",
+ "04 - deposit init loop"),
+ /* End of deposit initialization */
+ /* Session initialization */
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("05 - refresh session init loop",
+ NB_REFRESH_INIT),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_START_TRANSACTION (""),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_REFRESH_SESSION ("05 - refresh session"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_SAVE_ARRAY ("05 - session array",
+ "05 - refresh session init loop",
+ "05 - refresh session",
+ NB_RESERVE_SAVE),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_COMMIT_TRANSACTION (""),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("05 - end",
+ "05 - refresh session init loop"),
+ /* End of refresh session initialization */
+ /* Refresh melt initialization */
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("06 - refresh melt init loop",
+ NB_MELT_INIT),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_START_TRANSACTION (""),
+ /* TODO: initialize using coins & sessions created localy
+ * in order to make sure the same coin are not melted twice*/
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY ("06 - session hash",
+ "06 - refresh melt init loop",
+ "05 - session array"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY ("06 - coin",
+ "06 - refresh melt init loop",
+ "03 - save coin"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_REFRESH_MELT ("06 - refresh melt",
+ "06 - session hash",
+ "06 - coin"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_COMMIT_TRANSACTION (""),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("06 - end",
+ "06 - refresh melt init loop"),
+ /* End of refresh melt initialization */
+ PERF_TALER_EXCHANGEDB_INIT_CMD_DEBUG ("End of initialization"),
+
+ PERF_TALER_EXCHANGEDB_INIT_CMD_DEBUG ("Start of performances measuring"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("21 - start"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("21 - reserve insert measure",
+ NB_RESERVE_SAVE),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_RESERVE ("21 - reserve"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_RESERVE ("21 - insert",
+ "21 - reserve"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("",
+ "21 - reserve insert measure"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("21 - stop"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_GAUGER ("21 - gauger",
+ "21 - start",
+ "21 - stop",
+ "POSTGRES",
+ "Number of reserve inserted per second",
+ "item/sec",
+ NB_RESERVE_SAVE),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_DEBUG ("End of reserve insertion"),
+
+ PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("22 - start"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("22 - reserve load measure",
+ NB_RESERVE_SAVE),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY ("22 - reserve",
+ "22 - reserve load measure",
+ "02 - save reserve"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_GET_RESERVE ("22 - get",
+ "22 - reserve"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("",
+ "22 - reserve load measure"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("22 - stop"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_GAUGER ("",
+ "22 - start",
+ "22 - stop",
+ "POSTGRES",
+ "Number of reserve loaded per second",
+ "item/sec",
+ NB_RESERVE_SAVE),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_DEBUG ("End of reserve retreival"),
+
+ PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("23 - start"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("23 - reserve history measure",
+ NB_RESERVE_SAVE),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY ("23 - reserve",
+ "23 - reserve history measure",
+ "02 - save reserve"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_GET_RESERVE_HISTORY ("",
+ "23 - reserve"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("",
+ "23 - reserve history measure"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("23 - stop"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_GAUGER ("",
+ "23 - start",
+ "23 - stop",
+ "POSTGRES",
+ "Number of reserve history loaded per second",
+ "item/sec",
+ NB_RESERVE_SAVE),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_DEBUG ("End of reserve history access"),
+
+
+ PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("24 - start"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("24 - withdraw insert measure",
+ NB_WITHDRAW_SAVE),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY ("24 - reserve",
+ "24 - withdraw insert measure",
+ "02 - save reserve"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY ("24 - denomination",
+ "24 - withdraw insert measure",
+ "01 - save denomination"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_WITHDRAW ("24 - withdraw",
+ "24 - denomination",
+ "24 - reserve"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_WITHDRAW ("24 - insert",
+ "24 - withdraw"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("",
+ "24 - withdraw insert measure"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("24 - stop"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_GAUGER ("",
+ "24 - start",
+ "24 - stop",
+ "POSTGRES",
+ "Number of withdraw insert per second",
+ "item/sec",
+ NB_WITHDRAW_SAVE),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_DEBUG ("End of withdraw insertion"),
+
+ PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("25 - start"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("25 - withdraw insert measure",
+ NB_RESERVE_SAVE),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY ("25 - coin",
+ "25 - withdraw insert measure",
+ "03 - save coin"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_GET_WITHDRAW ("",
+ "25 - coin"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("",
+ "25 - withdraw insert measure"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("25 - stop"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_GAUGER ("",
+ "25 - start",
+ "25 - stop",
+ "POSTGRES",
+ "Number of withdraw loaded per second",
+ "item/sec",
+ NB_RESERVE_SAVE),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_DEBUG ("End of withdraw loading"),
+
+ PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("26 - start"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("26 - get coin transaction",
+ NB_WITHDRAW_SAVE),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY ("26 - coin",
+ "26 - get coin transaction",
+ "03 - save coin"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_GET_COIN_TRANSACTION("",
+ "26 - coin"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("",
+ "26 - get coin transaction"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("26 - end"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_GAUGER ("",
+ "26 - start",
+ "26 - end",
+ "POSTGRES",
+ "Number of coin transaction history loaded per second",
+ "item/sec",
+ NB_WITHDRAW_SAVE),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_DEBUG ("End of transaction loading"),
+
+ PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("27 - start"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("27 - /reserve/withdraw",
+ NB_WITHDRAW_SAVE),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY ("27 - reserve",
+ "27 - /reserve/withdraw",
+ "02 - save reserve"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY ("27 - dki",
+ "27 - /reserve/withdraw",
+ "01 - save denomination"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_WITHDRAW_SIGN ("",
+ "27 - dki",
+ "27 - reserve"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("",
+ "27 - /reserve/withdraw"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("27 - end"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_GAUGER ("",
+ "27 - start",
+ "27 - end",
+ "POSTGRES",
+ "Number of /reserve/withdraw per second",
+ "item/sec",
+ NB_WITHDRAW_SAVE),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_DEBUG ("End of /reserve/withdraw"),
+
+ PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("28 - start"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("28 - /deposit",
+ NB_DEPOSIT_SAVE),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY ("28 - coin",
+ "28 - /deposit",
+ "03 - save coin"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_DEPOSIT ("28 - deposit",
+ "28 - coin"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("",
+ "28 - /deposit"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("28 - stop"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_GAUGER ("",
+ "28 - start",
+ "28 - stop",
+ "POSTGRES",
+ "Number of /deposit per second",
+ "item/sec",
+ NB_DEPOSIT_SAVE),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("29 - start"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("29 - insert refresh session",
+ NB_REFRESH_SAVE),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_START_TRANSACTION (""),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_REFRESH_SESSION (""),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_COMMIT_TRANSACTION (""),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("",
+ "29 - insert refresh session"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("29 - stop"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_GAUGER ("",
+ "29 - start",
+ "29 - stop",
+ "POSTGRES",
+ "Number of refresh session inserted per second",
+ "item/sec",
+ NB_REFRESH_SAVE),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_END (""),
+ };
+
+ ret = PERF_TALER_EXCHANGEDB_run_benchmark (
+ "perf-taler-exchangedb",
+ "./test-exchange-db-postgres.conf",
+ (struct PERF_TALER_EXCHANGEDB_Cmd []) {PERF_TALER_EXCHANGEDB_INIT_CMD_END("")},
+ benchmark);
+ if (GNUNET_SYSERR == ret)
+ return 1;
+ return 0;
+}
diff --git a/src/exchangedb/perf_taler_exchangedb_init.c b/src/exchangedb/perf_taler_exchangedb_init.c
new file mode 100644
index 000000000..516f3ea58
--- /dev/null
+++ b/src/exchangedb/perf_taler_exchangedb_init.c
@@ -0,0 +1,625 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015 GNUnet e.V.
+
+ 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 exchangedb/perf_taler_exchangedb_init.c
+ * @brief Interpreter library for exchange database performance analysis
+ * @author Nicolas Fournier
+ */
+#include "platform.h"
+#include "perf_taler_exchangedb_init.h"
+#include <gnunet/gnunet_signatures.h>
+#include "taler_signatures.h"
+#include "taler_amount_lib.h"
+
+
+#define CURRENCY "EUR"
+#define PERF_TALER_EXCHANGEDB_RSA_SIZE 512
+
+
+/**
+ * Generate a dummy DenominationKeyInformation for testing purposes
+ * @return a dummy denomination key
+ */
+struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *
+PERF_TALER_EXCHANGEDB_denomination_init ()
+{
+ struct GNUNET_CRYPTO_EddsaPrivateKey *master_prvt;
+ struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki;
+ struct TALER_DenominationPrivateKey denom_priv;
+ struct TALER_DenominationPublicKey denom_pub;
+ struct TALER_EXCHANGEDB_DenominationKeyInformationP issue;
+
+ master_prvt = GNUNET_CRYPTO_eddsa_key_create();
+
+ dki = GNUNET_new (struct TALER_EXCHANGEDB_DenominationKeyIssueInformation);
+ GNUNET_assert (NULL != dki);
+ denom_priv.rsa_private_key
+ = GNUNET_CRYPTO_rsa_private_key_create (PERF_TALER_EXCHANGEDB_RSA_SIZE);
+ GNUNET_assert (NULL != denom_priv.rsa_private_key);
+ denom_pub.rsa_public_key =
+ GNUNET_CRYPTO_rsa_private_key_get_public (denom_priv.rsa_private_key);
+ GNUNET_assert (NULL != denom_pub.rsa_public_key);
+ {/* issue */
+ struct TALER_MasterSignatureP signature;
+ struct TALER_DenominationKeyValidityPS properties;
+
+ {/* properties */
+ struct TALER_Amount amount;
+
+ properties.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY);
+ properties.purpose.size = htonl (sizeof (struct TALER_DenominationKeyValidityPS));
+ GNUNET_CRYPTO_eddsa_key_get_public (master_prvt,
+ &properties.master.eddsa_pub);
+ properties.start = GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_get());
+ properties.expire_withdraw = GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_get_forever_());
+ properties.expire_spend = GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_get_forever_());
+ properties.expire_legal = GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_get_forever_());
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":1.1", &amount));
+ TALER_amount_hton (&properties.value, &amount);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":0.1", &amount));
+ TALER_amount_hton (&properties.fee_withdraw, &amount);
+ TALER_amount_hton (&properties.fee_deposit, &amount);
+ TALER_amount_hton (&properties.fee_refresh, &amount);
+ GNUNET_CRYPTO_rsa_public_key_hash (denom_pub.rsa_public_key,
+ &properties.denom_hash);
+ issue.properties = properties;
+ }
+ {/* signature */
+ GNUNET_CRYPTO_eddsa_sign (master_prvt,
+ &properties.purpose,
+ &signature.eddsa_signature);
+ issue.signature = signature;
+ }
+ }
+ dki->denom_priv = denom_priv;
+ dki->denom_pub = denom_pub;
+ dki->issue = issue;
+ GNUNET_free (master_prvt);
+ return dki;
+}
+
+
+/**
+ * Copies the given denomination
+ * @param reserve the deposit copy
+ * @return a copy of @a deposit; NULL if error
+ */
+struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *
+PERF_TALER_EXCHANGEDB_denomination_copy (const struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki)
+{
+ struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *copy;
+
+ GNUNET_assert (NULL !=
+ (copy = GNUNET_new (struct TALER_EXCHANGEDB_DenominationKeyIssueInformation)));
+ {/* denom_priv */
+ copy->denom_priv.rsa_private_key =
+ GNUNET_CRYPTO_rsa_private_key_dup ( dki->denom_priv.rsa_private_key);
+ }
+ {/* denom_pub */
+ copy->denom_pub.rsa_public_key =
+ GNUNET_CRYPTO_rsa_public_key_dup (dki->denom_pub.rsa_public_key);
+ }
+ {/* issue */
+ copy->issue.properties = dki->issue.properties;
+ copy->issue.signature = dki->issue.signature;
+ }
+ return copy;
+}
+
+
+/**
+ * Free memory of a DenominationKeyIssueInformation
+ * @param dki pointer to the struct to free
+ */
+int
+PERF_TALER_EXCHANGEDB_denomination_free (struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki)
+{
+ if (NULL == dki)
+ return GNUNET_OK;
+ GNUNET_CRYPTO_rsa_private_key_free (dki->denom_priv.rsa_private_key);
+ GNUNET_CRYPTO_rsa_public_key_free (dki->denom_pub.rsa_public_key);
+
+ GNUNET_free (dki);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Generate a dummy reserve for testing
+ * @return a reserve with 1000 EUR in it
+ */
+struct PERF_TALER_EXCHANGEDB_Reserve *
+PERF_TALER_EXCHANGEDB_reserve_init ()
+{
+ struct PERF_TALER_EXCHANGEDB_Reserve *reserve;
+
+ GNUNET_assert (NULL !=
+ (reserve = GNUNET_new (struct PERF_TALER_EXCHANGEDB_Reserve)));
+ {/* private */
+ struct GNUNET_CRYPTO_EddsaPrivateKey *private;
+ private = GNUNET_CRYPTO_eddsa_key_create ();
+ GNUNET_assert (NULL != private);
+ reserve->private = *private;
+ GNUNET_free (private);
+ }
+
+ GNUNET_CRYPTO_eddsa_key_get_public (&reserve->private,
+ &reserve->reserve.pub.eddsa_pub);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":1000", &reserve->reserve.balance));
+ reserve->reserve.expiry = GNUNET_TIME_absolute_get_forever_ ();
+ return reserve;
+}
+
+
+/**
+ * Copies the given reserve
+ * @param reserve the reserve to copy
+ * @return a copy of @a reserve; NULL if error
+ */
+struct PERF_TALER_EXCHANGEDB_Reserve *
+PERF_TALER_EXCHANGEDB_reserve_copy (const struct PERF_TALER_EXCHANGEDB_Reserve *reserve)
+{
+ struct PERF_TALER_EXCHANGEDB_Reserve *copy;
+ GNUNET_assert (NULL !=
+ (copy = GNUNET_new (struct PERF_TALER_EXCHANGEDB_Reserve)));
+ *copy = *reserve;
+ return copy;
+}
+
+
+/**
+ * Free memory of a reserve
+ * @param reserve pointer to the structure to be freed
+ */
+int
+PERF_TALER_EXCHANGEDB_reserve_free (struct PERF_TALER_EXCHANGEDB_Reserve *reserve)
+{
+ if (NULL == reserve)
+ return GNUNET_OK;
+ GNUNET_free (reserve);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Generate a dummy deposit for testing purposes
+ * @param dki the denomination key used to sign the key
+ */
+struct TALER_EXCHANGEDB_Deposit *
+PERF_TALER_EXCHANGEDB_deposit_init (const struct PERF_TALER_EXCHANGEDB_Coin *coin)
+{
+ struct TALER_EXCHANGEDB_Deposit *deposit;
+ struct TALER_CoinSpendSignatureP csig;
+ struct TALER_MerchantPublicKeyP merchant_pub;
+ struct GNUNET_HashCode h_contract;
+ struct GNUNET_HashCode h_wire;
+ const char wire[] = "{"
+ "\"type\":\"SEPA\","
+ "\"IBAN\":\"DE67830654080004822650\","
+ "\"NAME\":\"GNUNET E.\","
+ "\"BIC\":\"GENODEF1SRL\""
+ "}";
+ static uint64_t transaction_id = 0;
+ struct GNUNET_TIME_Absolute timestamp;
+ struct GNUNET_TIME_Absolute refund_deadline;
+ struct TALER_Amount amount_with_fee;
+ struct TALER_Amount deposit_fee;
+
+ GNUNET_assert (NULL !=
+ (deposit = GNUNET_malloc (sizeof (struct TALER_EXCHANGEDB_Deposit) + sizeof (wire))));
+ GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_WEAK,
+ &h_contract);
+ GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_WEAK,
+ &h_wire);
+ { //csig
+ struct u32_presign
+ {
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+ struct GNUNET_HashCode h_wire;
+ struct GNUNET_HashCode h_contract;
+ } unsigned_data;
+
+ unsigned_data.h_contract = h_contract;
+ unsigned_data.h_wire = h_wire;
+ unsigned_data.purpose.size = htonl (sizeof (struct u32_presign));
+ unsigned_data.purpose.purpose = htonl (GNUNET_SIGNATURE_PURPOSE_TEST);
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CRYPTO_eddsa_sign (&coin->priv,
+ &unsigned_data.purpose,
+ &csig.eddsa_signature));
+ }
+ { //merchant_pub
+ struct GNUNET_CRYPTO_EddsaPrivateKey *eddsa_prv;
+
+ eddsa_prv = GNUNET_CRYPTO_eddsa_key_create ();
+ GNUNET_assert(NULL != eddsa_prv);
+ GNUNET_CRYPTO_eddsa_key_get_public (
+ eddsa_prv,
+ &merchant_pub.eddsa_pub);
+ GNUNET_free (eddsa_prv);
+ }
+ timestamp = GNUNET_TIME_absolute_get ();
+ refund_deadline = GNUNET_TIME_absolute_get ();
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":1.1",
+ &amount_with_fee));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":0.1",
+ &deposit_fee));
+ {
+ deposit->coin.coin_pub = coin->public_info.coin_pub;
+ deposit->coin.denom_pub.rsa_public_key = GNUNET_CRYPTO_rsa_public_key_dup (
+ coin->public_info.denom_pub.rsa_public_key);
+ GNUNET_assert (NULL != coin->public_info.denom_pub.rsa_public_key);
+ deposit->coin.denom_sig.rsa_signature = GNUNET_CRYPTO_rsa_signature_dup (
+ coin->public_info.denom_sig.rsa_signature);
+ GNUNET_assert (NULL != coin->public_info.denom_sig.rsa_signature);
+ }
+ deposit->csig = csig;
+ deposit->h_contract = h_contract;
+ deposit->h_wire = h_wire;
+ deposit->wire = json_loads (wire, 0, NULL);
+ deposit->transaction_id = transaction_id++;
+ deposit->timestamp = timestamp;
+ deposit->refund_deadline = refund_deadline;
+ deposit->amount_with_fee = amount_with_fee;
+ deposit->deposit_fee = deposit_fee;
+ return deposit;
+}
+
+
+/**
+ * Copies the given deposit
+ * @param reserve the deposit copy
+ * @return a copy of @a deposit; NULL if error
+ */
+struct TALER_EXCHANGEDB_Deposit *
+PERF_TALER_EXCHANGEDB_deposit_copy (const struct TALER_EXCHANGEDB_Deposit *deposit)
+{
+ struct TALER_EXCHANGEDB_Deposit *copy;
+
+ copy = GNUNET_new (struct TALER_EXCHANGEDB_Deposit);
+ *copy = *deposit;
+ json_incref (copy->wire);
+ copy->coin.denom_pub.rsa_public_key =
+ GNUNET_CRYPTO_rsa_public_key_dup (deposit->coin.denom_pub.rsa_public_key);
+ copy->coin.denom_sig.rsa_signature =
+ GNUNET_CRYPTO_rsa_signature_dup (deposit->coin.denom_sig.rsa_signature);
+ return copy;
+}
+
+
+/**
+ * Free memory of a deposit
+ * @param deposit pointer to the structure to free
+ */
+int
+PERF_TALER_EXCHANGEDB_deposit_free (struct TALER_EXCHANGEDB_Deposit *deposit)
+{
+ if (NULL == deposit)
+ return GNUNET_OK;
+ GNUNET_CRYPTO_rsa_public_key_free (deposit->coin.denom_pub.rsa_public_key);
+ GNUNET_CRYPTO_rsa_signature_free (deposit->coin.denom_sig.rsa_signature);
+ json_decref (deposit->wire);
+ GNUNET_free (deposit);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Generate a CollectableBlindcoin for testing purpuses
+ * @param dki denomination key used to sign the coin
+ * @param reserve reserve providing the money for the coin
+ * @return a randomly generated CollectableBlindcoin
+ */
+struct PERF_TALER_EXCHANGEDB_Coin *
+PERF_TALER_EXCHANGEDB_coin_init (
+ const struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki,
+ const struct PERF_TALER_EXCHANGEDB_Reserve *reserve)
+{
+ struct PERF_TALER_EXCHANGEDB_Coin *coin;
+ struct GNUNET_CRYPTO_EddsaPrivateKey *priv;
+ struct GNUNET_HashCode hc;
+
+ coin = GNUNET_new (struct PERF_TALER_EXCHANGEDB_Coin);
+ GNUNET_assert (NULL != coin);
+ /* priv */
+
+ priv = GNUNET_CRYPTO_eddsa_key_create();
+ GNUNET_assert (NULL != priv);
+ coin->priv = *priv;
+ GNUNET_free (priv);
+
+ /* public_info */
+ GNUNET_CRYPTO_eddsa_key_get_public (&coin->priv,
+ &coin->public_info.coin_pub.eddsa_pub);
+ coin->public_info.denom_pub.rsa_public_key =
+ GNUNET_CRYPTO_rsa_public_key_dup (dki->denom_pub.rsa_public_key);
+ GNUNET_CRYPTO_hash (&coin->public_info.coin_pub,
+ sizeof (struct TALER_CoinSpendPublicKeyP),
+ &hc);
+ coin->public_info.denom_sig.rsa_signature =
+ GNUNET_CRYPTO_rsa_sign_fdh (dki->denom_priv.rsa_private_key,
+ &hc);
+ GNUNET_assert (NULL != coin->public_info.denom_pub.rsa_public_key);
+ GNUNET_assert (NULL != coin->public_info.denom_sig.rsa_signature);
+
+ /* blind */
+ coin->blind.sig.rsa_signature =
+ GNUNET_CRYPTO_rsa_signature_dup (coin->public_info.denom_sig.rsa_signature);
+ coin->blind.denom_pub.rsa_public_key =
+ GNUNET_CRYPTO_rsa_public_key_dup (dki->denom_pub.rsa_public_key);
+ GNUNET_assert (NULL != coin->blind.sig.rsa_signature);
+ GNUNET_assert (NULL != coin->blind.denom_pub.rsa_public_key);
+ TALER_amount_ntoh (&coin->blind.amount_with_fee,
+ &dki->issue.properties.value);
+ TALER_amount_ntoh (&coin->blind.withdraw_fee,
+ &dki->issue.properties.fee_withdraw);
+ coin->blind.reserve_pub = reserve->reserve.pub;
+ GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_WEAK,
+ &coin->blind.h_coin_envelope);
+
+ return coin;
+}
+
+
+/**
+ * Copies the given coin
+ *
+ * @param coin the coin to copy
+ * @return a copy of coin; NULL if error
+ */
+struct PERF_TALER_EXCHANGEDB_Coin *
+PERF_TALER_EXCHANGEDB_coin_copy (const struct PERF_TALER_EXCHANGEDB_Coin *coin)
+{
+ struct PERF_TALER_EXCHANGEDB_Coin *copy;
+
+ copy = GNUNET_new (struct PERF_TALER_EXCHANGEDB_Coin);
+ /* priv */
+ copy->priv = coin->priv;
+ /* public_info */
+ copy->public_info.coin_pub = coin->public_info.coin_pub;
+ copy->public_info.denom_pub.rsa_public_key =
+ GNUNET_CRYPTO_rsa_public_key_dup (coin->public_info.denom_pub.rsa_public_key);
+ copy->public_info.denom_sig.rsa_signature =
+ GNUNET_CRYPTO_rsa_signature_dup (coin->public_info.denom_sig.rsa_signature);
+
+ /* blind */
+ copy->blind.sig.rsa_signature =
+ GNUNET_CRYPTO_rsa_signature_dup (coin->blind.sig.rsa_signature);
+ copy->blind.denom_pub.rsa_public_key =
+ GNUNET_CRYPTO_rsa_public_key_dup (coin->blind.denom_pub.rsa_public_key);
+ copy->blind.amount_with_fee = coin->blind.amount_with_fee;
+ copy->blind.withdraw_fee = coin->blind.withdraw_fee;
+ copy->blind.reserve_pub = coin->blind.reserve_pub;
+ copy->blind.h_coin_envelope = coin->blind.h_coin_envelope;
+ copy->blind.reserve_sig = coin->blind.reserve_sig;
+
+ return copy;
+}
+
+
+/**
+ * Free memory of @a coin
+ *
+ * @param coin pointer to the structure to free
+ */
+int
+PERF_TALER_EXCHANGEDB_coin_free (struct PERF_TALER_EXCHANGEDB_Coin *coin)
+{
+ if (NULL == coin)
+ return GNUNET_OK;
+ GNUNET_CRYPTO_rsa_public_key_free (coin->public_info.denom_pub.rsa_public_key);
+ GNUNET_CRYPTO_rsa_signature_free (coin->public_info.denom_sig.rsa_signature);
+ GNUNET_CRYPTO_rsa_signature_free (coin->blind.sig.rsa_signature);
+ GNUNET_CRYPTO_rsa_public_key_free (coin->blind.denom_pub.rsa_public_key);
+ GNUNET_free (coin);
+ return GNUNET_OK;
+}
+
+
+/**
+ * @return a randomly generated refresh session
+ */
+struct TALER_EXCHANGEDB_RefreshSession *
+PERF_TALER_EXCHANGEDB_refresh_session_init ()
+{
+ struct TALER_EXCHANGEDB_RefreshSession *refresh_session;
+
+ GNUNET_assert (NULL !=
+ (refresh_session = GNUNET_new (struct TALER_EXCHANGEDB_RefreshSession)));
+ refresh_session->noreveal_index = 1;
+ refresh_session->num_oldcoins = 1;
+ refresh_session->num_newcoins = 1;
+
+ return refresh_session;
+}
+
+
+/**
+ * @return #GNUNET_OK if the copy was successful, #GNUNET_SYSERR if it wasn't
+ */
+int
+PERF_TALER_EXCHANGEDB_refresh_session_copy (struct TALER_EXCHANGEDB_RefreshSession *session,
+ struct TALER_EXCHANGEDB_RefreshSession *copy)
+{
+ *copy = *session;
+ return GNUNET_OK;
+}
+
+
+/**
+ * Free a refresh session
+ */
+int
+PERF_TALER_EXCHANGEDB_refresh_session_free (struct TALER_EXCHANGEDB_RefreshSession *refresh_session)
+{
+ if (NULL == refresh_session)
+ return GNUNET_OK;
+ GNUNET_free (refresh_session);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Create a melt operation
+ *
+ * @param session the refresh session
+ * @param dki the denomination the melted coin uses
+ * @return a pointer to a #TALER_EXCHANGEDB_RefreshMelt
+ */
+struct TALER_EXCHANGEDB_RefreshMelt *
+PERF_TALER_EXCHANGEDB_refresh_melt_init (struct GNUNET_HashCode *session,
+ struct PERF_TALER_EXCHANGEDB_Coin *coin)
+{
+ struct TALER_EXCHANGEDB_RefreshMelt *melt;
+ struct TALER_CoinSpendSignatureP coin_sig;
+ struct TALER_Amount amount;
+ struct TALER_Amount amount_with_fee;
+
+ {
+ struct
+ {
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+ struct GNUNET_HashCode session;
+ } to_sign;
+
+ to_sign.purpose.purpose = GNUNET_SIGNATURE_PURPOSE_TEST;
+ to_sign.purpose.size = htonl (sizeof (to_sign));
+ to_sign.session = *session;
+ GNUNET_CRYPTO_eddsa_sign (&coin->priv,
+ &to_sign.purpose,
+ &coin_sig.eddsa_signature);
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":1.1",
+ &amount));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":0.1",
+ &amount_with_fee));
+ melt = GNUNET_new (struct TALER_EXCHANGEDB_RefreshMelt);
+ melt->coin.coin_pub = coin->public_info.coin_pub;
+ melt->coin.denom_sig.rsa_signature =
+ GNUNET_CRYPTO_rsa_signature_dup (coin->public_info.denom_sig.rsa_signature);
+ melt->coin.denom_pub.rsa_public_key =
+ GNUNET_CRYPTO_rsa_public_key_dup (coin->public_info.denom_pub.rsa_public_key);
+ GNUNET_assert (NULL != melt->coin.denom_pub.rsa_public_key);
+ GNUNET_assert (NULL != melt->coin.denom_sig.rsa_signature);
+ melt->coin_sig = coin_sig;
+ melt->session_hash = *session;
+ melt->amount_with_fee = amount;
+ melt->melt_fee = amount_with_fee;
+ return melt;
+}
+
+
+/**
+ * Copies the internals of a #TALER_EXCHANGEDB_RefreshMelt
+ *
+ * @param melt the refresh melt to copy
+ * @return an copy of @ melt
+ */
+struct TALER_EXCHANGEDB_RefreshMelt *
+PERF_TALER_EXCHANGEDB_refresh_melt_copy (const struct TALER_EXCHANGEDB_RefreshMelt *melt)
+{
+ struct TALER_EXCHANGEDB_RefreshMelt *copy;
+
+ copy = GNUNET_new (struct TALER_EXCHANGEDB_RefreshMelt);
+ *copy = *melt;
+ copy->coin.denom_sig.rsa_signature =
+ GNUNET_CRYPTO_rsa_signature_dup (melt->coin.denom_sig.rsa_signature);
+ GNUNET_assert (NULL != copy->coin.denom_sig.rsa_signature);
+
+ return copy;
+}
+
+
+/**
+ * Free the internal memory of a #TALER_EXCHANGEDB_RefreshMelt
+ *
+ * @param melt the #TALER_EXCHANGEDB_RefreshMelt to free
+ * @return #GNUNET_OK if the operation was successful, #GNUNET_SYSERROR
+ */
+int
+PERF_TALER_EXCHANGEDB_refresh_melt_free (struct TALER_EXCHANGEDB_RefreshMelt *melt)
+{
+ GNUNET_CRYPTO_rsa_signature_free (melt->coin.denom_sig.rsa_signature);
+ GNUNET_free (melt);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Create a #TALER_EXCHANGEDB_RefreshCommitCoin
+ */
+struct TALER_EXCHANGEDB_RefreshCommitCoin *
+PERF_TALER_EXCHANGEDB_refresh_commit_coin_init ()
+{
+ struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coin;
+ struct TALER_RefreshLinkEncrypted refresh_link;
+
+ commit_coin = GNUNET_new (struct TALER_EXCHANGEDB_RefreshCommitCoin);
+ GNUNET_assert (NULL != commit_coin);
+ {/* refresh_link */
+ refresh_link = (struct TALER_RefreshLinkEncrypted)
+ {
+ .blinding_key_enc = "blinding_key",
+ .blinding_key_enc_size = 13
+ };
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &refresh_link.coin_priv_enc,
+ sizeof(struct TALER_CoinSpendPrivateKeyP));
+ }
+ commit_coin->coin_ev = "coin_ev";
+ commit_coin->coin_ev_size = 8;
+ commit_coin->refresh_link = GNUNET_new (struct TALER_RefreshLinkEncrypted);
+ *commit_coin->refresh_link = refresh_link;
+ return commit_coin;
+}
+
+
+/**
+ * Copies a #TALER_EXCHANGEDB_RefreshCommitCoin
+ *
+ * @param commit_coin the commit to copy
+ * @return a copy of @a commit_coin
+ */
+struct TALER_EXCHANGEDB_RefreshCommitCoin *
+PERF_TALER_EXCHANGEDB_refresh_commit_coin_copy (struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coin)
+{
+ struct TALER_EXCHANGEDB_RefreshCommitCoin *copy;
+
+ copy = GNUNET_new (struct TALER_EXCHANGEDB_RefreshCommitCoin);
+ copy->refresh_link = GNUNET_new (struct TALER_RefreshLinkEncrypted);
+ *copy->refresh_link = *commit_coin->refresh_link;
+ return copy;
+}
+
+
+/**
+ * Free a #TALER_EXCHANGEDB_RefreshCommitCoin
+ *
+ * @param commit_coin the coin to free
+ */
+void
+PERF_TALER_EXCHANGEDB_refresh_commit_coin_free (struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coin)
+{
+ GNUNET_free (commit_coin->refresh_link);
+ GNUNET_free (commit_coin);
+}
diff --git a/src/exchangedb/perf_taler_exchangedb_init.h b/src/exchangedb/perf_taler_exchangedb_init.h
new file mode 100644
index 000000000..0ff074108
--- /dev/null
+++ b/src/exchangedb/perf_taler_exchangedb_init.h
@@ -0,0 +1,257 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015 GNUnet e.V.
+
+ 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 exchangedb/perf_taler_exchangedb_init.h
+ * @brief Heler function for creating dummy inputs for the exchange database
+ * @author Nicolas Fournier
+ */
+#ifndef __PERF_TALER_EXCHANGEDB_INIT_H___
+#define __PERF_TALER_EXCHANGEDB_INIT_H___
+
+#include "taler_exchangedb_plugin.h"
+
+
+#define CURRENCY "EUR"
+
+/**
+ * All information about a reserve
+ */
+struct PERF_TALER_EXCHANGEDB_Reserve
+{
+ /**
+ * Information about a rserve available to the Exchange
+ */
+ struct TALER_EXCHANGEDB_Reserve reserve;
+
+ /**
+ * Private key of a reserve
+ */
+ struct GNUNET_CRYPTO_EddsaPrivateKey private;
+};
+
+
+/**
+ * All informations about a coin
+ */
+struct PERF_TALER_EXCHANGEDB_Coin
+{
+ /**
+ * Blinded coin, known by the exchange
+ */
+ struct TALER_EXCHANGEDB_CollectableBlindcoin blind;
+
+ /**
+ * Public key of the coin and othes informations
+ */
+ struct TALER_CoinPublicInfo public_info;
+
+ /**
+ * Private key of the coin
+ */
+ struct GNUNET_CRYPTO_EddsaPrivateKey priv;
+};
+
+
+/**
+ * Generate a dummy DenominationKeyInformation for testing purposes
+ * @return a dummy denomination key
+ */
+struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *
+PERF_TALER_EXCHANGEDB_denomination_init (void);
+
+
+/**
+ * Copies the given denomination
+ * @param reserve the deposit copy
+ * @return a copy of @a deposit; NULL if error
+ */
+struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *
+PERF_TALER_EXCHANGEDB_denomination_copy (
+ const struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki);
+
+
+/**
+ * Free memory of a DenominationKeyIssueInformation
+ * @param dki pointer to the struct to free
+ */
+int
+PERF_TALER_EXCHANGEDB_denomination_free (
+ struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki);
+
+
+/**
+ * Generate a dummy reserve for testing
+ * @return a reserve with 1000 EUR in it
+ */
+struct PERF_TALER_EXCHANGEDB_Reserve *
+PERF_TALER_EXCHANGEDB_reserve_init (void);
+
+
+/**
+ * Copies the given reserve
+ * @param reserve the reserve to copy
+ * @return a copy of @a reserve; NULL if error
+ */
+struct PERF_TALER_EXCHANGEDB_Reserve *
+PERF_TALER_EXCHANGEDB_reserve_copy (const struct PERF_TALER_EXCHANGEDB_Reserve *reserve);
+
+
+/**
+ * Free memory of a reserve
+ * @param reserve pointer to the structure to be freed
+ */
+int
+PERF_TALER_EXCHANGEDB_reserve_free (struct PERF_TALER_EXCHANGEDB_Reserve *reserve);
+
+
+/**
+ * Generate a dummy deposit for testing purposes
+ * @param dki the denomination key used to sign the key
+ */
+struct TALER_EXCHANGEDB_Deposit *
+PERF_TALER_EXCHANGEDB_deposit_init (
+ const struct PERF_TALER_EXCHANGEDB_Coin *coin);
+
+
+/**
+ * Copies the given deposit
+ * @param reserve the deposit copy
+ * @return a copy of @a deposit; NULL if error
+ */
+struct TALER_EXCHANGEDB_Deposit *
+PERF_TALER_EXCHANGEDB_deposit_copy (const struct TALER_EXCHANGEDB_Deposit *deposit);
+
+
+/**
+ * Free memory of a deposit
+ * @param deposit pointer to the structure to free
+ */
+int
+PERF_TALER_EXCHANGEDB_deposit_free (struct TALER_EXCHANGEDB_Deposit *deposit);
+
+
+/**
+ * Generate a coin for testing purpuses
+ * @param dki denomination key used to sign the coin
+ * @param reserve reserve providing the money for the coin
+ * @return a randomly generated CollectableBlindcoin
+ */
+struct PERF_TALER_EXCHANGEDB_Coin *
+PERF_TALER_EXCHANGEDB_coin_init (
+ const struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki,
+ const struct PERF_TALER_EXCHANGEDB_Reserve *reserve);
+
+
+/**
+ * Copies the given coin
+ * @param coin the coin to copy
+ * @return a copy of coin; NULL if error
+ */
+struct PERF_TALER_EXCHANGEDB_Coin *
+PERF_TALER_EXCHANGEDB_coin_copy (
+ const struct PERF_TALER_EXCHANGEDB_Coin *coin);
+
+
+/**
+ * Liberate memory of @a coin
+ * @param coin pointer to the structure to free
+ */
+int
+PERF_TALER_EXCHANGEDB_coin_free (
+ struct PERF_TALER_EXCHANGEDB_Coin *coin);
+
+
+/**
+ * @return a randomly generated refresh session
+ */
+struct TALER_EXCHANGEDB_RefreshSession *
+PERF_TALER_EXCHANGEDB_refresh_session_init (void);
+
+
+/**
+ * @return #GNUNET_OK if the copy was successful, #GNUNET_SYSERR if it wasn't
+ */
+int
+PERF_TALER_EXCHANGEDB_refresh_session_copy (struct TALER_EXCHANGEDB_RefreshSession *session,
+ struct TALER_EXCHANGEDB_RefreshSession *copy);
+
+
+/**
+ * Frees memory of a refresh_session
+ */
+int
+PERF_TALER_EXCHANGEDB_refresh_session_free (
+ struct TALER_EXCHANGEDB_RefreshSession *refresh_session);
+
+
+/**
+ * Create a melt operation
+ *
+ * @param session the refresh session
+ * @param dki the denomination the melted coin uses
+ * @return a pointer to a #TALER_EXCHANGEDB_RefreshMelt
+ */
+struct TALER_EXCHANGEDB_RefreshMelt *
+PERF_TALER_EXCHANGEDB_refresh_melt_init (struct GNUNET_HashCode *session,
+ struct PERF_TALER_EXCHANGEDB_Coin *coin);
+
+
+/**
+ * Copies the internals of a #TALER_EXCHANGEDB_RefreshMelt
+ *
+ * @param melt the refresh melt to copy
+ * @return an copy of @ melt
+ */
+struct TALER_EXCHANGEDB_RefreshMelt *
+PERF_TALER_EXCHANGEDB_refresh_melt_copy (const struct TALER_EXCHANGEDB_RefreshMelt *melt);
+
+
+/**
+ * Free the internal memory of a #TALER_EXCHANGEDB_RefreshMelt
+ *
+ * @param melt the #TALER_EXCHANGEDB_RefreshMelt to free
+ * @return #GNUNET_OK if the operation was successful, #GNUNET_SYSERROR
+ */
+int
+PERF_TALER_EXCHANGEDB_refresh_melt_free (struct TALER_EXCHANGEDB_RefreshMelt *melt);
+
+
+/**
+ * Create a #TALER_EXCHANGEDB_RefreshCommitCoin
+ */
+struct TALER_EXCHANGEDB_RefreshCommitCoin *
+PERF_TALER_EXCHANGEDB_refresh_commit_coin_init (void);
+
+
+/**
+ * Copies a #TALER_EXCHANGEDB_RefreshCommitCoin
+ *
+ * @param commit_coin the commit to copy
+ * @return a copy of @a commit_coin
+ */
+struct TALER_EXCHANGEDB_RefreshCommitCoin *
+PERF_TALER_EXCHANGEDB_refresh_commit_coin_copy (struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coin);
+
+
+/**
+ * Free a #TALER_EXCHANGEDB_RefreshCommitCoin
+ *
+ * @param commit_coin the coin to free
+ */
+void
+PERF_TALER_EXCHANGEDB_refresh_commit_coin_free (struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coin);
+
+#endif
diff --git a/src/exchangedb/perf_taler_exchangedb_interpreter.c b/src/exchangedb/perf_taler_exchangedb_interpreter.c
new file mode 100644
index 000000000..78b58a5ae
--- /dev/null
+++ b/src/exchangedb/perf_taler_exchangedb_interpreter.c
@@ -0,0 +1,1997 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015 GNUnet e.V.
+
+ 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 exchangedb/perf_taler_exchangedb_interpreter.c
+ * @brief Interpreter library for exchange database performance analysis
+ * @author Nicolas Fournier
+ */
+#include "platform.h"
+#include "perf_taler_exchangedb_interpreter.h"
+#include "perf_taler_exchangedb_init.h"
+#include "gauger.h"
+
+
+/**
+ * Represents the state of the interpreter
+ */
+struct PERF_TALER_EXCHANGEDB_interpreter_state
+{
+ /**
+ * State of the commands
+ */
+ struct PERF_TALER_EXCHANGEDB_Cmd *cmd;
+
+ /**
+ * Database plugin
+ */
+ struct TALER_EXCHANGEDB_Plugin *plugin;
+
+ /**
+ * Current database session
+ */
+ struct TALER_EXCHANGEDB_Session *session;
+
+ /**
+ * The current index of the interpreter
+ */
+ unsigned int i;
+};
+
+
+/**
+ * Free the memory of @a data
+ */
+static void
+data_free (struct PERF_TALER_EXCHANGEDB_Data *data)
+{
+ switch (data->type)
+ {
+ case PERF_TALER_EXCHANGEDB_TIME:
+ if (NULL == data->data.time)
+ break;
+ GNUNET_free (data->data.time);
+ data->data.time = NULL;
+ break;
+
+ case PERF_TALER_EXCHANGEDB_DEPOSIT:
+ if (NULL == data->data.deposit)
+ break;
+ PERF_TALER_EXCHANGEDB_deposit_free (data->data.deposit);
+ data->data.deposit = NULL;
+ break;
+
+ case PERF_TALER_EXCHANGEDB_COIN:
+ if (NULL == data->data.coin)
+ break;
+ PERF_TALER_EXCHANGEDB_coin_free (data->data.coin);
+ data->data.coin = NULL;
+ break;
+
+ case PERF_TALER_EXCHANGEDB_RESERVE:
+ if (NULL == data->data.reserve)
+ break;
+ PERF_TALER_EXCHANGEDB_reserve_free (data->data.reserve);
+ data->data.reserve = NULL;
+ break;
+
+ case PERF_TALER_EXCHANGEDB_DENOMINATION_INFO:
+ if (NULL == data->data.dki)
+ break;
+ PERF_TALER_EXCHANGEDB_denomination_free (data->data.dki);
+ data->data.dki = NULL;
+ break;
+
+ case PERF_TALER_EXCHANGEDB_REFRESH_HASH:
+ if (NULL == data->data.session_hash)
+ break;
+ GNUNET_free (data->data.session_hash);
+ data->data.session_hash = NULL;
+ break;
+
+ case PERF_TALER_EXCHANGEDB_REFRESH_MELT:
+ if (NULL == data->data.refresh_melt)
+ break;
+ PERF_TALER_EXCHANGEDB_refresh_melt_free (data->data.refresh_melt);
+ data->data.refresh_melt = NULL;
+ break;
+
+ case PERF_TALER_EXCHANGEDB_NONE:
+ break;
+ }
+}
+
+
+/**
+ * Copies @a data into @a copy
+ *
+ * @param data the data to be copied
+ * @param[out] copy the copy made
+ */
+static void
+data_copy (const struct PERF_TALER_EXCHANGEDB_Data *data,
+ struct PERF_TALER_EXCHANGEDB_Data *copy)
+{
+ copy->type = data->type;
+ switch (data->type)
+ {
+ case PERF_TALER_EXCHANGEDB_TIME:
+ copy->data.time = GNUNET_new (struct GNUNET_TIME_Absolute);
+ *copy->data.time = *data->data.time;
+ return;
+
+ case PERF_TALER_EXCHANGEDB_DEPOSIT:
+ copy->data.deposit
+ = PERF_TALER_EXCHANGEDB_deposit_copy (data->data.deposit);
+ return;
+
+ case PERF_TALER_EXCHANGEDB_COIN:
+ copy->data.coin
+ = PERF_TALER_EXCHANGEDB_coin_copy (data->data.coin);
+ return;
+
+ case PERF_TALER_EXCHANGEDB_RESERVE:
+ copy->data.reserve
+ = PERF_TALER_EXCHANGEDB_reserve_copy (data->data.reserve);
+ return;
+
+ case PERF_TALER_EXCHANGEDB_DENOMINATION_INFO:
+ copy->data.dki
+ = PERF_TALER_EXCHANGEDB_denomination_copy (data->data.dki);
+ return;
+
+ case PERF_TALER_EXCHANGEDB_REFRESH_HASH:
+ copy-> data.session_hash = GNUNET_new (struct GNUNET_HashCode);
+ *copy->data.session_hash
+ = *data->data.session_hash;
+ break;
+
+ case PERF_TALER_EXCHANGEDB_REFRESH_MELT:
+ copy->data.refresh_melt
+ = PERF_TALER_EXCHANGEDB_refresh_melt_copy (data->data.refresh_melt);
+ break;
+
+ case PERF_TALER_EXCHANGEDB_NONE:
+ break;
+ }
+}
+
+
+/**
+ * Finds the first command in cmd with the name search
+ *
+ * @return the index of the first command with name search
+ * #GNUNET_SYSERR if none found
+ */
+static int
+cmd_find (const struct PERF_TALER_EXCHANGEDB_Cmd *cmd,
+ const char *search)
+{
+ unsigned int i;
+
+ for (i=0; PERF_TALER_EXCHANGEDB_CMD_END != cmd[i].command; i++)
+ if (0 == strcmp (cmd[i].label, search))
+ return i;
+ return GNUNET_SYSERR;
+}
+
+
+/**
+ * Initialization of a command array
+ * and check for the type of the label
+ *
+ * @param cmd the comand array initialized
+ * @return #GNUNET_OK if the initialization was sucessful
+ * #GNUNET_SYSERR if there was a probleb. See the log for details
+ */
+static int
+cmd_init (struct PERF_TALER_EXCHANGEDB_Cmd cmd[])
+{
+ unsigned int i;
+
+ for (i=0; PERF_TALER_EXCHANGEDB_CMD_END != cmd[i].command; i++)
+ {
+ switch (cmd[i].command)
+ {
+ case PERF_TALER_EXCHANGEDB_CMD_END_LOOP:
+ {
+ int ret;
+
+ ret = cmd_find (cmd,
+ cmd[i].details.end_loop.label_loop);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Undefined reference to %s\n",
+ i,
+ cmd[i].details.end_loop.label_loop);
+ return GNUNET_SYSERR;
+ }
+ if (PERF_TALER_EXCHANGEDB_CMD_LOOP != cmd[ret].command)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Wrong type reference to %s\n",
+ i,
+ cmd[i].details.end_loop.label_loop);
+ return GNUNET_SYSERR;
+ }
+ cmd[i].details.end_loop.index_loop = ret;
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_SAVE_ARRAY:
+ {
+ int ret;
+
+ ret = cmd_find (cmd,
+ cmd[i].details.save_array.label_save);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Undefined reference to %s\n",
+ i,
+ cmd[i].details.save_array.label_save);
+ return GNUNET_SYSERR;
+ }
+ if (PERF_TALER_EXCHANGEDB_NONE == cmd[ret].exposed.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Wrong type reference to %s\n",
+ i,
+ cmd[i].details.save_array.label_save);
+ return GNUNET_SYSERR;
+ }
+ cmd[i].details.save_array.index_save = ret;
+
+ ret = cmd_find (cmd,
+ cmd[i].details.save_array.label_loop);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Undefined reference to %s\n",
+ i,
+ cmd[i].details.save_array.label_loop);
+ return GNUNET_SYSERR;
+ }
+ if (PERF_TALER_EXCHANGEDB_CMD_LOOP != cmd[ret].command)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Wrong type reference to %s\n",
+ i,
+ cmd[i].details.save_array.label_loop);
+ return GNUNET_SYSERR;
+ }
+ cmd[i].details.save_array.index_loop = ret;
+
+ GNUNET_assert (NULL == cmd[i].details.save_array.data_saved);
+ cmd[i].details.save_array.data_saved =
+ GNUNET_new_array (cmd[i].details.save_array.nb_saved,
+ struct PERF_TALER_EXCHANGEDB_Data);
+ cmd[i].details.save_array.type_saved =
+ cmd[cmd[i].details.save_array.index_save].exposed.type;
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_LOAD_ARRAY:
+ {
+ int ret;
+
+ ret = cmd_find (cmd,
+ cmd[i].details.load_array.label_save);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Undefined reference to %s\n",
+ i,
+ cmd[i].details.load_array.label_save);
+ return GNUNET_SYSERR;
+ }
+ if (PERF_TALER_EXCHANGEDB_CMD_SAVE_ARRAY != cmd[ret].command)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Wrong type reference to %s\n",
+ i,
+ cmd[i].details.load_array.label_save);
+ return GNUNET_SYSERR;
+ }
+ cmd[i].details.load_array.index_save = ret;
+
+ ret = cmd_find (cmd,
+ cmd[i].details.load_array.label_loop);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Undefined reference to %s\n",
+ i,
+ cmd[i].details.load_array.label_loop);
+ return GNUNET_SYSERR;
+ }
+ if (PERF_TALER_EXCHANGEDB_CMD_LOOP != cmd[ret].command)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Wrong type reference to %s\n",
+ i,
+ cmd[i].details.load_array.label_loop);
+ return GNUNET_SYSERR;
+ }
+ cmd[i].details.load_array.index_loop = ret;
+
+ cmd[i].details.load_array.permutation =
+ GNUNET_CRYPTO_random_permute (
+ GNUNET_CRYPTO_QUALITY_WEAK,
+ cmd[cmd[i].details.load_array.index_save].details.save_array.nb_saved);
+ GNUNET_assert (NULL != cmd[i].details.load_array.permutation);
+
+ cmd[i].exposed.type = cmd[cmd[i].details.load_array.index_save].details.save_array.type_saved;
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_LOAD_RANDOM:
+ {
+ int ret;
+
+ ret = cmd_find (cmd,
+ cmd[i].details.load_random.label_save);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Undefined reference to %s\n",
+ i,
+ cmd[i].details.load_random.label_save);
+ return GNUNET_SYSERR;
+ }
+ if (PERF_TALER_EXCHANGEDB_CMD_SAVE_ARRAY != cmd[ret].command)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Wrong type reference to %s\n",
+ i,
+ cmd[i].details.load_random.label_save);
+ return GNUNET_SYSERR;
+ }
+ cmd[i].details.load_random.index_save = ret;
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_GAUGER:
+ {
+ int ret;
+
+ ret = cmd_find (cmd,
+ cmd[i].details.gauger.label_start);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Undefined reference to %s\n",
+ i,
+ cmd[i].details.gauger.label_start);
+ return GNUNET_SYSERR;
+ }
+ if (PERF_TALER_EXCHANGEDB_TIME != cmd[ret].exposed.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Wrong type reference to %s\n",
+ i,
+ cmd[i].details.gauger.label_start);
+ return GNUNET_SYSERR;
+ }
+ cmd[i].details.gauger.index_start = ret;
+
+ ret = cmd_find (cmd,
+ cmd[i].details.gauger.label_stop);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Undefined reference to %s\n",
+ i,
+ cmd[i].details.gauger.label_stop);
+ return GNUNET_SYSERR;
+ }
+ if (PERF_TALER_EXCHANGEDB_TIME != cmd[ret].exposed.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Wrong type reference to %s\n",
+ i,
+ cmd[i].details.gauger.label_stop);
+ return GNUNET_SYSERR;
+ }
+ cmd[i].details.gauger.index_stop = ret;
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_INSERT_DENOMINATION:
+ {
+ int ret;
+
+ ret = cmd_find (cmd,
+ cmd[i].details.insert_denomination.label_denom);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Undefined reference to %s\n",
+ i,
+ cmd[i].details.insert_denomination.label_denom);
+ return GNUNET_SYSERR;
+ }
+ if (PERF_TALER_EXCHANGEDB_DENOMINATION_INFO != cmd[ret].exposed.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Wrong type reference to %s\n",
+ i,
+ cmd[i].details.insert_denomination.label_denom);
+ return GNUNET_SYSERR;
+ }
+ cmd[i].details.insert_denomination.index_denom = ret;
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_GET_DENOMINATION:
+ {
+ int ret;
+
+ ret = cmd_find (cmd,
+ cmd[i].details.get_denomination.label_denom);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Undefined reference to %s\n",
+ i,
+ cmd[i].details.get_denomination.label_denom);
+ return GNUNET_SYSERR;
+ }
+ if (PERF_TALER_EXCHANGEDB_DENOMINATION_INFO != cmd[ret].exposed.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Wrong type reference to %s\n",
+ i,
+ cmd[i].details.get_denomination.label_denom);
+ return GNUNET_SYSERR;
+ }
+ cmd[i].details.get_denomination.index_denom = ret;
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_INSERT_RESERVE:
+ {
+ int ret;
+
+ ret = cmd_find (cmd,
+ cmd[i].details.insert_reserve.label_reserve);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Undefined reference to %s\n",
+ i,
+ cmd[i].details.insert_reserve.label_reserve);
+ return GNUNET_SYSERR;
+ }
+ if (PERF_TALER_EXCHANGEDB_RESERVE != cmd[ret].exposed.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Wrong type reference to %s\n",
+ i,
+ cmd[i].details.insert_reserve.label_reserve);
+ return GNUNET_SYSERR;
+ }
+ cmd[i].details.insert_reserve.index_reserve = ret;
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_GET_RESERVE:
+ {
+ int ret;
+
+ ret = cmd_find (cmd,
+ cmd[i].details.get_reserve.label_reserve);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Undefined reference to %s\n",
+ i,
+ cmd[i].details.get_reserve.label_reserve);
+ return GNUNET_SYSERR;
+ }
+ if (PERF_TALER_EXCHANGEDB_RESERVE != cmd[ret].exposed.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Wrong type reference to %s\n",
+ i,
+ cmd[i].details.get_reserve.label_reserve);
+ return GNUNET_SYSERR;
+ }
+ cmd[i].details.get_reserve.index_reserve = ret;
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_GET_RESERVE_HISTORY:
+ {
+ int ret;
+
+ ret = cmd_find (cmd,
+ cmd[i].details.get_reserve_history.label_reserve);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Undefined reference to %s\n",
+ i,
+ cmd[i].details.get_reserve_history.label_reserve);
+ return GNUNET_SYSERR;
+ }
+ if (PERF_TALER_EXCHANGEDB_RESERVE != cmd[ret].exposed.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Wrong type reference to %s\n",
+ i,
+ cmd[i].details.get_reserve_history.label_reserve);
+ return GNUNET_SYSERR;
+ }
+ cmd[i].details.get_reserve_history.index_reserve = ret;
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_CREATE_WITHDRAW:
+ {
+ int ret;
+
+ ret = cmd_find (cmd,
+ cmd[i].details.create_withdraw.label_dki);
+ {
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Undefined reference to %s\n",
+ i,
+ cmd[i].details.create_withdraw.label_dki);
+ return GNUNET_SYSERR;
+ }
+ if (PERF_TALER_EXCHANGEDB_DENOMINATION_INFO != cmd[ret].exposed.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Wrong type reference to %s\n",
+ i,
+ cmd[i].details.create_withdraw.label_dki);
+ return GNUNET_SYSERR;
+ }
+ }
+ cmd[i].details.create_withdraw.index_dki = ret;
+ ret = cmd_find (cmd,
+ cmd[i].details.create_withdraw.label_reserve);
+ {
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Undefined reference to %s\n",
+ i,
+ cmd[i].details.create_withdraw.label_reserve);
+ return GNUNET_SYSERR;
+ }
+ if (PERF_TALER_EXCHANGEDB_RESERVE != cmd[ret].exposed.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Wrong type reference to %s\n",
+ i,
+ cmd[i].details.create_withdraw.label_reserve);
+ return GNUNET_SYSERR;
+ }
+ }
+ cmd[i].details.create_withdraw.index_reserve = ret;
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_INSERT_WITHDRAW:
+ {
+ int ret;
+
+ ret = cmd_find (cmd,
+ cmd[i].details.insert_withdraw.label_coin);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Undefined reference to %s\n",
+ i,
+ cmd[i].details.insert_withdraw.label_coin);
+ return GNUNET_SYSERR;
+ }
+ if (PERF_TALER_EXCHANGEDB_COIN != cmd[ret].exposed.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Wrong type reference to %s\n",
+ i,
+ cmd[i].details.insert_withdraw.label_coin);
+ return GNUNET_SYSERR;
+ }
+ cmd[i].details.insert_withdraw.index_coin = ret;
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_GET_WITHDRAW:
+ {
+ int ret;
+
+ ret = cmd_find (cmd,
+ cmd[i].details.get_withdraw.label_coin);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Undefined reference to %s\n",
+ i,
+ cmd[i].details.get_withdraw.label_coin);
+ return GNUNET_SYSERR;
+ }
+ if (PERF_TALER_EXCHANGEDB_COIN != cmd[ret].exposed.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Wrong type reference to %s\n",
+ i,
+ cmd[i].details.get_withdraw.label_coin);
+ return GNUNET_SYSERR;
+ }
+ cmd[i].details.get_withdraw.index_coin = ret;
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_GET_COIN_TRANSACTION:
+ {
+ int ret;
+ ret = cmd_find (cmd,
+ cmd[i].details.get_coin_transaction.label_coin);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Undefined reference to %s\n",
+ i,
+ cmd[i].details.get_coin_transaction.label_coin);
+ return GNUNET_SYSERR;
+ }
+ if (PERF_TALER_EXCHANGEDB_COIN != cmd[ret].exposed.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Wrong type reference to %s\n",
+ i,
+ cmd[i].details.get_coin_transaction.label_coin);
+ return GNUNET_SYSERR;
+ }
+ cmd[i].details.get_coin_transaction.index_coin = ret;
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_CREATE_DEPOSIT:
+ {
+ int ret;
+
+ ret = cmd_find (cmd,
+ cmd[i].details.create_deposit.label_coin);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Undefined reference to %s\n",
+ i,
+ cmd[i].details.create_deposit.label_coin);
+ return GNUNET_SYSERR;
+ }
+ if (PERF_TALER_EXCHANGEDB_COIN != cmd[ret].exposed.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Wrong type reference to %s\n",
+ i,
+ cmd[i].details.create_deposit.label_coin);
+ return GNUNET_SYSERR;
+ }
+ cmd[i].details.create_deposit.index_coin = ret;
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_INSERT_DEPOSIT:
+ {
+ int ret;
+
+ ret = cmd_find( cmd,
+ cmd[i].details.insert_deposit.label_deposit);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Undefined reference to %s\n",
+ i,
+ cmd[i].details.insert_deposit.label_deposit);
+ return GNUNET_SYSERR;
+ }
+ if (PERF_TALER_EXCHANGEDB_DEPOSIT != cmd[ret].exposed.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Wrong type reference to %s\n",
+ i,
+ cmd[i].details.insert_deposit.label_deposit);
+ return GNUNET_SYSERR;
+ }
+ cmd[i].details.insert_deposit.index_deposit = ret;
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_GET_DEPOSIT:
+ {
+ int ret;
+
+ ret = cmd_find (cmd,
+ cmd[i].details.get_deposit.label_deposit);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Undefined reference to %s\n",
+ i,
+ cmd[i].details.get_deposit.label_deposit);
+ return GNUNET_SYSERR;
+ }
+ if (PERF_TALER_EXCHANGEDB_DEPOSIT != cmd[ret].exposed.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Wrong type reference to %s\n",
+ i,
+ cmd[i].details.get_deposit.label_deposit);
+ return GNUNET_SYSERR;
+ }
+ cmd[i].details.get_deposit.index_deposit = ret;
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_GET_REFRESH_SESSION:
+ {
+ int ret;
+
+ ret = cmd_find (cmd,
+ cmd[i].details.get_refresh_session.label_hash);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Undefined reference to %s\n",
+ i,
+ cmd[i].details.get_refresh_session.label_hash);
+ return GNUNET_SYSERR;
+ }
+ if (PERF_TALER_EXCHANGEDB_REFRESH_HASH != cmd[ret].exposed.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Wrong type reference to %s\n",
+ i,
+ cmd[i].details.get_refresh_session.label_hash);
+ return GNUNET_SYSERR;
+ }
+ cmd[i].details.get_refresh_session.index_hash = ret;
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_INSERT_REFRESH_MELT:
+ {
+ int ret;
+
+ ret = cmd_find (cmd,
+ cmd[i].details.insert_refresh_melt.label_hash);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Undefined reference to %s\n",
+ i,
+ cmd[i].details.insert_refresh_melt.label_hash);
+ return GNUNET_SYSERR;
+ }
+ if (PERF_TALER_EXCHANGEDB_REFRESH_HASH != cmd[ret].exposed.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Wrong type reference to %s\n",
+ i,
+ cmd[i].details.insert_refresh_melt.label_hash);
+ return GNUNET_SYSERR;
+ }
+ cmd[i].details.insert_refresh_melt.index_hash = ret;
+ ret = cmd_find (cmd,
+ cmd[i].details.insert_refresh_melt.label_coin);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Undefined reference to %s\n",
+ i,
+ cmd[i].details.insert_refresh_melt.label_coin);
+ return GNUNET_SYSERR;
+ }
+ if (PERF_TALER_EXCHANGEDB_COIN != cmd[ret].exposed.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Wrong type reference to %s\n",
+ i,
+ cmd[i].details.insert_refresh_melt.label_coin);
+ return GNUNET_SYSERR;
+ }
+ cmd[i].details.insert_refresh_melt.index_coin = ret; }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_GET_REFRESH_MELT:
+ {
+ int ret;
+ ret = cmd_find (cmd,
+ cmd[i].details.get_refresh_melt.label_hash);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Undefined reference to %s\n",
+ i,
+ cmd[i].details.get_refresh_melt.label_hash);
+ return GNUNET_SYSERR;
+ }
+ if (PERF_TALER_EXCHANGEDB_REFRESH_HASH != cmd[ret].exposed.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Wrong type reference to %s\n",
+ i,
+ cmd[i].details.get_refresh_melt.label_hash);
+ return GNUNET_SYSERR;
+ }
+ cmd[i].details.get_refresh_melt.index_hash = ret;
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_INSERT_REFRESH_ORDER:
+ {
+ int ret;
+ ret = cmd_find (cmd,
+ cmd[i].details.insert_refresh_order.label_hash);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Undefined reference to %s\n",
+ i,
+ cmd[i].details.insert_refresh_order.label_hash);
+ return GNUNET_SYSERR;
+ }
+ if (PERF_TALER_EXCHANGEDB_REFRESH_HASH != cmd[ret].exposed.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Wrong type reference to %s\n",
+ i,
+ cmd[i].details.insert_refresh_order.label_hash);
+ return GNUNET_SYSERR;
+ }
+ cmd[i].details.insert_refresh_order.index_hash = ret;
+
+ ret = cmd_find (cmd,
+ cmd[i].details.insert_refresh_order.label_denom);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Undefined reference to %s\n",
+ i,
+ cmd[i].details.insert_refresh_order.label_denom);
+ return GNUNET_SYSERR;
+ }
+ if (PERF_TALER_EXCHANGEDB_DENOMINATION_INFO != cmd[ret].exposed.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Wrong type reference to %s\n",
+ i,
+ cmd[i].details.insert_refresh_order.label_denom);
+ return GNUNET_SYSERR;
+ }
+ cmd[i].details.insert_refresh_order.index_denom = ret;
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_GET_REFRESH_ORDER:
+ {
+ int ret;
+ ret = cmd_find (cmd,
+ cmd[i].details.get_refresh_order.label_hash);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Undefined reference to %s\n",
+ i,
+ cmd[i].details.get_refresh_order.label_hash);
+ return GNUNET_SYSERR;
+ }
+ if (PERF_TALER_EXCHANGEDB_REFRESH_HASH != cmd[ret].exposed.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Wrong type reference to %s\n",
+ i,
+ cmd[i].details.get_refresh_order.label_hash);
+ return GNUNET_SYSERR;
+ }
+ cmd[i].details.get_refresh_order.index_hash = ret;
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_INSERT_REFRESH_COMMIT_COIN:
+ {
+ int ret;
+ ret = cmd_find (cmd,
+ cmd[i].details.insert_refresh_commit_coin.label_hash);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Undefined reference to %s\n",
+ i,
+ cmd[i].details.insert_refresh_commit_coin.label_hash);
+ return GNUNET_SYSERR;
+ }
+ if (PERF_TALER_EXCHANGEDB_REFRESH_HASH != cmd[ret].exposed.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Wrong type reference to %s\n",
+ i,
+ cmd[i].details.insert_refresh_commit_coin.label_hash);
+ return GNUNET_SYSERR;
+ }
+ cmd[i].details.insert_refresh_commit_coin.index_hash = ret;
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_GET_REFRESH_COMMIT_COIN:
+ {
+ int ret;
+ ret = cmd_find (cmd,
+ cmd[i].details.get_refresh_commit_coin.label_hash);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Undefined reference to %s\n",
+ i,
+ cmd[i].details.get_refresh_commit_coin.label_hash);
+ return GNUNET_SYSERR;
+ }
+ if (PERF_TALER_EXCHANGEDB_REFRESH_HASH != cmd[ret].exposed.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Wrong type reference to %s\n",
+ i,
+ cmd[i].details.get_refresh_commit_coin.label_hash);
+ return GNUNET_SYSERR;
+ }
+ cmd[i].details.get_refresh_commit_coin.index_hash = ret;
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_INSERT_REFRESH_COMMIT_LINK:
+ {
+ int ret;
+ ret = cmd_find (cmd,
+ cmd[i].details.insert_refresh_commit_link.label_hash);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Undefined reference to %s\n",
+ i,
+ cmd[i].details.insert_refresh_commit_link.label_hash);
+ return GNUNET_SYSERR;
+ }
+ if (PERF_TALER_EXCHANGEDB_REFRESH_HASH != cmd[ret].exposed.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Wrong type reference to %s\n",
+ i,
+ cmd[i].details.insert_refresh_commit_link.label_hash);
+ return GNUNET_SYSERR;
+ }
+ cmd[i].details.insert_refresh_commit_link.index_hash = ret;
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_GET_REFRESH_COMMIT_LINK:
+ {
+ int ret;
+ ret = cmd_find (cmd,
+ cmd[i].details.get_refresh_commit_link.label_hash);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Undefined reference to %s\n",
+ i,
+ cmd[i].details.get_refresh_commit_link.label_hash);
+ return GNUNET_SYSERR;
+ }
+ if (PERF_TALER_EXCHANGEDB_REFRESH_HASH != cmd[ret].exposed.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Wrong type reference to %s\n",
+ i,
+ cmd[i].details.get_refresh_commit_link.label_hash);
+ return GNUNET_SYSERR;
+ }
+ cmd[i].details.get_refresh_commit_link.index_hash = ret;
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_GET_MELT_COMMITMENT:
+ {
+ int ret;
+ ret = cmd_find (cmd,
+ cmd[i].details.get_melt_commitment.label_hash);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Undefined reference to %s\n",
+ i,
+ cmd[i].details.get_melt_commitment.label_hash);
+ return GNUNET_SYSERR;
+ }
+ if (PERF_TALER_EXCHANGEDB_REFRESH_HASH != cmd[ret].exposed.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Wrong type reference to %s\n",
+ i,
+ cmd[i].details.get_melt_commitment.label_hash);
+ return GNUNET_SYSERR;
+ }
+ cmd[i].details.get_melt_commitment.index_hash = ret;
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_INSERT_REFRESH_OUT:
+ {
+ int ret;
+ ret = cmd_find (cmd,
+ cmd[i].details.insert_refresh_out.label_hash);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Undefined reference to %s\n",
+ i,
+ cmd[i].details.insert_refresh_out.label_hash);
+ return GNUNET_SYSERR;
+ }
+ if (PERF_TALER_EXCHANGEDB_REFRESH_HASH != cmd[ret].exposed.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Wrong type reference to %s\n",
+ i,
+ cmd[i].details.insert_refresh_out.label_hash);
+ return GNUNET_SYSERR;
+ }
+ cmd[i].details.insert_refresh_out.index_hash = ret;
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_GET_LINK_DATA_LIST:
+ {
+ int ret;
+ ret = cmd_find (cmd,
+ cmd[i].details.get_link_data_list.label_hash);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Undefined reference to %s\n",
+ i,
+ cmd[i].details.get_link_data_list.label_hash);
+ return GNUNET_SYSERR;
+ }
+ if (PERF_TALER_EXCHANGEDB_REFRESH_HASH != cmd[ret].exposed.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Wrong type reference to %s\n",
+ i,
+ cmd[i].details.get_link_data_list.label_hash);
+ return GNUNET_SYSERR;
+ }
+ cmd[i].details.get_link_data_list.index_hash = ret;
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_GET_TRANSFER:
+ {
+ int ret;
+ ret = cmd_find (cmd,
+ cmd[i].details.get_transfer.label_hash);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Undefined reference to %s\n",
+ i,
+ cmd[i].details.get_transfer.label_hash);
+ return GNUNET_SYSERR;
+ }
+ if (PERF_TALER_EXCHANGEDB_REFRESH_HASH != cmd[ret].exposed.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "%d:Wrong type reference to %s\n",
+ i,
+ cmd[i].details.get_transfer.label_hash);
+ return GNUNET_SYSERR;
+ }
+ cmd[i].details.get_transfer.index_hash = ret;
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_END:
+ case PERF_TALER_EXCHANGEDB_CMD_DEBUG:
+ case PERF_TALER_EXCHANGEDB_CMD_LOOP:
+ case PERF_TALER_EXCHANGEDB_CMD_NEW_SESSION:
+ case PERF_TALER_EXCHANGEDB_CMD_START_TRANSACTION:
+ case PERF_TALER_EXCHANGEDB_CMD_COMMIT_TRANSACTION:
+ case PERF_TALER_EXCHANGEDB_CMD_ABORT_TRANSACTION:
+ case PERF_TALER_EXCHANGEDB_CMD_GET_TIME:
+ case PERF_TALER_EXCHANGEDB_CMD_CREATE_DENOMINATION:
+ case PERF_TALER_EXCHANGEDB_CMD_CREATE_RESERVE:
+ case PERF_TALER_EXCHANGEDB_CMD_CREATE_REFRESH_SESSION:
+ break;
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Free the memory of the command chain
+ */
+static int
+cmd_clean (struct PERF_TALER_EXCHANGEDB_Cmd cmd[])
+{
+ unsigned int i;
+
+ for (i=0; PERF_TALER_EXCHANGEDB_CMD_END != cmd[i].command; i++)
+ {
+ switch (cmd[i].command)
+ {
+ case PERF_TALER_EXCHANGEDB_CMD_SAVE_ARRAY:
+ {
+ unsigned int j;
+
+ for (j = 0; j < cmd[i].details.save_array.nb_saved; j++)
+ {
+ data_free (&cmd[i].details.save_array.data_saved[j]);
+ }
+ GNUNET_free (cmd[i].details.save_array.data_saved);
+ cmd[i].details.save_array.data_saved = NULL;
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_LOAD_ARRAY:
+ GNUNET_free (cmd[i].details.load_array.permutation);
+ cmd[i].details.load_array.permutation = NULL;
+ break;
+
+ default:
+ break;
+ }
+ data_free (&cmd[i].exposed);
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Handles the command #PERF_TALER_EXCHANGEDB_CMD_END_LOOP for the interpreter
+ * Cleans the memory at the end of the loop
+ */
+static void
+interpret_end_loop (struct PERF_TALER_EXCHANGEDB_interpreter_state *state)
+{
+ unsigned int i;
+ int jump;
+
+ jump = state->cmd[state->i].details.end_loop.index_loop;
+ // Cleaning up the memory in the loop
+ for (i = jump; i < state->i; i++)
+ data_free (&state->cmd[i].exposed);
+
+ state->cmd[jump].details.loop.curr_iteration++;
+ /* If the loop is not finished */
+ if (state->cmd[jump].details.loop.max_iterations >
+ state->cmd[jump].details.loop.curr_iteration)
+ {
+ /* jump back to the start */
+ state->i = jump;
+ }
+ else
+ {
+ /* Reset the loop counter and continue running */
+ state->cmd[jump].details.loop.curr_iteration = 0;
+ }
+}
+
+
+/**
+ * Part of the interpreter specific to
+ * #PERF_TALER_EXCHANGEDB_CMD_SAVE_ARRAY
+ * Saves the data exposed by another command into
+ * an array in the command specific struct.
+ */
+static void
+interpret_save_array (struct PERF_TALER_EXCHANGEDB_interpreter_state *state)
+{
+ struct PERF_TALER_EXCHANGEDB_Cmd *cmd = &state->cmd[state->i];
+ struct PERF_TALER_EXCHANGEDB_Cmd *save_ref;
+ struct PERF_TALER_EXCHANGEDB_Cmd *loop_ref;
+ int loop_index;
+ int save_index;
+ unsigned int selection_chance;
+
+ loop_index = cmd->details.save_array.index_loop;
+ save_index = cmd->details.save_array.index_save;
+ loop_ref = &state->cmd[loop_index];
+ save_ref = &state->cmd[save_index];
+ /* Array initialization on first loop iteration
+ Alows for nested loops */
+ if (0 == cmd->details.loop.curr_iteration)
+ {
+ cmd->details.save_array.index = 0;
+ }
+ /* The probability distribution of the saved items will be a little biased
+ against the few last items but it should not be a big problem. */
+ selection_chance = loop_ref->details.loop.max_iterations /
+ cmd->details.save_array.nb_saved;
+ /*
+ * If the remaining space is equal to the remaining number of
+ * iterations, the item is automaticly saved.
+ *
+ * Else it is saved only if the random numbre generated is 0
+ */
+ if ( (0 < (cmd->details.save_array.nb_saved -
+ cmd->details.save_array.index) ) &&
+ ( ((loop_ref->details.loop.max_iterations -
+ loop_ref->details.loop.curr_iteration) ==
+ (cmd->details.save_array.nb_saved -
+ cmd->details.save_array.index)) ||
+ (0 == GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK,
+ selection_chance)) ) )
+ {
+ struct PERF_TALER_EXCHANGEDB_Data *save_location;
+ struct PERF_TALER_EXCHANGEDB_Data *item_saved;
+
+ save_location = &cmd->details.save_array.data_saved[cmd->details.save_array.index];
+ item_saved = &save_ref->exposed;
+ data_copy (item_saved, save_location);
+ cmd->details.save_array.index++;
+ }
+}
+
+
+/**
+ * Part of the interpreter specific to
+ * #PERF_TALER_EXCHANGEDB_CMD_LOAD_ARRAY
+ * Gets data from a #PERF_TALER_EXCHANGEDB_CMD_SAVE_ARRAY and exposes a copy
+ */
+static void
+interpret_load_array (struct PERF_TALER_EXCHANGEDB_interpreter_state *state)
+{
+ struct PERF_TALER_EXCHANGEDB_Cmd *cmd = &state->cmd[state->i];
+ unsigned int loop_iter;
+ int loop_index;
+ int save_index;
+ struct PERF_TALER_EXCHANGEDB_Data *loaded_data;
+
+ loop_index = cmd->details.load_array.index_loop;
+ save_index = cmd->details.load_array.index_save;
+ loop_iter = state->cmd[loop_index].details.loop.curr_iteration;
+ {
+ unsigned int i;
+ unsigned int quotient;
+
+ /* In case the iteration number is higher than the amount saved,
+ * the number is run several times in the permutation array */
+ quotient = loop_iter / state->cmd[save_index].details.save_array.nb_saved;
+ loop_iter = loop_iter % state->cmd[save_index].details.save_array.nb_saved;
+ for (i=0; i<=quotient; i++)
+ loop_iter = cmd->details.load_array.permutation[loop_iter];
+ }
+ /* Extracting the data from the loop_indexth indice in save_index
+ * array.
+ */
+ loaded_data = &state->cmd[save_index].details.save_array.data_saved[loop_iter];
+ data_copy (loaded_data,
+ &cmd->exposed);
+}
+
+
+/**
+ * Part of the interpreter specific to
+ * #PERF_TALER_EXCHANGEDB_CMD_LOAD_RANDOM
+ * Get a random element from a #PERF_TALER_EXCHANGEDB_CMD_SAVE_ARRAY and exposes it
+ */
+static void
+interprete_load_random (struct PERF_TALER_EXCHANGEDB_interpreter_state *state)
+{
+ struct PERF_TALER_EXCHANGEDB_Cmd *cmd = &state->cmd[state->i];
+ unsigned int index;
+ int save_index;
+
+ save_index = cmd->details.load_random.index_save;
+ index = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK,
+ state->cmd[save_index].details.save_array.nb_saved);
+ data_copy (&state->cmd[save_index].details.save_array.data_saved[index],
+ &cmd->exposed);
+}
+
+
+/**
+ * Iterate over the commands, acting accordingly at each step
+ *
+ * @param state the current state of the interpreter
+ */
+static int
+interpret (struct PERF_TALER_EXCHANGEDB_interpreter_state *state)
+{
+ for (state->i=0; PERF_TALER_EXCHANGEDB_CMD_END != state->cmd[state->i].command; state->i++)
+ {
+ switch (state->cmd[state->i].command)
+ {
+ case PERF_TALER_EXCHANGEDB_CMD_END:
+ return GNUNET_YES;
+
+ case PERF_TALER_EXCHANGEDB_CMD_DEBUG:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "%s\n",
+ state->cmd[state->i].label);
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_LOOP:
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_END_LOOP:
+ interpret_end_loop (state);
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_GET_TIME:
+ state->cmd[state->i].exposed.data.time =
+ GNUNET_new (struct GNUNET_TIME_Absolute);
+ *state->cmd[state->i].exposed.data.time =
+ GNUNET_TIME_absolute_get ();
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_GAUGER:
+ {
+ unsigned int start_index;
+ unsigned int stop_index;
+ float ips;
+ struct GNUNET_TIME_Absolute start;
+ struct GNUNET_TIME_Absolute stop;
+ struct GNUNET_TIME_Relative elapsed;
+
+ start_index = state->cmd[state->i].details.gauger.index_start;
+ stop_index = state->cmd[state->i].details.gauger.index_stop;
+ start = *state->cmd[start_index].exposed.data.time;
+ stop = *state->cmd[stop_index].exposed.data.time;
+ elapsed = GNUNET_TIME_absolute_get_difference (start,
+ stop);
+ ips = (1.0 * state->cmd[state->i].details.gauger.divide) / (elapsed.rel_value_us/1000000.0);
+ GAUGER (state->cmd[state->i].details.gauger.category,
+ state->cmd[state->i].details.gauger.description,
+ ips,
+ state->cmd[state->i].details.gauger.unit);
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_NEW_SESSION:
+ state->session = state->plugin->get_session (state->plugin->cls, GNUNET_YES);
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_START_TRANSACTION:
+ state->plugin->start (state->plugin->cls, state->session);
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_COMMIT_TRANSACTION:
+ state->plugin->commit (state->plugin->cls, state->session);
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_ABORT_TRANSACTION:
+ state->plugin->rollback (state->plugin->cls,
+ state->session);
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_SAVE_ARRAY:
+ interpret_save_array (state);
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_LOAD_ARRAY:
+ interpret_load_array (state);
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_LOAD_RANDOM:
+ interprete_load_random (state);
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_CREATE_DEPOSIT:
+ {
+ int coin_index;
+ struct TALER_EXCHANGEDB_Deposit *deposit;
+
+ coin_index = state->cmd[state->i].details.create_deposit.index_coin;
+ deposit = PERF_TALER_EXCHANGEDB_deposit_init (state->cmd[coin_index].exposed.data.coin);
+ GNUNET_assert (NULL != deposit);
+ state->cmd[state->i].exposed.data.deposit = deposit;
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_INSERT_DEPOSIT:
+ {
+ int deposit_index;
+ int ret;
+ struct TALER_EXCHANGEDB_Deposit *deposit;
+
+ deposit_index = state->cmd[state->i].details.insert_deposit.index_deposit;
+ deposit = state->cmd[deposit_index].exposed.data.deposit;
+ ret = state->plugin->insert_deposit (state->plugin->cls,
+ state->session,
+ deposit);
+ GNUNET_assert (GNUNET_SYSERR != ret);
+ state->cmd[state->i].exposed.data.deposit = deposit;
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_GET_DEPOSIT:
+ {
+ unsigned int source_index;
+ int ret;
+ struct PERF_TALER_EXCHANGEDB_Data *data;
+
+ source_index = state->cmd[state->i].details.get_deposit.index_deposit;
+ data = &state->cmd[source_index].exposed;
+ ret = state->plugin->have_deposit (state->plugin->cls,
+ state->session,
+ data->data.deposit);
+ GNUNET_assert (GNUNET_SYSERR != ret);
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_CREATE_RESERVE:
+ {
+ struct PERF_TALER_EXCHANGEDB_Reserve *reserve;
+
+ reserve = PERF_TALER_EXCHANGEDB_reserve_init ();
+ state->cmd[state->i].exposed.data.reserve = reserve;
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_INSERT_RESERVE:
+ {
+ unsigned int reserve_index;
+ int ret;
+ struct PERF_TALER_EXCHANGEDB_Reserve *reserve;
+ json_t *details = NULL;
+
+ reserve_index = state->cmd[state->i].details.insert_reserve.index_reserve;
+ reserve = state->cmd[reserve_index].exposed.data.reserve;
+ details = json_pack ("{s:i}","justification",
+ GNUNET_CRYPTO_random_u32 (
+ GNUNET_CRYPTO_QUALITY_WEAK,
+ UINT32_MAX));
+ GNUNET_assert (NULL != details);
+ ret = state->plugin->reserves_in_insert (state->plugin->cls,
+ state->session,
+ &reserve->reserve.pub,
+ &reserve->reserve.balance,
+ GNUNET_TIME_absolute_get (),
+ details);
+ GNUNET_assert (GNUNET_SYSERR != ret);
+ json_decref (details);
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_GET_RESERVE:
+ {
+ unsigned int reserve_index;
+ int ret;
+ struct PERF_TALER_EXCHANGEDB_Data *data;
+
+
+ reserve_index = state->cmd[state->i].details.get_reserve.index_reserve;
+ data = &state->cmd[reserve_index].exposed;
+ ret = state->plugin->reserve_get (state->plugin->cls,
+ state->session,
+ &data->data.reserve->reserve);
+ GNUNET_assert (GNUNET_OK == ret);
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_GET_RESERVE_HISTORY:
+ {
+ unsigned int reserve_index;
+ struct TALER_EXCHANGEDB_ReserveHistory *history;
+ struct PERF_TALER_EXCHANGEDB_Data *data;
+
+ reserve_index = state->cmd[state->i].details.get_reserve_history.index_reserve;
+ data = &state->cmd[reserve_index].exposed;
+ history = state->plugin->get_reserve_history (state->plugin->cls,
+ state->session,
+ &data->data.reserve->reserve.pub);
+ GNUNET_assert (NULL != history);
+ state->plugin->free_reserve_history (state->plugin->cls,
+ history);
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_CREATE_DENOMINATION:
+ {
+ struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki =
+ PERF_TALER_EXCHANGEDB_denomination_init ();
+ GNUNET_assert (NULL != dki);
+ state->cmd[state->i].exposed.data.dki = dki;
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_INSERT_DENOMINATION:
+ {
+ unsigned int denom_index;
+ int ret;
+ struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki ;
+
+ denom_index = state->cmd[state->i].details.insert_denomination.index_denom;
+ dki = state->cmd[denom_index].exposed.data.dki;
+ ret = state->plugin->insert_denomination_info (state->plugin->cls,
+ state->session,
+ &dki->denom_pub,
+ &dki->issue);
+ GNUNET_assert (GNUNET_SYSERR != ret);
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_GET_DENOMINATION:
+ {
+ unsigned int denom_index;
+ int ret;
+ struct PERF_TALER_EXCHANGEDB_Data *data;
+
+ denom_index = state->cmd[state->i].details.get_denomination.index_denom;
+ data = &state->cmd[denom_index].exposed;
+ ret = state->plugin->get_denomination_info (state->plugin->cls,
+ state->session,
+ &data->data.dki->denom_pub,
+ &data->data.dki->issue);
+ GNUNET_assert (GNUNET_SYSERR != ret);
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_CREATE_WITHDRAW:
+ {
+ unsigned int dki_index;
+ unsigned int reserve_index;
+ struct PERF_TALER_EXCHANGEDB_Coin *coin ;
+
+ dki_index = state->cmd[state->i].details.create_withdraw.index_dki;
+ reserve_index = state->cmd[state->i].details.create_withdraw.index_reserve;
+ coin = PERF_TALER_EXCHANGEDB_coin_init (state->cmd[dki_index].exposed.data.dki,
+ state->cmd[reserve_index].exposed.data.reserve);
+ GNUNET_assert (NULL != coin);
+ state->cmd[state->i].exposed.data.coin = coin;
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_INSERT_WITHDRAW:
+ {
+ unsigned int coin_index;
+ int ret;
+ struct PERF_TALER_EXCHANGEDB_Coin *coin ;
+
+ coin_index = state->cmd[state->i].details.insert_withdraw.index_coin;
+ coin = state->cmd[coin_index].exposed.data.coin;
+ ret = state->plugin->insert_withdraw_info (state->plugin->cls,
+ state->session,
+ &coin->blind);
+ GNUNET_assert (GNUNET_SYSERR != ret);
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_GET_WITHDRAW:
+ {
+ unsigned int source_index;
+ int ret;
+ struct PERF_TALER_EXCHANGEDB_Data *data;
+
+ source_index = state->cmd[state->i].details.get_denomination.index_denom;
+ data = &state->cmd[source_index].exposed;
+ ret = state->plugin->get_withdraw_info (state->plugin->cls,
+ state->session,
+ &data->data.coin->blind.h_coin_envelope,
+ &data->data.coin->blind);
+ GNUNET_assert (GNUNET_SYSERR != ret);
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_GET_COIN_TRANSACTION:
+ {
+ unsigned int coin_index;
+ struct PERF_TALER_EXCHANGEDB_Coin *coin;
+ struct TALER_EXCHANGEDB_TransactionList *transactions;
+
+ coin_index = state->cmd[state->i].details.get_coin_transaction.index_coin;
+ coin = state->cmd[coin_index].exposed.data.coin;
+ transactions = state->plugin->get_coin_transactions (state->plugin->cls,
+ state->session,
+ &coin->public_info.coin_pub);
+ GNUNET_assert (transactions != NULL);
+ state->plugin->free_coin_transaction_list (state->plugin->cls,
+ transactions);
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_CREATE_REFRESH_SESSION:
+ {
+ struct GNUNET_HashCode *hash;
+ struct TALER_EXCHANGEDB_RefreshSession *refresh_session;
+
+ hash = GNUNET_new (struct GNUNET_HashCode);
+ refresh_session = PERF_TALER_EXCHANGEDB_refresh_session_init ();
+ GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_WEAK,
+ hash);
+ state->plugin->create_refresh_session (state->session,
+ state->session,
+ hash,
+ refresh_session);
+ state->cmd[state->i].exposed.data.session_hash = hash;
+ PERF_TALER_EXCHANGEDB_refresh_session_free (refresh_session);
+ GNUNET_free (refresh_session);
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_GET_REFRESH_SESSION:
+ {
+ unsigned int hash_index;
+ struct GNUNET_HashCode *hash;
+ struct TALER_EXCHANGEDB_RefreshSession refresh;
+
+ hash_index = state->cmd[state->i].details.get_refresh_session.index_hash;
+ hash = state->cmd[hash_index].exposed.data.session_hash;
+ state->plugin->get_refresh_session (state->session,
+ state->session,
+ hash,
+ &refresh);
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_INSERT_REFRESH_MELT:
+ {
+ unsigned int hash_index;
+ unsigned int coin_index;
+ struct GNUNET_HashCode *hash;
+ struct TALER_EXCHANGEDB_RefreshMelt *melt;
+ struct PERF_TALER_EXCHANGEDB_Coin *coin;
+
+ hash_index = state->cmd[state->i].details.insert_refresh_melt.index_hash;
+ coin_index = state->cmd[state->i].details.insert_refresh_melt.index_coin;
+ hash = state->cmd[hash_index].exposed.data.session_hash;
+ coin = state->cmd[coin_index].exposed.data.coin;
+ melt = PERF_TALER_EXCHANGEDB_refresh_melt_init (hash,
+ coin);
+ state->plugin->insert_refresh_melt (state->plugin->cls,
+ state->session,
+ 1,
+ melt);
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_GET_REFRESH_MELT:
+ {
+ int ret;
+ unsigned int hash_index;
+ struct GNUNET_HashCode *hash;
+ struct TALER_EXCHANGEDB_RefreshMelt melt;
+
+ hash_index = cmd_find (state->cmd,
+ state->cmd[state->i].details.get_refresh_melt.label_hash);
+ hash = state->cmd[hash_index].exposed.data.session_hash;
+ ret = state->plugin->get_refresh_melt (state->plugin->cls,
+ state->session,
+ hash,
+ 1,
+ &melt);
+ GNUNET_assert (GNUNET_SYSERR != ret);
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_INSERT_REFRESH_ORDER:
+ {
+ unsigned int hash_index;
+ unsigned int denom_index;
+ struct GNUNET_HashCode *session_hash;
+ struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *denom;
+
+ hash_index = state->cmd[state->i].details.insert_refresh_order.index_hash;
+ denom_index = state->cmd[state->i].details.insert_refresh_order.index_denom;
+ session_hash = state->cmd[hash_index].exposed.data.session_hash;
+ denom = state->cmd[denom_index].exposed.data.dki;
+ state->plugin->insert_refresh_order (state->plugin->cls,
+ state->session,
+ session_hash,
+ 1,
+ &denom->denom_pub);
+
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_GET_REFRESH_ORDER:
+ {
+ int hash_index;
+ struct GNUNET_HashCode *hash;
+ struct TALER_DenominationPublicKey denom_pub;
+
+ hash_index = state->cmd[state->i].details.get_refresh_order.index_hash;
+ hash = state->cmd[hash_index].exposed.data.session_hash;
+ state->plugin->get_refresh_order (state->plugin->cls,
+ state->session,
+ hash,
+ 1,
+ &denom_pub);
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_INSERT_REFRESH_COMMIT_COIN:
+ {
+ int ret;
+ unsigned int hash_index;
+ struct TALER_EXCHANGEDB_RefreshCommitCoin *refresh_commit;
+
+ hash_index = state->cmd[state->i].details.insert_refresh_commit_coin.index_hash;
+ refresh_commit = PERF_TALER_EXCHANGEDB_refresh_commit_coin_init ();
+ ret = state->plugin->insert_refresh_commit_coins (state->plugin->cls,
+ state->session,
+ state->cmd[hash_index].exposed.data.session_hash,
+ 1,
+ 1,
+ refresh_commit);
+ GNUNET_assert (GNUNET_OK == ret);
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_GET_REFRESH_COMMIT_COIN:
+ {
+ unsigned int hash_index;
+ struct TALER_EXCHANGEDB_RefreshCommitCoin refresh_commit;
+
+ hash_index = state->cmd[state->i].details.insert_refresh_commit_coin.index_hash;
+ state->plugin->get_refresh_commit_coins (state->plugin->cls,
+ state->session,
+ state->cmd[hash_index].exposed.data.session_hash,
+ 1,
+ 1,
+ &refresh_commit);
+
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_INSERT_REFRESH_COMMIT_LINK:
+ {
+// unsigned int hash_index;
+//
+// hash_index = state->cmd[state->i].details.insert_refresh_commit_link.index_hash;
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_GET_REFRESH_COMMIT_LINK:
+ {
+ int ret;
+ unsigned int hash_index;
+ struct TALER_EXCHANGEDB_RefreshCommitCoin commit_coin;
+
+ hash_index = state->cmd[state->i].details.get_refresh_commit_link.index_hash;
+ ret = state->plugin->get_refresh_commit_coins(state->plugin->cls,
+ state->session,
+ state->cmd[hash_index].exposed.data.session_hash,
+ 1,
+ 1,
+ &commit_coin);
+ GNUNET_assert (GNUNET_SYSERR != ret);
+ }
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_GET_MELT_COMMITMENT:
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_INSERT_REFRESH_OUT:
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_GET_LINK_DATA_LIST:
+ break;
+
+ case PERF_TALER_EXCHANGEDB_CMD_GET_TRANSFER:
+ break;
+
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Runs the commands given in @a cmd, working with
+ * the database referenced by @a db_plugin
+ *
+ * @param db_plugin the connection to the database
+ * @param cmd the commands to run
+ */
+int
+PERF_TALER_EXCHANGEDB_interpret (struct TALER_EXCHANGEDB_Plugin *db_plugin,
+ struct PERF_TALER_EXCHANGEDB_Cmd cmd[])
+{
+ int ret;
+ struct PERF_TALER_EXCHANGEDB_interpreter_state state =
+ {.i = 0, .cmd = cmd, .plugin = db_plugin};
+
+ ret = cmd_init (cmd);
+ if (GNUNET_SYSERR == ret)
+ return ret;
+ state.session = db_plugin->get_session (db_plugin->cls,
+ GNUNET_YES);
+ GNUNET_assert (NULL != state.session);
+ ret = interpret (&state);
+ cmd_clean (cmd);
+ return ret;
+}
+
+
+/**
+ * Initialize the database and run the benchmark
+ *
+ * @param benchmark_name the name of the benchmark, displayed in the logs
+ * @param configuration_file path to the taler configuration file to use
+ * @param init the commands to use for the database initialisation,
+ * if #NULL the standard initialization is used
+ * @param benchmark the commands for the benchmark
+ * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
+ */
+int
+PERF_TALER_EXCHANGEDB_run_benchmark (const char *benchmark_name,
+ const char *configuration_file,
+ struct PERF_TALER_EXCHANGEDB_Cmd *init,
+ struct PERF_TALER_EXCHANGEDB_Cmd *benchmark)
+{
+ struct TALER_EXCHANGEDB_Plugin *plugin;
+ struct GNUNET_CONFIGURATION_Handle *config;
+ int ret = 0;
+ struct PERF_TALER_EXCHANGEDB_Cmd init_def[] =
+ {
+ // Denomination used to create coins
+ PERF_TALER_EXCHANGEDB_INIT_CMD_DEBUG ("00 - Start of interpreter"),
+
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("01 - denomination loop",
+ PERF_TALER_EXCHANGEDB_NB_DENOMINATION_INIT),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_START_TRANSACTION (""),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_DENOMINATION ("01 - denomination"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_DENOMINATION ("01 - insert",
+ "01 - denomination"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_COMMIT_TRANSACTION (""),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_SAVE_ARRAY ("01 - save denomination",
+ "01 - denomination loop",
+ "01 - denomination",
+ PERF_TALER_EXCHANGEDB_NB_DENOMINATION_SAVE),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("",
+ "01 - denomination loop"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_DEBUG ("01 - init denomination complete"),
+ // End of initialization
+ // Reserve initialization
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("02 - init reserve loop",
+ PERF_TALER_EXCHANGEDB_NB_RESERVE_INIT),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_RESERVE ("02 - reserve"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_RESERVE ("02 - insert",
+ "02 - reserve"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_SAVE_ARRAY ("02 - save reserve",
+ "02 - init reserve loop",
+ "02 - reserve",
+ PERF_TALER_EXCHANGEDB_NB_RESERVE_SAVE),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("",
+ "02 - init reserve loop"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_DEBUG ("02 - reserve init complete"),
+ // End reserve init
+ // Withdrawal initialization
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("03 - init withdraw loop",
+ PERF_TALER_EXCHANGEDB_NB_WITHDRAW_INIT),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_START_TRANSACTION (""),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY ("03 - denomination load",
+ "03 - init withdraw loop",
+ "01 - save denomination"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY ("03 - reserve load",
+ "03 - init withdraw loop",
+ "02 - save reserve"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_WITHDRAW ("03 - withdraw",
+ "03 - denomination load",
+ "03 - reserve load"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_WITHDRAW ("03 - insert",
+ "03 - withdraw"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_COMMIT_TRANSACTION (""),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_SAVE_ARRAY ("03 - save coin",
+ "03 - init withdraw loop",
+ "03 - withdraw",
+ PERF_TALER_EXCHANGEDB_NB_WITHDRAW_SAVE),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("",
+ "03 - init withdraw loop"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_DEBUG ("03 - withdraw init complete"),
+ //End of withdrawal initialization
+ //Deposit initialization
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("04 - deposit init loop",
+ PERF_TALER_EXCHANGEDB_NB_DEPOSIT_INIT),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_START_TRANSACTION ("04 - start transaction"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY ("04 - denomination load",
+ "04 - deposit init loop",
+ "03 - save coin"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_DEPOSIT ("04 - deposit",
+ "04 - denomination load"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_COMMIT_TRANSACTION ("04 - commit transaction"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_SAVE_ARRAY ("04 - deposit array",
+ "04 - deposit init loop",
+ "04 - deposit",
+ PERF_TALER_EXCHANGEDB_NB_DEPOSIT_SAVE),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("04 - deposit init loop end",
+ "04 - deposit init loop"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_DEBUG ("04 - deposit init complete"),
+ // End of deposit initialization
+ PERF_TALER_EXCHANGEDB_INIT_CMD_END ("end")
+ };
+
+ GNUNET_log_setup (benchmark_name,
+ "INFO",
+ NULL);
+ config = GNUNET_CONFIGURATION_create ();
+ ret = GNUNET_CONFIGURATION_parse (config,
+ configuration_file);
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Error parsing configuration file\n");
+ return GNUNET_SYSERR;
+ }
+ plugin = TALER_EXCHANGEDB_plugin_load (config);
+ if (NULL == plugin)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Error connectiong to the database\n");
+ return ret;
+ }
+ ret = plugin->create_tables (plugin->cls,
+ GNUNET_YES);
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Error while creating the database architecture\n");
+ return ret;
+ }
+ /*
+ * Running the initialization
+ */
+ if (NULL == init)
+ {
+ init = init_def;
+ }
+ ret = PERF_TALER_EXCHANGEDB_interpret (plugin,
+ init);
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Error during database initialization\n");
+ return ret;
+ }
+ /*
+ * Running the benchmark
+ */
+ ret = PERF_TALER_EXCHANGEDB_interpret (plugin,
+ benchmark);
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Error while runing the benchmark\n");
+ return ret;
+ }
+ /* Drop tables */
+ {
+ struct TALER_EXCHANGEDB_Session *session;
+
+ session = plugin->get_session (plugin->cls,
+ GNUNET_YES);
+ ret = plugin->drop_temporary (plugin->cls,
+ session);
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Error cleaning the database\n");
+ return ret;
+ }
+ }
+ TALER_EXCHANGEDB_plugin_unload (plugin);
+ GNUNET_CONFIGURATION_destroy (config);
+ return ret;
+}
diff --git a/src/exchangedb/perf_taler_exchangedb_interpreter.h b/src/exchangedb/perf_taler_exchangedb_interpreter.h
new file mode 100644
index 000000000..a83251c60
--- /dev/null
+++ b/src/exchangedb/perf_taler_exchangedb_interpreter.h
@@ -0,0 +1,1319 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015 GNUnet e.V.
+
+ 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 exchangedb/perf_taler_exchangedb_interpreter.h
+ * @brief Library for performance analysis of the Taler database
+ * @author Nicolas Fournier
+ *
+ * This library contains functions and macro alowing Taler performance analysis
+ * to be written with ease.
+ * To do so, create a #PERF_TALER_EXCHANGEDB_Cmd array and fill it with the commands
+ * to execute in chronological order. Some command have an exposed variable wich
+ * can be reused in other commands.
+ * Macros are available to make the use much easier so feel free to use them
+ * to initialize your own command array.
+ */
+
+#ifndef __PERF_TALER_EXCHANGEDB_INTERPRETER_H__
+#define __PERF_TALER_EXCHANGEDB_INTERPRETER_H__
+
+#include <sys/time.h>
+#include "taler_exchangedb_plugin.h"
+
+
+#define PERF_TALER_EXCHANGEDB_NB_DENOMINATION_INIT 10
+#define PERF_TALER_EXCHANGEDB_NB_DENOMINATION_SAVE 10
+
+#define PERF_TALER_EXCHANGEDB_NB_RESERVE_INIT 100
+#define PERF_TALER_EXCHANGEDB_NB_RESERVE_SAVE 10
+
+#define PERF_TALER_EXCHANGEDB_NB_DEPOSIT_INIT 100
+#define PERF_TALER_EXCHANGEDB_NB_DEPOSIT_SAVE 10
+
+#define PERF_TALER_EXCHANGEDB_NB_WITHDRAW_INIT 100
+#define PERF_TALER_EXCHANGEDB_NB_WITHDRAW_SAVE 10
+
+
+/**
+ * Marks the end of the command chain
+ *
+ * @param _label The label of the command
+ */
+#define PERF_TALER_EXCHANGEDB_INIT_CMD_END(_label) \
+{ \
+ .command = PERF_TALER_EXCHANGEDB_CMD_END, \
+ .label = _label, \
+ .exposed.type = PERF_TALER_EXCHANGEDB_NONE \
+}
+
+
+/**
+ * Prints @ _label to stdout
+ *
+ * @param _label The label of the command,
+ * will be logged each time the command runs
+ */
+#define PERF_TALER_EXCHANGEDB_INIT_CMD_DEBUG(_label) \
+{ \
+ .command = PERF_TALER_EXCHANGEDB_CMD_DEBUG, \
+ .label = _label, \
+ .exposed.type = PERF_TALER_EXCHANGEDB_NONE \
+}
+
+/**
+ * The begining of a loop
+ *
+ * @param _label the label of the loop
+ * @param _iter the number of iterations of the loop
+ */
+#define PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP(_label, _iter) \
+{ \
+ .command = PERF_TALER_EXCHANGEDB_CMD_LOOP , \
+ .label = _label , \
+ .exposed.type = PERF_TALER_EXCHANGEDB_NONE , \
+ .details.loop = { \
+ .max_iterations = _iter , \
+ .curr_iteration = 0 } \
+}
+
+/**
+ * Marks the end of the loop @_label_loop
+ *
+ * @param _label the label of the command
+ * @param _label_loop the label of the loop closed by this command
+ */
+#define PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP(_label, _label_loop) \
+{\
+ .command = PERF_TALER_EXCHANGEDB_CMD_END_LOOP , \
+ .label = _label , \
+ .exposed.type = PERF_TALER_EXCHANGEDB_NONE , \
+ .details.end_loop.label_loop = _label_loop \
+}
+
+/**
+ * Saves the time of execution to use for logging with Gauger
+ *
+ * @param _label the label of the command
+ */
+#define PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME(_label) \
+{ \
+ .command = PERF_TALER_EXCHANGEDB_CMD_GET_TIME, \
+ .label = _label, \
+ .exposed.type = PERF_TALER_EXCHANGEDB_TIME \
+}
+
+/**
+ * Commits the duration between @a _label_start and @a _label_stop
+ * to Gauger with @a _description explaining what was measured.
+ *
+ * @param _label the label of this command
+ * @param _label_start label of the start of the measurment
+ * @param _label_stop label of the end of the measurment
+ * @param _description description of the measure displayed in Gauger
+ * @param _unit the unit of the data measured, typicly something/sec
+ * @param _divide number of measurments in the interval
+ */
+#define PERF_TALER_EXCHANGEDB_INIT_CMD_GAUGER(_label, _label_start, _label_stop, _category, _description, _unit, _divide) \
+{ \
+ .command = PERF_TALER_EXCHANGEDB_CMD_GAUGER, \
+ .label = _label, \
+ .exposed.type = PERF_TALER_EXCHANGEDB_NONE, \
+ .details.gauger = { \
+ .label_start = _label_start, \
+ .label_stop = _label_stop, \
+ .category = _category, \
+ .description = _description, \
+ .unit = _unit, \
+ .divide = _divide, \
+ } \
+}
+
+/**
+ * Initiate a database transaction
+ *
+ * @param _label the label of the command
+ */
+#define PERF_TALER_EXCHANGEDB_INIT_CMD_START_TRANSACTION(_label) \
+{ \
+ .command = PERF_TALER_EXCHANGEDB_CMD_START_TRANSACTION, \
+ .label = _label, \
+ .exposed.type = PERF_TALER_EXCHANGEDB_NONE, \
+}
+
+/**
+ * Commits a database transaction
+ *
+ * @param _label the label of the command
+ */
+#define PERF_TALER_EXCHANGEDB_INIT_CMD_COMMIT_TRANSACTION(_label) \
+{ \
+ .command = PERF_TALER_EXCHANGEDB_CMD_COMMIT_TRANSACTION, \
+ .label = _label, \
+ .exposed.type = PERF_TALER_EXCHANGEDB_NONE, \
+}
+
+/**
+ * Abort the current transaction
+ *
+ * @param _label the label of the command
+ */
+#define PERF_TALER_EXCHANGEDB_INIT_CMD_ABORT_TRANSACTION(_label) \
+{ \
+ .command = PERF_TALER_EXCHANGEDB_CMD_ABORT_TRANSACTION, \
+ .label = _label,
+
+/**
+ * Saves randomly selected items from @a _label_save
+ * Saved items can latter be access using #PERF_TALER_EXCHANGEDB_CMD_LOAD_ARRAY
+ *
+ * @param _label the label of the command, used by other commands to reference it
+ * @param _label_loop the label of the loop the array iterates over
+ * @param _label_save the label of the command which outout is saved by this command
+ * @param _nb_saved the total number of items to be saved
+ */
+#define PERF_TALER_EXCHANGEDB_INIT_CMD_SAVE_ARRAY(_label, _label_loop, _label_save, _nb_saved) \
+{ \
+ .command = PERF_TALER_EXCHANGEDB_CMD_SAVE_ARRAY, \
+ .label = _label, \
+ .exposed.type = PERF_TALER_EXCHANGEDB_NONE, \
+ .details.save_array = { \
+ .label_loop = _label_loop, \
+ .label_save = _label_save, \
+ .nb_saved = _nb_saved, \
+ } \
+}
+
+/**
+ * Loads data from a #PERF_TALER_EXCHANGEDB_CMD_SAVE_ARRAY to allow other
+ * commands to access it
+ *
+ * @param _label the label of this command, referenced by commands to access it's outpout
+ * @param _label_loop the label of the loop to iterate over
+ * @param _label_save the label of the #PERF_TALER_EXCHANGEDB_CMD_SAVE_ARRAY providing data
+ */
+#define PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY(_label, _label_loop, _label_save) \
+{ \
+ .command = PERF_TALER_EXCHANGEDB_CMD_LOAD_ARRAY, \
+ .label = _label, \
+ .exposed.type = PERF_TALER_EXCHANGEDB_NONE, \
+ .details.load_array = { \
+ .label_loop = _label_loop, \
+ .label_save = _label_save \
+ } \
+}
+
+/**
+ * Create a denomination key to use
+ * Exposes a #PERF_TALER_EXCHANGEDB_DENOMINATION_INFO to be used by other commands
+ * @exposed #PERF_TALER_EXCHANGEDB_DENOMINATION_INFO
+ *
+ * @param _label the label of this command
+ */
+#define PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_DENOMINATION(_label) \
+{ \
+ .command = PERF_TALER_EXCHANGEDB_CMD_CREATE_DENOMINATION, \
+ .label = _label, \
+ .exposed.type = PERF_TALER_EXCHANGEDB_DENOMINATION_INFO, \
+}
+
+/**
+ * Inserts informations about a denomination key in the database
+ *
+ * @param _label the label of this command
+ * @param _label_denom the label of the denomination to insert
+ */
+#define PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_DENOMINATION(_label, _label_denom) \
+{ \
+ .command = PERF_TALER_EXCHANGEDB_CMD_INSERT_DENOMINATION, \
+ .label = _label, \
+ .exposed.type = PERF_TALER_EXCHANGEDB_NONE, \
+ .details.insert_denomination.label_denom = _label_denom, \
+}
+
+/**
+ * Polls the database about informations regarding a specific denomination key
+ *
+ * @param _label the label of this command
+ * @param _label_denom the label of the command providing information about the denomination key
+ */
+#define PERF_TALER_EXCHANGEDB_INIT_CMD_GET_DENOMINATION(_label, _label_denom) \
+{ \
+ .command = PERF_TALER_EXCHANGEDB_CMD_GET_DENOMINATION, \
+ .label = _label, \
+ .exposed.type = PERF_TALER_EXCHANGEDB_NONE, \
+ .details.get_denomination.label_denom = _label_denom \
+}
+
+/**
+ * Create a reserve to be used later
+ * Exposes a #PERF_TALER_EXCHANGEDB_RESERVE
+ *
+ * @param _label the label of the command
+ */
+#define PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_RESERVE(_label) \
+{ \
+ .command = PERF_TALER_EXCHANGEDB_CMD_CREATE_RESERVE, \
+ .label = _label, \
+ .exposed.type = PERF_TALER_EXCHANGEDB_RESERVE \
+}
+
+/**
+ * Insert a new reserve in the database containing 1000 Euros
+ *
+ * @param _label the name of this command
+ * @param _label_reserve the label of the reserve to insert
+ */
+#define PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_RESERVE(_label, _label_reserve) \
+{ \
+ .command = PERF_TALER_EXCHANGEDB_CMD_INSERT_RESERVE, \
+ .label = _label, \
+ .exposed.type = PERF_TALER_EXCHANGEDB_NONE, \
+ .details.insert_reserve.label_reserve = _label_reserve \
+}
+
+/**
+ * Polls the database for a secific reserve's details
+ *
+ * @param _label the label of this command
+ * @param _label_reserve the reserve to poll
+ */
+#define PERF_TALER_EXCHANGEDB_INIT_CMD_GET_RESERVE(_label, _label_reserve) \
+{ \
+ .command = PERF_TALER_EXCHANGEDB_CMD_GET_RESERVE, \
+ .label = _label, \
+ .exposed.type = PERF_TALER_EXCHANGEDB_NONE, \
+ .details.get_reserve.label_reserve = _label_reserve \
+}
+
+/**
+ * Polls the database for the history of a reserve
+ *
+ * @param _label the label of the command
+ * @param _label_reserve the reserve to examine
+ */
+#define PERF_TALER_EXCHANGEDB_INIT_CMD_GET_RESERVE_HISTORY(_label, _label_reserve) \
+{ \
+ .command = PERF_TALER_EXCHANGEDB_CMD_GET_RESERVE_HISTORY, \
+ .label = _label, \
+ .exposed.type = PERF_TALER_EXCHANGEDB_NONE, \
+ .details.get_reserve_history.label_reserve = _label_reserve \
+}
+
+/**
+ * Creates a coin to be used later
+ *
+ * @param _label the label of this command
+ * @param _label_dki denomination key used to sign the coin
+ * @param _label_reserve reserve used to emmit the coin
+ */
+#define PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_WITHDRAW(_label, _label_dki, _label_reserve) \
+{ \
+ .command = PERF_TALER_EXCHANGEDB_CMD_CREATE_WITHDRAW, \
+ .label = _label, \
+ .exposed.type = PERF_TALER_EXCHANGEDB_COIN, \
+ .details.create_withdraw = {\
+ .label_dki = _label_dki, \
+ .label_reserve = _label_reserve, \
+ } \
+}
+
+/**
+ * Inserts informations about a withdrawal in the database
+ *
+ * @exposes #PERF_TALER_EXCHANGEDB_COIN
+ *
+ * @param _label the label of this command
+ * @param _label_coin the coin to insert
+ */
+#define PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_WITHDRAW(_label, _label_coin) \
+{ \
+ .command = PERF_TALER_EXCHANGEDB_CMD_INSERT_WITHDRAW, \
+ .label = _label, \
+ .exposed.type = PERF_TALER_EXCHANGEDB_NONE, \
+ .details.insert_withdraw.label_coin = _label_coin\
+}
+
+
+/**
+ * Polls the database about informations regarding a specific withdrawal
+ *
+ * @param _label the label of this command
+ * @param _label_coin the coin to check
+ */
+#define PERF_TALER_EXCHANGEDB_INIT_CMD_GET_WITHDRAW(_label, _label_coin) \
+{ \
+ .command = PERF_TALER_EXCHANGEDB_CMD_GET_WITHDRAW, \
+ .label = _label, \
+ .exposed.type = PERF_TALER_EXCHANGEDB_NONE, \
+ .details.get_withdraw.label_coin = _label_coin, \
+}
+
+
+/**
+ * The /reserve/withdraw api call
+ *
+ * Exposes #PERF_TALER_EXCHANGEDB_COIN
+ *
+ * @param _label the label of this command
+ * @param _label_dki the denomination of the created coin
+ * @param _label_reserve the reserve used to provide currency
+ */
+#define PERF_TALER_EXCHANGEDB_INIT_CMD_WITHDRAW_SIGN(_label, _label_dki, _label_reserve) \
+ PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_WITHDRAW (_label "withdraw", \
+ _label_dki, \
+ _label_reserve), \
+ PERF_TALER_EXCHANGEDB_INIT_CMD_GET_DENOMINATION(_label "withdraw info", \
+ _label_dki), \
+ PERF_TALER_EXCHANGEDB_INIT_CMD_GET_RESERVE_HISTORY(_label "reserve_history", \
+ _label_reserve), \
+ PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_WITHDRAW(_label "insert withdraw", \
+ _label "withdraw")
+
+/**
+ * Create a deposit for use later
+ * @exposes #PERF_TALER_EXCHANGEDB_DEPOSIT
+ *
+ * @param _label the label of this command
+ * @param _label_coin the coin used to pay
+ */
+#define PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_DEPOSIT(_label, _label_coin) \
+{ \
+ .command = PERF_TALER_EXCHANGEDB_CMD_CREATE_DEPOSIT, \
+ .label = _label, \
+ .exposed.type = PERF_TALER_EXCHANGEDB_DEPOSIT, \
+ .details.create_deposit.label_coin = _label_coin, \
+}
+
+/**
+ * Insert a deposit into the database
+ *
+ * @param _label the label of this command
+ * @param _label_deposit the deposit inseerted
+ */
+#define PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_DEPOSIT(_label, _label_deposit) \
+{ \
+ .command = PERF_TALER_EXCHANGEDB_CMD_INSERT_DEPOSIT,\
+ .label = _label, \
+ .exposed.type = PERF_TALER_EXCHANGEDB_NONE, \
+ .details.insert_deposit.label_deposit = _label_deposit, \
+}
+
+/**
+ * Check if a deposit is in the database
+ *
+ * @param _label the label of this command
+ * @param _label_deposit the deposit to use
+ */
+#define PERF_TALER_EXCHANGEDB_INIT_CMD_GET_DEPOSIT(_label, _label_deposit) \
+{ \
+ .command = PERF_TALER_EXCHANGEDB_CMD_GET_DEPOSIT, \
+ .label = _label, \
+ .exposed.type = PERF_TALER_EXCHANGEDB_NONE, \
+ .details.get_deposit.label_deposit = _label_deposit \
+}
+
+/**
+ * Access the transaction history of a coin
+ *
+ * @param _label the label of the command
+ * @param _label_coin the coin which history is checked
+ */
+#define PERF_TALER_EXCHANGEDB_INIT_CMD_GET_COIN_TRANSACTION(_label, _label_coin) \
+{ \
+ .command = PERF_TALER_EXCHANGEDB_CMD_GET_COIN_TRANSACTION, \
+ .label = _label, \
+ .exposed.type = PERF_TALER_EXCHANGEDB_NONE, \
+ .details.get_coin_transaction.label_coin = _label_coin \
+}
+
+/**
+ * The /deposit api call
+ *
+ * @param _label the label of the command
+ * @param _label_coin the coin used for the deposit
+ */
+#define PERF_TALER_EXCHANGEDB_INIT_CMD_DEPOSIT(_label, _label_coin) \
+ PERF_TALER_EXCHANGEDB_INIT_CMD_GET_COIN_TRANSACTION (_label "coin history", \
+ _label_coin), \
+ PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_DEPOSIT (_label "deposit", \
+ _label_coin), \
+ PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_DEPOSIT (_label "insert", \
+ _label "deposit")
+/**
+ * Insert informations about a refresh session
+ * melts one coin into another
+ *
+ * @param _label the label of the command
+ */
+#define PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_REFRESH_SESSION(_label) \
+{ \
+ .command = PERF_TALER_EXCHANGEDB_CMD_CREATE_REFRESH_SESSION, \
+ .label = _label, \
+ .exposed.type = PERF_TALER_EXCHANGEDB_REFRESH_HASH \
+}
+
+/**
+ * Get informations about a refresh session
+ *
+ * @param _label the label of the command
+ * @param _label_hash the label of the hash to search
+ */
+#define PERF_TALER_EXCHANGEDB_INIT_CMD_GET_REFRESH_SESSION(_label, \
+ _label_hash) \
+{ \
+ .command = PERF_TALER_EXCHANGEDB_CMD_GET_REFRESH_SESSION, \
+ .label = _label, \
+ .exposed.type = PERF_TALER_EXCHANGEDB_NONE \
+}
+
+/**
+ * Insert a melt operation in the database
+ *
+ * @param _label the label of the command
+ * @param _label_hash the label of the hash of the session
+ * @param _label_coin the label of the coin to melt
+ */
+#define PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_REFRESH_MELT(_label, \
+ _label_hash, \
+ _label_coin) \
+{ \
+ .command = PERF_TALER_EXCHANGEDB_CMD_INSERT_REFRESH_MELT, \
+ .label = _label, \
+ .details.insert_refresh_melt.label_hash = _label_hash, \
+ .details.insert_refresh_melt.label_coin = _label_coin, \
+ .exposed.type = PERF_TALER_EXCHANGEDB_NONE \
+}
+
+/**
+ * Get informations about a melt operation
+ *
+ * @param _label the label of the command
+ * @param _label_hash the label of the hash of the refresh session
+ */
+#define PERF_TALER_EXCHANGEDB_INIT_CMD_GET_REFRESH_MELT(_label, \
+ _label_hash) \
+{ \
+ .command = PERF_TALER_EXCHANGEDB_CMD_GET_REFRESH_MELT, \
+ .label = _label, \
+ .detail.get_refresh_melt.label_hash = _label_hash, \
+ .exposed.type = PERF_TALER_EXCHANGEDB_NONE \
+}
+
+/**
+ * The type of data stored in #PERF_TALER_EXCHANGEDB_Memory
+ */
+enum PERF_TALER_EXCHANGEDB_Type
+{
+ PERF_TALER_EXCHANGEDB_NONE,
+ PERF_TALER_EXCHANGEDB_TIME,
+ PERF_TALER_EXCHANGEDB_DENOMINATION_INFO,
+ PERF_TALER_EXCHANGEDB_RESERVE,
+ PERF_TALER_EXCHANGEDB_COIN,
+ PERF_TALER_EXCHANGEDB_DEPOSIT,
+ PERF_TALER_EXCHANGEDB_REFRESH_HASH,
+ PERF_TALER_EXCHANGEDB_REFRESH_MELT
+};
+
+
+/**
+ * Structure used to handle several data type
+ */
+struct PERF_TALER_EXCHANGEDB_Data
+{
+ enum PERF_TALER_EXCHANGEDB_Type type;
+
+ /**
+ * Storage for a variety of data type
+ * The data saved should match #type
+ */
+ union PERF_TALER_EXCHANGEDB_Memory
+ {
+ /** #PERF_TALER_EXCHANGEDB_TIME */
+ struct GNUNET_TIME_Absolute *time;
+ /** #PERF_TALER_EXCHANGEDB_DEPOSIT */
+ struct TALER_EXCHANGEDB_Deposit *deposit;
+ /** #PERF_TALER_EXCHANGEDB_COIN */
+ struct PERF_TALER_EXCHANGEDB_Coin *coin;
+ /** #PERF_TALER_EXCHANGEDB_RESERVE */
+ struct PERF_TALER_EXCHANGEDB_Reserve *reserve;
+ /** #PERF_TALER_EXCHANGEDB_DENOMINATION_INFO */
+ struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki;
+ /** #PERF_TALER_EXCHANGEDB_REFRESH_HASH */
+ struct GNUNET_HashCode *session_hash;
+ /** #PERF_TALER_EXCHANGEDB_REFRESH_MELT */
+ struct TALER_EXCHANGEDB_RefreshMelt *refresh_melt;
+ } data;
+};
+
+
+/**
+ * Name of the command
+ */
+enum PERF_TALER_EXCHANGEDB_CMD_Name
+{
+ /**
+ * All comand chain must hace this as their last command
+ */
+ PERF_TALER_EXCHANGEDB_CMD_END,
+
+ /**
+ * Prints it's label
+ */
+ PERF_TALER_EXCHANGEDB_CMD_DEBUG,
+
+ /**
+ * Define the start of al command chain loop
+ */
+ PERF_TALER_EXCHANGEDB_CMD_LOOP,
+
+ /**
+ * Define the end of a command chain loop
+ */
+ PERF_TALER_EXCHANGEDB_CMD_END_LOOP,
+
+ /**
+ * Save the time at which the command was executed
+ */
+ PERF_TALER_EXCHANGEDB_CMD_GET_TIME,
+
+ /**
+ * Upload performance to Gauger
+ */
+ PERF_TALER_EXCHANGEDB_CMD_GAUGER,
+
+ /**
+ * Start a new session
+ */
+ PERF_TALER_EXCHANGEDB_CMD_NEW_SESSION,
+
+ /**
+ * Start a database transaction
+ */
+ PERF_TALER_EXCHANGEDB_CMD_START_TRANSACTION,
+
+ /**
+ * End a database transaction
+ */
+ PERF_TALER_EXCHANGEDB_CMD_COMMIT_TRANSACTION,
+
+ /**
+ * Abort a transaction started with #PERF_TALER_EXCHANGEDB_CMD_START_TRANSACTION
+ */
+ PERF_TALER_EXCHANGEDB_CMD_ABORT_TRANSACTION,
+
+ /**
+ * Saves random deposits from a loop
+ */
+ PERF_TALER_EXCHANGEDB_CMD_SAVE_ARRAY,
+
+ /**
+ * Load items saved earlier in a #PERF_TALER_EXCHANGEDB_CMD_SAVE_ARRAY
+ * The items are loaded in a random order, but all of them will be loaded
+ */
+ PERF_TALER_EXCHANGEDB_CMD_LOAD_ARRAY,
+
+ /**
+ * Loads a random item from a #PERF_TALER_EXCHANGEDB_CMD_SAVE_ARRAY
+ * A random item is loaded each time the command is run
+ */
+ PERF_TALER_EXCHANGEDB_CMD_LOAD_RANDOM,
+
+ /**
+ * Create a denomination to be used later
+ */
+ PERF_TALER_EXCHANGEDB_CMD_CREATE_DENOMINATION,
+
+ /**
+ * Insert informations about a denomination key in the database
+ */
+ PERF_TALER_EXCHANGEDB_CMD_INSERT_DENOMINATION,
+
+ /**
+ * Polls the database for informations about a specific denomination key
+ */
+ PERF_TALER_EXCHANGEDB_CMD_GET_DENOMINATION,
+
+ /**
+ * Create a reserve to be used later
+ */
+ PERF_TALER_EXCHANGEDB_CMD_CREATE_RESERVE,
+
+ /**
+ * Insert currency in a reserve / Create a reserve
+ */
+ PERF_TALER_EXCHANGEDB_CMD_INSERT_RESERVE,
+
+ /**
+ * Get Informations about a reserve
+ */
+ PERF_TALER_EXCHANGEDB_CMD_GET_RESERVE,
+
+ /**
+ * Get the history of a reserve
+ */
+ PERF_TALER_EXCHANGEDB_CMD_GET_RESERVE_HISTORY,
+
+ /**
+ * Create a withdrawal to be used later
+ */
+ PERF_TALER_EXCHANGEDB_CMD_CREATE_WITHDRAW,
+
+ /**
+ * Insert informations about a withdrawal in the database
+ */
+ PERF_TALER_EXCHANGEDB_CMD_INSERT_WITHDRAW,
+
+ /**
+ * Pulls informations about a withdrawal from the database
+ */
+ PERF_TALER_EXCHANGEDB_CMD_GET_WITHDRAW,
+
+ /**
+ * Get the list of all transactions the coin has been in
+ */
+ PERF_TALER_EXCHANGEDB_CMD_GET_COIN_TRANSACTION,
+
+ /**
+ * Create a deposit to be used later
+ */
+ PERF_TALER_EXCHANGEDB_CMD_CREATE_DEPOSIT,
+
+ /**
+ * Insert a deposit into the database
+ */
+ PERF_TALER_EXCHANGEDB_CMD_INSERT_DEPOSIT,
+
+ /**
+ * Check if a deposit is in the database
+ */
+ PERF_TALER_EXCHANGEDB_CMD_GET_DEPOSIT,
+
+ /**
+ * Create a refresh session
+ * The number of melted coins is 1,
+ * The number of exchangeed coins is 1
+ */
+ PERF_TALER_EXCHANGEDB_CMD_CREATE_REFRESH_SESSION,
+
+ /**
+ * Get a refresh session informations
+ */
+ PERF_TALER_EXCHANGEDB_CMD_GET_REFRESH_SESSION,
+
+ /**
+ * Insert a refresh melt
+ */
+ PERF_TALER_EXCHANGEDB_CMD_INSERT_REFRESH_MELT,
+
+ /**
+ * Get informations about a refresh melt operation
+ */
+ PERF_TALER_EXCHANGEDB_CMD_GET_REFRESH_MELT,
+
+ /**
+ * Insert a melt refresh order
+ */
+ PERF_TALER_EXCHANGEDB_CMD_INSERT_REFRESH_ORDER,
+
+ /**
+ * Get informations about a refresh order
+ */
+ PERF_TALER_EXCHANGEDB_CMD_GET_REFRESH_ORDER,
+
+ /**
+ * Insert refresh commit coin
+ */
+ PERF_TALER_EXCHANGEDB_CMD_INSERT_REFRESH_COMMIT_COIN,
+
+ /**
+ * Get refresh commit coin
+ */
+ PERF_TALER_EXCHANGEDB_CMD_GET_REFRESH_COMMIT_COIN,
+
+ /**
+ * Insert refresh commit link
+ */
+ PERF_TALER_EXCHANGEDB_CMD_INSERT_REFRESH_COMMIT_LINK,
+
+ /**
+ * Get refresh commit link
+ */
+ PERF_TALER_EXCHANGEDB_CMD_GET_REFRESH_COMMIT_LINK,
+
+ /**
+ * Get information avout the melt commit
+ */
+ PERF_TALER_EXCHANGEDB_CMD_GET_MELT_COMMITMENT,
+
+ /**
+ * Insert a new coin into the database after a melt operation
+ */
+ PERF_TALER_EXCHANGEDB_CMD_INSERT_REFRESH_OUT,
+
+ /**
+ * Get the link data list of a coin
+ */
+ PERF_TALER_EXCHANGEDB_CMD_GET_LINK_DATA_LIST,
+
+ /**
+ * Get the shared secret and the transfere public key
+ */
+ PERF_TALER_EXCHANGEDB_CMD_GET_TRANSFER
+
+};
+
+
+/**
+ * Contains extra data required for any command
+ */
+union PERF_TALER_EXCHANGEDB_CMD_Details
+{
+ /**
+ * Extra data requiered for the #PERF_TALER_EXCHANGEDB_CMD_LOOP command
+ */
+ struct PERF_TALER_EXCHANGEDB_CMD_loopDetails
+ {
+ /**
+ * Maximum number of iteration in the loop
+ */
+ const unsigned int max_iterations;
+
+ /**
+ * The current iteration of the loop
+ */
+ unsigned int curr_iteration;
+ } loop;
+
+ /**
+ * Extra data requiered by the #PERF_TALER_EXCHANGEDB_CMD_END_LOOP command
+ */
+ struct PERF_TALER_EXCHANGEDB_CMD_endLoopDetails
+ {
+ /**
+ * Label of the loop closed by the command
+ */
+ const char *label_loop;
+ unsigned int index_loop;
+ } end_loop;
+
+ /**
+ * Details about the #PERF_TALER_EXCHANGEDB_CMD_GAUGER command
+ */
+ struct PERF_TALER_EXCHANGEDB_CMD_gaugerDetails
+ {
+ /**
+ * Label of the starting timestamp
+ */
+ const char *label_start;
+ unsigned int index_start;
+
+ /**
+ * Label of the ending timestamp
+ */
+ const char *label_stop;
+ unsigned int index_stop;
+
+ /**
+ * The category of the measurment
+ */
+ const char *category;
+
+ /**
+ * Description of the metric, used in Gauger
+ */
+ const char *description;
+
+ /**
+ * The name of the metric beeing used
+ */
+ const char *unit;
+
+ /**
+ * Constant the result needs to be divided by
+ * to get the result per unit
+ */
+ float divide;
+ } gauger;
+
+ /**
+ * Contains extra data requiered by the #PERF_TALER_EXCHANGEDB_CMD_SAVE_ARRAY command
+ */
+ struct PERF_TALER_EXCHANGEDB_CMD_saveArrayDetails
+ {
+ /**
+ * Number of items to save
+ */
+ unsigned int nb_saved;
+
+ /**
+ * Number of items already saved
+ */
+ unsigned int index;
+
+ /**
+ * Label of the loop it is attached to
+ */
+ const char *label_loop;
+ unsigned int index_loop;
+
+ /**
+ * Label of the command exposing the item
+ */
+ const char *label_save;
+ unsigned int index_save;
+
+ /**
+ * Array of data saved
+ */
+ struct PERF_TALER_EXCHANGEDB_Data *data_saved;
+
+ /**
+ * Type of the data that will be stored in @a data_saved, for
+ * 'static' type checking.
+ */
+ enum PERF_TALER_EXCHANGEDB_Type type_saved;
+
+ } save_array;
+
+ /**
+ * Extra data required for the #PERF_TALER_EXCHANGEDB_CMD_LOAD_ARRAY command
+ */
+ struct PERF_TALER_EXCHANGEDB_CMD_loadArrayDetails
+ {
+ /**
+ * The loop in which the command is located
+ */
+ const char *label_loop;
+ unsigned int index_loop;
+
+ /**
+ * Label of the command where the items were saved
+ */
+ const char *label_save;
+ unsigned int index_save;
+
+ /**
+ * A permutation array used to randomize the order the items are loaded in
+ */
+ unsigned int *permutation;
+ } load_array;
+
+ /**
+ * Contains data for the #PERF_TALER_EXCHANGEDB_CMD_LOAD_RANDOM command
+ */
+ struct PERF_TALER_EXCHANGEDB_CMD_loadRandomDetails
+ {
+ /**
+ * The label of the #PERF_TALER_EXCHANGEDB_CMD_SAVE_ARRAY the items will be extracted from
+ */
+ const char *label_save;
+ unsigned int index_save;
+ } load_random;
+
+ /**
+ * Extra data requiered by the #PERF_TALER_EXCHANGEDB_CMD_INSERT_DENOMINATION command
+ */
+ struct PERF_TALER_EXCHANGEDB_CMD_insertDenominationDetails
+ {
+ /**
+ * The label of the source of the denomination to insert
+ */
+ const char *label_denom;
+ unsigned int index_denom;
+ } insert_denomination;
+
+ /**
+ * Extra data requiered by the #PERF_TALER_EXCHANGEDB_CMD_GET_DENOMINATION command
+ */
+ struct PERF_TALER_EXCHANGEDB_CMD_getDenominationDetails
+ {
+ /**
+ * The label of the source of the denomination to check
+ */
+ const char *label_denom;
+ unsigned int index_denom;
+ } get_denomination;
+
+ /**
+ * Extra data requiered for the #PERF_TALER_EXCHANGEDB_CMD_INSERT_RESERVE command
+ */
+ struct PERF_TALER_EXCHANGEDB_CMD_insertReserveDetails
+ {
+ /**
+ * The label of the source of the reserve to insert
+ */
+ const char *label_reserve;
+ unsigned int index_reserve;
+ } insert_reserve;
+
+ /**
+ * Extra data requiered for the #PERF_TALER_EXCHANGEDB_CMD_GET_RESERVE command
+ */
+ struct PERF_TALER_EXCHANGEDB_CMD_getReserveDetails
+ {
+ /**
+ * The label of the source of the reserve to check
+ */
+ const char *label_reserve;
+ unsigned int index_reserve;
+ } get_reserve;
+
+ /**
+ * Extra data requiered for the #PERF_TALER_EXCHANGEDB_CMD_GET_RESERVE_HISTORY command
+ */
+ struct PERF_TALER_EXCHANGEDB_CMD_getReserveHistoryDetails
+ {
+ /**
+ * The label of the source of the reserve to check
+ */
+ const char *label_reserve;
+ unsigned int index_reserve;
+ } get_reserve_history;
+
+ /**
+ * Extra data related to the #PERF_TALER_EXCHANGEDB_CMD_CREATE_WITHDRAW command
+ */
+ struct PERF_TALER_EXCHANGEDB_CMD_createWithdrawDetails
+ {
+ /**
+ * label of the denomination key used to sign the coin
+ */
+ const char *label_dki;
+ unsigned int index_dki;
+
+ /**
+ * label of the reserve the money to exchange the coin comes from
+ */
+ const char *label_reserve;
+ unsigned int index_reserve;
+ } create_withdraw;
+
+ /**
+ * data requiered for the #PERF_TALER_EXCHANGEDB_CMD_INSERT_WITHDRAW
+ */
+ struct PERF_TALER_EXCHANGEDB_CMD_insertWithdrawDetails
+ {
+ /**
+ * label of the source for the coin information
+ */
+ const char *label_coin;
+ unsigned int index_coin;
+ } insert_withdraw;
+
+ /**
+ * data requiered for the #PERF_TALER_EXCHANGEDB_CMD_GET_WITHDRAW
+ */
+ struct PERF_TALER_EXCHANGEDB_CMD_getWithdraw
+ {
+ /**
+ * label of the source for the coin information
+ */
+ const char *label_coin;
+ unsigned int index_coin;
+ } get_withdraw;
+
+ /**
+ * Data requiered for the #PERF_TALER_EXCHANGEDB_CMD_GET_COIN_TRANSACTION command
+ */
+ struct PERF_TALER_EXCHANGEDB_CMD_getCoinTransactionDetails
+ {
+ /**
+ * The coin which history is checked
+ */
+ const char *label_coin;
+ unsigned int index_coin;
+ } get_coin_transaction;
+
+ /**
+ * Data used by the #PERF_TALER_EXCHANGEDB_CMD_CREATE_DEPOSIT command
+ */
+ struct PERF_TALER_EXCHANGEDB_CMD_createDepositDetails
+ {
+ /**
+ * Label of the source where the reserve used to create the coin is
+ */
+ const char *label_coin;
+ unsigned int index_coin;
+ } create_deposit;
+
+ /**
+ * Extra data requiered for the #PERF_TALER_EXCHANGEDB_CMD_INSERT_DEPOSIT command
+ */
+ struct PERF_TALER_EXCHANGEDB_CMD_insertDepositDetails
+ {
+ /**
+ * The label of the source of the deposit to check
+ */
+ const char *label_deposit;
+ unsigned int index_deposit;
+ } insert_deposit;
+
+ /**
+ * Extra data requiered for the #PERF_TALER_EXCHANGEDB_CMD_GET_DEPOSIT command
+ */
+ struct PERF_TALER_EXCHANGEDB_CMD_getDepositDetails
+ {
+ /**
+ * The label of the source of the deposit to check
+ */
+ const char *label_deposit;
+ unsigned int index_deposit;
+ } get_deposit;
+
+ /**
+ * Data requiered for the #PERF_TALER_EXCHANGEDB_CMD_GET_REFRESH_SESSION command
+ */
+ struct PERF_TALER_EXCHANGEDB_CMD_getRefreshSessionDetails
+ {
+ /**
+ * label of the source of the hash of the session
+ */
+ const char *label_hash;
+ unsigned int index_hash;
+ } get_refresh_session;
+
+ /**
+ * Data requiered for the #PERF_TALER_EXCHANGEDB_CMD_INSERT_REFRESH_MELT command
+ */
+ struct PERF_TALER_EXCHANGEDB_CMD_insertRefreshMeltDetails
+ {
+ /**
+ * The label of the hash of the refresh session
+ */
+ const char *label_hash;
+ unsigned int index_hash;
+
+ /**
+ * The label of the coin to melt
+ */
+ const char *label_coin;
+ unsigned int index_coin;
+ } insert_refresh_melt;
+
+ /**
+ * Data requiered for the #PERF_TALER_EXCHANGEDB_CMD_GET_REFRESH_MELT command
+ */
+ struct PERF_TALER_EXCHANGEDB_CMD_getRefreshMeltDetails
+ {
+ /**
+ * The label of the hash of the session
+ */
+ const char *label_hash;
+ unsigned int index_hash;
+ } get_refresh_melt;
+
+ /**
+ * Data requiered for the #PERF_TALER_EXCHANGEDB_CMD_INSERT_REFRESH_ORDER command
+ */
+ struct PERF_TALER_EXCHANGEDB_CMD_insertRefreshOrderDetails
+ {
+ /**
+ * The refresh session hash
+ */
+ const char *label_hash;
+ unsigned int index_hash;
+
+ /**
+ * The new coin denomination
+ */
+ const char *label_denom;
+ unsigned int index_denom;
+ } insert_refresh_order;
+
+ /**
+ * Data requiered for the #PERF_TALER_EXCHANGEDB_CMD_GET_REFRESH_ORDER command
+ */
+ struct PERF_TALER_EXCHANGEDB_CMD_getRefreshOrderDetails
+ {
+ /**
+ * The session hash
+ */
+ const char *label_hash;
+ unsigned int index_hash;
+
+ } get_refresh_order;
+
+ /**
+ * Data requiered for the #PERF_TALER_EXCHANGEDB_CMD_INSERT_REFRESH_COMMIT_COIN command
+ */
+ struct PERF_TALER_EXCHANGEDB_CMD_insertRefreshCommitCoinDetails
+ {
+ /**
+ * The refresh session hash
+ */
+ const char *label_hash;
+ unsigned int index_hash;
+
+ } insert_refresh_commit_coin;
+
+ /**
+ * Data requiered for the #PERF_TALER_EXCHANGEDB_CMD_GET_REFRESH_COMMIT_COIN command
+ */
+ struct PERF_TALER_EXCHANGEDB_CMD_getRefreshCommitCoinDetails
+ {
+ /**
+ * The refresh session hash
+ */
+ const char *label_hash;
+ unsigned int index_hash;
+
+ } get_refresh_commit_coin;
+
+ /**
+ * Data requiered for the #PERF_TALER_EXCHANGEDB_CMD_INSERT_REFRESH_COMMIT_LINK command
+ */
+ struct PERF_TALER_EXCHANGEDB_CMD_insertRefreshCommitLinkDetails
+ {
+ /**
+ * The refresh session hash
+ */
+ const char *label_hash;
+ unsigned int index_hash;
+
+ } insert_refresh_commit_link;
+
+ /**
+ * Data requiered by the #PERF_TALER_EXCHANGEDB_CMD_GET_REFRESH_COMMIT_LINK command
+ */
+ struct PERF_TALER_EXCHANGEDB_CMD_getRefreshCommitLinkDetails
+ {
+ /**
+ * The refresh session hash
+ */
+ const char *label_hash;
+ unsigned int index_hash;
+ } get_refresh_commit_link;
+
+ /**
+ * Data requiered for the #PERF_TALER_EXCHANGEDB_CMD_GET_MELT_COMMITMENT command
+ */
+ struct PERF_TALER_EXCHANGEDB_CMD_getMeltCommitmentDaetails
+ {
+ /**
+ * The refresh session hash
+ */
+ const char *label_hash;
+ unsigned int index_hash;
+ } get_melt_commitment;
+
+ /**
+ * Data requiered by the #PERF_TALER_EXCHANGEDB_CMD_INSERT_REFRESH_OUT command
+ */
+ struct PERF_TALER_EXCHANGEDB_CMD_insertRefreshOutDetails
+ {
+ /**
+ * The refresh session hash
+ */
+ const char *label_hash;
+ unsigned int index_hash;
+ } insert_refresh_out;
+
+ /**
+ * Data requiered by the #PERF_TALER_EXCHANGEDB_CMD_GET_LINK_DATA_LIST command
+ */
+ struct PERF_TALER_EXCHANGEDB_CMD_getLinkDataListDetails
+ {
+ /**
+ * The refresh session hash
+ */
+ const char *label_hash;
+ unsigned int index_hash;
+ } get_link_data_list;
+
+ /**
+ * Data requiered by the #PERF_TALER_EXCHANGEDB_CMD_GET_TRANSFER command
+ */
+ struct PERF_TALER_EXCHANGEDB_CMD_getTransferDetails
+ {
+ /**
+ * The refresh session hash
+ */
+ const char *label_hash;
+ unsigned int index_hash;
+ } get_transfer;
+
+};
+
+
+/**
+ * Command to be interpreted.
+ */
+struct PERF_TALER_EXCHANGEDB_Cmd
+{
+ /**
+ * Type of the command
+ */
+ enum PERF_TALER_EXCHANGEDB_CMD_Name command;
+
+ /**
+ * Label to refer to the command
+ */
+ const char *label;
+
+ /**
+ * Command specific data
+ */
+ union PERF_TALER_EXCHANGEDB_CMD_Details details;
+
+ /**
+ * Data easily accessible
+ */
+ struct PERF_TALER_EXCHANGEDB_Data exposed;
+};
+
+
+/**
+ * Run a benchmark
+ *
+ * @param benchmark_name the name of the benchmark, displayed in the logs
+ * @param configuration_file path to the taler configuration file to use
+ * @param init the commands to use for the database initialisation,
+ * if #NULL the standard initialization is used
+ * @param benchmark the commands for the benchmark
+ * @return GNUNET_OK upon success; GNUNET_SYSERR upon failure
+ */
+int
+PERF_TALER_EXCHANGEDB_run_benchmark (const char *benchmark_name,
+ const char *configuration_file,
+ struct PERF_TALER_EXCHANGEDB_Cmd *init,
+ struct PERF_TALER_EXCHANGEDB_Cmd *benchmark);
+
+
+/**
+ * Runs the command array @a cmd
+ * using @a db_plugin to connect to the database
+ *
+ * @param db_plugin the connection to the database
+ * @param cmd the commands to run
+ */
+int
+PERF_TALER_EXCHANGEDB_interpret(
+ struct TALER_EXCHANGEDB_Plugin *db_plugin,
+ struct PERF_TALER_EXCHANGEDB_Cmd cmd[]);
+
+
+/**
+ * Check if the given command array is syntaxicly correct
+ * This will check if the label are corrects but will not check if
+ * they are pointing to an apropriate command.
+ *
+ * @param cmd the command array to check
+ * @return #GNUNET_OK is @a cmd is correct; #GNUNET_SYSERR if it is'nt
+ */
+int
+PERF_TALER_EXCHANGEDB_check (const struct PERF_TALER_EXCHANGEDB_Cmd *cmd);
+
+#endif
diff --git a/src/exchangedb/perf_taler_exchangedb_values.h b/src/exchangedb/perf_taler_exchangedb_values.h
new file mode 100644
index 000000000..c3b50fea2
--- /dev/null
+++ b/src/exchangedb/perf_taler_exchangedb_values.h
@@ -0,0 +1,25 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015 GNUnet e.V.
+
+ 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 exchangedb/perf_taler_exchangedb_values.h
+ * @brief Values for tweaking the performance analysis
+ * @author Nicolas Fournier
+ */
+#ifndef __PERF_TALER_EXCHANGEDB__VALUES_H__
+#define __PERF_TALER_EXCHANGEDB__VALUES_H__
+
+
+#endif
diff --git a/src/exchangedb/plugin_exchangedb_common.c b/src/exchangedb/plugin_exchangedb_common.c
new file mode 100644
index 000000000..c8e689cfd
--- /dev/null
+++ b/src/exchangedb/plugin_exchangedb_common.c
@@ -0,0 +1,162 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2015 GNUnet e.V.
+
+ 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 exchangedb/plugin_exchangedb_common.c
+ * @brief Functions shared across plugins, this file is meant to be
+ * included 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 TALER_EXCHANGEDB_ReserveHistory *rh)
+{
+ struct TALER_EXCHANGEDB_BankTransfer *bt;
+ struct TALER_EXCHANGEDB_CollectableBlindcoin *cbc;
+ struct TALER_EXCHANGEDB_ReserveHistory *backref;
+
+ while (NULL != rh)
+ {
+ switch(rh->type)
+ {
+ case TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE:
+ bt = rh->details.bank;
+ if (NULL != bt->wire)
+ json_decref (bt->wire);
+ GNUNET_free (bt);
+ break;
+ case TALER_EXCHANGEDB_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 TALER_EXCHANGEDB_LinkDataList *ldl)
+{
+ struct TALER_EXCHANGEDB_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_EXCHANGEDB_TransactionList *list)
+{
+ struct TALER_EXCHANGEDB_TransactionList *next;
+
+ while (NULL != list)
+ {
+ next = list->next;
+
+ switch (list->type)
+ {
+ case TALER_EXCHANGEDB_TT_DEPOSIT:
+ json_decref (list->details.deposit->wire);
+ GNUNET_CRYPTO_rsa_public_key_free (list->details.deposit->coin.denom_pub.rsa_public_key);
+ GNUNET_CRYPTO_rsa_signature_free (list->details.deposit->coin.denom_sig.rsa_signature);
+ GNUNET_free (list->details.deposit);
+ break;
+ case TALER_EXCHANGEDB_TT_REFRESH_MELT:
+ GNUNET_free (list->details.melt);
+ break;
+ }
+ GNUNET_free (list);
+ list = next;
+ }
+}
+
+
+/**
+ * Free melt commitment data.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state (unused)
+ * @param mc data structure to free
+ */
+static void
+common_free_melt_commitment (void *cls,
+ struct TALER_EXCHANGEDB_MeltCommitment *mc)
+{
+ unsigned int i;
+ unsigned int k;
+
+ if (NULL != mc->melts)
+ {
+ for (i=0;i<mc->num_oldcoins;i++)
+ {
+ GNUNET_CRYPTO_rsa_signature_free (mc->melts[i].coin.denom_sig.rsa_signature);
+ GNUNET_CRYPTO_rsa_public_key_free (mc->melts[i].coin.denom_pub.rsa_public_key);
+ }
+ GNUNET_free (mc->melts);
+ }
+ if (NULL != mc->denom_pubs)
+ {
+ for (i=0;i<mc->num_newcoins;i++)
+ if (NULL != mc->denom_pubs[i].rsa_public_key)
+ GNUNET_CRYPTO_rsa_public_key_free (mc->denom_pubs[i].rsa_public_key);
+ GNUNET_free (mc->denom_pubs);
+ }
+ for (k=0;k<TALER_CNC_KAPPA;k++)
+ {
+ if (NULL != mc->commit_coins[k])
+ {
+ for (i=0;i<mc->num_newcoins;i++)
+ {
+ GNUNET_free (mc->commit_coins[k][i].refresh_link);
+ GNUNET_free (mc->commit_coins[k][i].coin_ev);
+ }
+ GNUNET_free (mc->commit_coins[k]);
+ }
+ GNUNET_free_non_null (mc->commit_links[k]);
+ }
+ GNUNET_free (mc);
+}
+
+/* end of plugin_exchangedb_common.c */
diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c
new file mode 100644
index 000000000..092aebc48
--- /dev/null
+++ b/src/exchangedb/plugin_exchangedb_postgres.c
@@ -0,0 +1,4295 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015, 2016 GNUnet e.V.
+
+ 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_exchangedb_postgres.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Florian Dold
+ * @author Christian Grothoff
+ * @author Sree Harsha Totakura
+ */
+#include "platform.h"
+#include "taler_pq_lib.h"
+#include "taler_exchangedb_plugin.h"
+#include <pthread.h>
+#include <libpq-fe.h>
+
+#include "plugin_exchangedb_common.c"
+
+/**
+ * For testing / experiments, we set the Postgres schema to
+ * #TALER_TEMP_SCHEMA_NAME so we can easily purge everything
+ * associated with a test. We *also* should use the database
+ * "talercheck" instead of "taler" for testing, but we're doing
+ * both: better safe than sorry.
+ */
+#define TALER_TEMP_SCHEMA_NAME "taler_temporary"
+
+/**
+ * Log a query error.
+ *
+ * @param result PQ result object of the query that failed
+ */
+#define QUERY_ERR(result) \
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Query failed at %s:%u: %s\n", __FILE__, __LINE__, PQresultErrorMessage (result))
+
+
+/**
+ * Log a really unexpected PQ error.
+ *
+ * @param result PQ result object of the PQ operation that failed
+ */
+#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. Logs the current line number
+ * and jumps to the "EXITIF_exit" label.
+ *
+ * @param cond condition that must be TRUE to exit with an error
+ */
+#define EXITIF(cond) \
+ do { \
+ if (cond) { GNUNET_break (0); goto EXITIF_exit; } \
+ } while (0)
+
+
+/**
+ * Execute an SQL statement and log errors on failure. Must be
+ * run in a function that has an "SQLEXEC_fail" label to jump
+ * to in case the SQL statement failed.
+ *
+ * @param conn database connection
+ * @param sql SQL statement to run
+ */
+#define SQLEXEC_(conn, sql) \
+ do { \
+ PGresult *result = PQexec (conn, sql); \
+ if (PGRES_COMMAND_OK != PQresultStatus (result)) \
+ { \
+ BREAK_DB_ERR (result); \
+ PQclear (result); \
+ goto SQLEXEC_fail; \
+ } \
+ PQclear (result); \
+ } while (0)
+
+
+/**
+ * Run an SQL statement, ignoring errors and clearing the result.
+ *
+ * @param conn database connection
+ * @param sql SQL statement to run
+ */
+#define SQLEXEC_IGNORE_ERROR_(conn, sql) \
+ do { \
+ PGresult *result = PQexec (conn, sql); \
+ PQclear (result); \
+ } while (0)
+
+
+/**
+ * Handle for a database session (per-thread, for transactions).
+ */
+struct TALER_EXCHANGEDB_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)
+{
+ SQLEXEC_(db,
+ "CREATE SCHEMA IF NOT EXISTS " TALER_TEMP_SCHEMA_NAME ";"
+ "SET search_path to " TALER_TEMP_SCHEMA_NAME ";");
+ 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
+ * @param session database session to use
+ * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
+ */
+static int
+postgres_drop_temporary (void *cls,
+ struct TALER_EXCHANGEDB_Session *session)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Dropping temporary tables\n");
+ SQLEXEC_ (session->conn,
+ "DROP SCHEMA " TALER_TEMP_SCHEMA_NAME " CASCADE;");
+ return GNUNET_OK;
+ SQLEXEC_fail:
+ return GNUNET_SYSERR;
+}
+
+
+/**
+ * Function called by libpq whenever it wants to log something.
+ * We already log whenever we care, so this function does nothing
+ * and merely exists to silence the libpq logging.
+ *
+ * @param arg NULL
+ * @param res information about some libpq event
+ */
+static void
+pq_notice_receiver_cb (void *arg,
+ const PGresult *res)
+{
+ /* do nothing, intentionally */
+}
+
+
+/**
+ * Function called by libpq whenever it wants to log something.
+ * We log those using the Taler logger.
+ *
+ * @param arg NULL
+ * @param message information about some libpq event
+ */
+static void
+pq_notice_processor_cb (void *arg,
+ const char *message)
+{
+ GNUNET_log_from (GNUNET_ERROR_TYPE_INFO,
+ "pq",
+ "%s",
+ message);
+}
+
+
+/**
+ * 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;
+ PGconn *conn;
+
+ conn = PQconnectdb (pc->connection_cfg_str);
+ if (CONNECTION_OK != PQstatus (conn))
+ {
+ TALER_LOG_ERROR ("Database connection failed: %s\n",
+ PQerrorMessage (conn));
+ PQfinish (conn);
+ return GNUNET_SYSERR;
+ }
+ PQsetNoticeReceiver (conn,
+ &pq_notice_receiver_cb,
+ NULL);
+ PQsetNoticeProcessor (conn,
+ &pq_notice_processor_cb,
+ NULL);
+ if ( (GNUNET_YES == temporary) &&
+ (GNUNET_SYSERR == set_temporary_schema (conn)))
+ {
+ PQfinish (conn);
+ return GNUNET_SYSERR;
+ }
+#define SQLEXEC(sql) SQLEXEC_(conn, sql);
+#define SQLEXEC_INDEX(sql) SQLEXEC_IGNORE_ERROR_(conn, sql);
+ /* Denomination table for holding the publicly available information of
+ denominations keys. The denominations are to be referred to by using
+ foreign keys. The denominations are deleted by a housekeeping tool;
+ hence, do not use `ON DELETE CASCADE' on these rows in the tables
+ referencing these rows */
+ SQLEXEC ("CREATE TABLE IF NOT EXISTS denominations"
+ "(pub BYTEA PRIMARY KEY"
+ ",master_pub BYTEA NOT NULL CHECK (LENGTH(master_pub)=32)"
+ ",master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)"
+ ",valid_from INT8 NOT NULL"
+ ",expire_withdraw INT8 NOT NULL"
+ ",expire_spend INT8 NOT NULL"
+ ",expire_legal INT8 NOT NULL"
+ ",coin_val INT8 NOT NULL" /* value of this denom */
+ ",coin_frac INT4 NOT NULL" /* fractional value of this denom */
+ ",coin_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL" /* assuming same currency for fees */
+ ",fee_withdraw_val INT8 NOT NULL"
+ ",fee_withdraw_frac INT4 NOT NULL"
+ ",fee_withdraw_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL"
+ ",fee_deposit_val INT8 NOT NULL"
+ ",fee_deposit_frac INT4 NOT NULL"
+ ",fee_deposit_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL"
+ ",fee_refresh_val INT8 NOT NULL"
+ ",fee_refresh_frac INT4 NOT NULL"
+ ",fee_refresh_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL"
+ ")");
+ /* reserves table is for summarization of a reserve. It is updated when new
+ funds are added and existing funds are withdrawn. The 'expiration_date'
+ can be used to eventually get rid of reserves that have not been used
+ for a very long time (either by refunding the owner or by greedily
+ grabbing the money, depending on the Exchange's terms of service) */
+ SQLEXEC ("CREATE TABLE IF NOT EXISTS reserves"
+ "(reserve_pub BYTEA PRIMARY KEY"
+ ",current_balance_val INT8 NOT NULL"
+ ",current_balance_frac INT4 NOT NULL"
+ ",current_balance_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL"
+ ",expiration_date INT8 NOT NULL"
+ ")");
+ /* index on reserves table */
+ SQLEXEC_INDEX ("CREATE INDEX reserves_reserve_pub_index ON "
+ "reserves (reserve_pub)");
+ /* reserves_in table collects the transactions which transfer funds
+ into the reserve. 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_val INT8 NOT NULL"
+ ",balance_frac INT4 NOT NULL"
+ ",balance_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL"
+ ",details TEXT NOT NULL "
+ ",execution_date INT8 NOT NULL"
+ ",PRIMARY KEY (reserve_pub,details)"
+ ");");
+ /* Create indices on reserves_in */
+ SQLEXEC_INDEX ("CREATE INDEX reserves_in_reserve_pub_index"
+ " ON reserves_in (reserve_pub);");
+ SQLEXEC_INDEX ("CREATE INDEX reserves_in_reserve_pub_details_index"
+ " ON reserves_in (reserve_pub,details);");
+ SQLEXEC_INDEX ("CREATE INDEX execution_index"
+ " ON reserves_in (execution_date);");
+ /* Table with the withdraw operations that have been performed on a reserve.
+ The 'h_blind_ev' is the hash of the blinded coin. It serves as a primary
+ key, as (broken) clients that use a non-random coin and blinding factor
+ should fail to even withdraw, as otherwise the coins will fail to deposit
+ (as they really must be unique). */
+ SQLEXEC ("CREATE TABLE IF NOT EXISTS reserves_out"
+ "(h_blind_ev BYTEA PRIMARY KEY"
+ ",denom_pub BYTEA NOT NULL REFERENCES denominations (pub)"
+ ",denom_sig BYTEA NOT NULL"
+ ",reserve_pub BYTEA NOT NULL CHECK (LENGTH(reserve_pub)=32) REFERENCES reserves (reserve_pub) ON DELETE CASCADE"
+ ",reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64)"
+ ",execution_date INT8 NOT NULL"
+ ",amount_with_fee_val INT8 NOT NULL"
+ ",amount_with_fee_frac INT4 NOT NULL"
+ ",amount_with_fee_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL"
+ ",withdraw_fee_val INT8 NOT NULL"
+ ",withdraw_fee_frac INT4 NOT NULL"
+ ",withdraw_fee_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL"
+ ");");
+ /* Index blindcoins(reserve_pub) for get_reserves_out statement */
+ SQLEXEC_INDEX ("CREATE INDEX reserves_out_reserve_pub_index ON"
+ " reserves_out (reserve_pub)");
+ SQLEXEC_INDEX ("CREATE INDEX reserves_out_h_blind_ev_index ON "
+ "reserves_out (h_blind_ev)");
+ /* Table with coins that have been (partially) spent, used to track
+ coin information only once. */
+ SQLEXEC("CREATE TABLE IF NOT EXISTS known_coins "
+ "(coin_pub BYTEA NOT NULL PRIMARY KEY"
+ ",denom_pub BYTEA NOT NULL REFERENCES denominations (pub)"
+ ",denom_sig BYTEA NOT NULL"
+ ")");
+ /**
+ * The DB will show negative values for some values of the following fields as
+ * we use them as 16 bit unsigned integers
+ * @a num_oldcoins
+ * @a num_newcoins
+ * Do not do arithmetic in SQL on these fields.
+ * NOTE: maybe we should instead forbid values >= 2^15 categorically?
+ */
+ SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_sessions "
+ "(session_hash BYTEA PRIMARY KEY CHECK (LENGTH(session_hash)=64)"
+ ",num_oldcoins INT2 NOT NULL"
+ ",num_newcoins INT2 NOT NULL"
+ ",noreveal_index INT2 NOT NULL"
+ ")");
+ /* Table with coins that have been melted. Gives the coin's public
+ key (coin_pub), the melting session, the index of this coin in that
+ session, the signature affirming the melting and the amount that
+ this coin contributed to the melting session.
+ */
+ SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_melts "
+ "(coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub)"
+ ",session_hash BYTEA NOT NULL REFERENCES refresh_sessions (session_hash)"
+ ",oldcoin_index INT2 NOT NULL"
+ ",coin_sig BYTEA NOT NULL CHECK(LENGTH(coin_sig)=64)"
+ ",amount_with_fee_val INT8 NOT NULL"
+ ",amount_with_fee_frac INT4 NOT NULL"
+ ",amount_with_fee_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL"
+ ",melt_fee_val INT8 NOT NULL"
+ ",melt_fee_frac INT4 NOT NULL"
+ ",melt_fee_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL"
+ ",PRIMARY KEY (session_hash, oldcoin_index)" /* a coin can be used only
+ once in a refresh session */
+ ") ");
+ /* Table with information about the desired denominations to be created
+ during a refresh operation; contains the denomination key for each
+ of the coins (for a given refresh session) */
+ SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_order "
+ "(session_hash BYTEA NOT NULL CHECK (LENGTH(session_hash)=64) REFERENCES refresh_sessions (session_hash)"
+ ",newcoin_index INT2 NOT NULL "
+ ",denom_pub BYTEA NOT NULL REFERENCES denominations (pub)"
+ ",PRIMARY KEY (session_hash, newcoin_index)"
+ ")");
+
+ /* Table with the commitments for a refresh operation; includes
+ the session_hash for which this is the link information, the
+ oldcoin index and the cut-and-choose index (from 0 to #TALER_CNC_KAPPA-1),
+ as well as the actual link data (the transfer public key and the encrypted
+ link secret).
+ NOTE: We might want to simplify this and not have the oldcoin_index
+ and instead store all link secrets, one after the other, in one big BYTEA.
+ (#3814) */
+ SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_commit_link "
+ "(session_hash BYTEA NOT NULL REFERENCES refresh_sessions (session_hash)"
+ ",transfer_pub BYTEA NOT NULL CHECK(LENGTH(transfer_pub)=32)"
+ ",link_secret_enc BYTEA NOT NULL"
+ ",oldcoin_index INT2 NOT NULL"
+ ",cnc_index INT2 NOT NULL"
+ ")");
+ /* Table with the commitments for the new coins that are to be created
+ during a melting session. Includes the session, the cut-and-choose
+ index and the index of the new coin, and the envelope of the new
+ coin to be signed, as well as the encrypted information about the
+ private key and the blinding factor for the coin (for verification
+ in case this cnc_index is chosen to be revealed)
+
+ NOTE: We might want to simplify this and not have the
+ newcoin_index and instead store all coin_evs and
+ link_vector_encs, one after the other, in two big BYTEAs.
+ (#3815) */
+ SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_commit_coin "
+ "(session_hash BYTEA NOT NULL REFERENCES refresh_sessions (session_hash) "
+ ",cnc_index INT2 NOT NULL"
+ ",newcoin_index INT2 NOT NULL"
+ ",link_vector_enc BYTEA NOT NULL"
+ ",coin_ev BYTEA NOT NULL"
+ ")");
+ /* Table with the signatures over coins generated during a refresh
+ operation. Needed to answer /refresh/link queries later. Stores
+ the coin signatures under the respective session hash and index. */
+ SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_out "
+ "(session_hash BYTEA NOT NULL CHECK(LENGTH(session_hash)=64) REFERENCES refresh_sessions (session_hash) "
+ ",newcoin_index INT2 NOT NULL"
+ ",ev_sig BYTEA NOT NULL"
+ ")");
+ /* This table contains the wire transfers the exchange is supposed to
+ execute to transmit funds to the merchants (and manage refunds). */
+ SQLEXEC("CREATE TABLE IF NOT EXISTS deposits "
+ "(serial_id BIGSERIAL PRIMARY KEY"
+ ",coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)"
+ ",denom_pub BYTEA NOT NULL REFERENCES denominations (pub)"
+ ",denom_sig BYTEA NOT NULL"
+ ",transaction_id INT8 NOT NULL"
+ ",amount_with_fee_val INT8 NOT NULL"
+ ",amount_with_fee_frac INT4 NOT NULL"
+ ",amount_with_fee_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL"
+ ",deposit_fee_val INT8 NOT NULL"
+ ",deposit_fee_frac INT4 NOT NULL"
+ ",deposit_fee_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL"
+ ",timestamp INT8 NOT NULL"
+ ",refund_deadline INT8 NOT NULL"
+ ",wire_deadline INT8 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"
+ ",tiny BOOLEAN NOT NULL DEFAULT false"
+ ",done BOOLEAN NOT NULL DEFAULT false"
+ ")");
+ /* Index for get_deposit statement on coin_pub, transaction_id and merchant_pub */
+ SQLEXEC_INDEX("CREATE INDEX deposits_coin_pub_index "
+ "ON deposits(coin_pub, transaction_id, merchant_pub)");
+ /* Table for the tracking API, mapping from wire transfer identifiers
+ to transactions and back */
+ SQLEXEC("CREATE TABLE IF NOT EXISTS aggregation_tracking "
+ "(h_contract BYTEA CHECK (LENGTH(h_contract)=64)"
+ ",h_wire BYTEA CHECK (LENGTH(h_wire)=64)"
+ ",coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)"
+ ",merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)"
+ ",transaction_id INT8 NOT NULL"
+ ",wtid_raw BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=" TALER_WIRE_TRANSFER_IDENTIFIER_LEN_STR ")"
+ ",execution_time INT8 NOT NULL"
+ ",coin_amount_val INT8 NOT NULL"
+ ",coin_amount_frac INT4 NOT NULL"
+ ",coin_amount_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL"
+ ",coin_fee_val INT8 NOT NULL"
+ ",coin_fee_frac INT4 NOT NULL"
+ ",coin_fee_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL"
+ ")");
+ /* Index for lookup_transactions statement on wtid */
+ SQLEXEC_INDEX("CREATE INDEX aggregation_tracking_wtid_index "
+ "ON aggregation_tracking(wtid_raw)");
+ /* Index for lookup_deposit_wtid statement */
+ SQLEXEC_INDEX("CREATE INDEX aggregation_tracking_deposit_index "
+ "ON aggregation_tracking(coin_pub,h_contract,h_wire,transaction_id,merchant_pub)");
+
+ /* This table contains the pre-commit data for
+ wire transfers the exchange is about to execute. */
+ SQLEXEC("CREATE TABLE IF NOT EXISTS prewire "
+ "(serial_id BIGSERIAL PRIMARY KEY"
+ ",type TEXT NOT NULL"
+ ",finished BOOLEAN NOT NULL DEFAULT false"
+ ",buf BYTEA NOT NULL"
+ ")");
+ /* Index for prepare_data_iterate statement */
+ SQLEXEC_INDEX("CREATE INDEX prepare_iteration_index "
+ "ON prewire(type,finished)");
+
+
+#undef SQLEXEC
+#undef SQLEXEC_INDEX
+
+ 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);
+
+ /* Used in #postgres_insert_denomination_info() */
+ PREPARE ("denomination_insert",
+ "INSERT INTO denominations "
+ "(pub"
+ ",master_pub"
+ ",master_sig"
+ ",valid_from"
+ ",expire_withdraw"
+ ",expire_spend"
+ ",expire_legal"
+ ",coin_val" /* value of this denom */
+ ",coin_frac" /* fractional value of this denom */
+ ",coin_curr" /* assuming same currency for fees */
+ ",fee_withdraw_val"
+ ",fee_withdraw_frac"
+ ",fee_withdraw_curr" /* must match coin_curr */
+ ",fee_deposit_val"
+ ",fee_deposit_frac"
+ ",fee_deposit_curr" /* must match coin_curr */
+ ",fee_refresh_val"
+ ",fee_refresh_frac"
+ ",fee_refresh_curr" /* must match coin_curr */
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10,"
+ " $11, $12, $13, $14, $15, $16, $17, $18, $19);",
+ 19, NULL);
+
+ /* Used in #postgres_get_denomination_info() */
+ PREPARE ("denomination_get",
+ "SELECT"
+ " master_pub"
+ ",master_sig"
+ ",valid_from"
+ ",expire_withdraw"
+ ",expire_spend"
+ ",expire_legal"
+ ",coin_val" /* value of this denom */
+ ",coin_frac" /* fractional value of this denom */
+ ",coin_curr" /* assuming same currency for fees */
+ ",fee_withdraw_val"
+ ",fee_withdraw_frac"
+ ",fee_withdraw_curr" /* must match coin_curr */
+ ",fee_deposit_val"
+ ",fee_deposit_frac"
+ ",fee_deposit_curr" /* must match coin_curr */
+ ",fee_refresh_val"
+ ",fee_refresh_frac"
+ ",fee_refresh_curr" /* must match coin_curr */
+ " FROM denominations"
+ " WHERE pub=$1;",
+ 1, NULL);
+
+ /* Used in #postgres_reserve_get() */
+ PREPARE ("reserve_get",
+ "SELECT"
+ " current_balance_val"
+ ",current_balance_frac"
+ ",current_balance_curr"
+ ",expiration_date"
+ " FROM reserves"
+ " WHERE reserve_pub=$1"
+ " LIMIT 1;",
+ 1, NULL);
+
+ /* Used in #postgres_reserves_in_insert() when the reserve is new */
+ PREPARE ("reserve_create",
+ "INSERT INTO reserves "
+ "(reserve_pub"
+ ",current_balance_val"
+ ",current_balance_frac"
+ ",current_balance_curr"
+ ",expiration_date"
+ ") VALUES "
+ "($1, $2, $3, $4, $5);",
+ 5, NULL);
+
+ /* Used in #postgres_reserves_update() when the reserve is updated */
+ PREPARE ("reserve_update",
+ "UPDATE reserves"
+ " SET"
+ " expiration_date=$1 "
+ ",current_balance_val=$2 "
+ ",current_balance_frac=$3 "
+ "WHERE current_balance_curr=$4 AND reserve_pub=$5",
+ 5, NULL);
+
+ /* Used in #postgres_reserves_in_insert() to store transaction details */
+ PREPARE ("reserves_in_add_transaction",
+ "INSERT INTO reserves_in "
+ "(reserve_pub"
+ ",balance_val"
+ ",balance_frac"
+ ",balance_curr"
+ ",details"
+ ",execution_date"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6);",
+ 6, NULL);
+
+ /* Used in #postgres_get_reserve_history() to obtain inbound transactions
+ for a reserve */
+ PREPARE ("reserves_in_get_transactions",
+ "SELECT"
+ " balance_val"
+ ",balance_frac"
+ ",balance_curr"
+ ",execution_date"
+ ",details"
+ " FROM reserves_in"
+ " WHERE reserve_pub=$1",
+ 1, NULL);
+
+ /* Used in #postgres_insert_withdraw_info() to store
+ the signature of a blinded coin with the blinded coin's
+ details before returning it during /reserve/withdraw. We store
+ the coin's denomination information (public key, signature)
+ and the blinded message as well as the reserve that the coin
+ is being withdrawn from and the signature of the message
+ authorizing the withdrawal. */
+ PREPARE ("insert_withdraw_info",
+ "INSERT INTO reserves_out "
+ "(h_blind_ev"
+ ",denom_pub"
+ ",denom_sig"
+ ",reserve_pub"
+ ",reserve_sig"
+ ",execution_date"
+ ",amount_with_fee_val"
+ ",amount_with_fee_frac"
+ ",amount_with_fee_curr"
+ ",withdraw_fee_val"
+ ",withdraw_fee_frac"
+ ",withdraw_fee_curr"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12);",
+ 12, NULL);
+
+ /* Used in #postgres_get_withdraw_info() to
+ locate the response for a /reserve/withdraw request
+ using the hash of the blinded message. Used to
+ make sure /reserve/withdraw requests are idempotent. */
+ PREPARE ("get_withdraw_info",
+ "SELECT"
+ " denom_pub"
+ ",denom_sig"
+ ",reserve_sig"
+ ",reserve_pub"
+ ",execution_date"
+ ",amount_with_fee_val"
+ ",amount_with_fee_frac"
+ ",amount_with_fee_curr"
+ ",withdraw_fee_val"
+ ",withdraw_fee_frac"
+ ",withdraw_fee_curr"
+ " FROM reserves_out"
+ " WHERE h_blind_ev=$1",
+ 1, NULL);
+
+ /* Used during #postgres_get_reserve_history() to
+ obtain all of the /reserve/withdraw operations that
+ have been performed on a given reserve. (i.e. to
+ demonstrate double-spending) */
+ PREPARE ("get_reserves_out",
+ "SELECT"
+ " h_blind_ev"
+ ",denom_pub"
+ ",denom_sig"
+ ",reserve_sig"
+ ",execution_date"
+ ",amount_with_fee_val"
+ ",amount_with_fee_frac"
+ ",amount_with_fee_curr"
+ ",withdraw_fee_val"
+ ",withdraw_fee_frac"
+ ",withdraw_fee_curr"
+ " FROM reserves_out"
+ " WHERE reserve_pub=$1;",
+ 1, NULL);
+
+ /* Used in #postgres_get_refresh_session() to fetch
+ high-level information about a refresh session */
+ PREPARE ("get_refresh_session",
+ "SELECT"
+ " num_oldcoins"
+ ",num_newcoins"
+ ",noreveal_index"
+ " FROM refresh_sessions "
+ " WHERE session_hash=$1 ",
+ 1, NULL);
+
+ /* Used in #postgres_create_refresh_session() to store
+ high-level information about a refresh session */
+ PREPARE ("insert_refresh_session",
+ "INSERT INTO refresh_sessions "
+ "(session_hash "
+ ",num_oldcoins "
+ ",num_newcoins "
+ ",noreveal_index "
+ ") VALUES "
+ "($1, $2, $3, $4);",
+ 4, NULL);
+
+ /* Used in #postgres_get_known_coin() to fetch
+ the denomination public key and signature for
+ a coin known to the exchange. */
+ PREPARE ("get_known_coin",
+ "SELECT"
+ " denom_pub"
+ ",denom_sig"
+ " FROM known_coins"
+ " WHERE coin_pub=$1",
+ 1, NULL);
+
+ /* Used in #postgres_insert_known_coin() to store
+ the denomination public key and signature for
+ a coin known to the exchange. */
+ PREPARE ("insert_known_coin",
+ "INSERT INTO known_coins "
+ "(coin_pub"
+ ",denom_pub"
+ ",denom_sig"
+ ") VALUES "
+ "($1,$2,$3);",
+ 3, NULL);
+
+ /* Store information about the desired denominations for a
+ refresh operation, used in #postgres_insert_refresh_order() */
+ PREPARE ("insert_refresh_order",
+ "INSERT INTO refresh_order "
+ "(newcoin_index "
+ ",session_hash "
+ ",denom_pub "
+ ") VALUES "
+ "($1, $2, $3);",
+ 3, NULL);
+
+ /* Obtain information about the desired denominations for a
+ refresh operation, used in #postgres_get_refresh_order() */
+ PREPARE ("get_refresh_order",
+ "SELECT denom_pub"
+ " FROM refresh_order"
+ " WHERE session_hash=$1 AND newcoin_index=$2",
+ 2, NULL);
+
+ /* Used in #postgres_insert_refresh_melt to store information
+ about melted coins */
+ PREPARE ("insert_refresh_melt",
+ "INSERT INTO refresh_melts "
+ "(coin_pub "
+ ",session_hash"
+ ",oldcoin_index "
+ ",coin_sig "
+ ",amount_with_fee_val "
+ ",amount_with_fee_frac "
+ ",amount_with_fee_curr "
+ ",melt_fee_val "
+ ",melt_fee_frac "
+ ",melt_fee_curr "
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);",
+ 10, NULL);
+
+ /* Used in #postgres_get_refresh_melt to obtain information
+ about melted coins */
+ PREPARE ("get_refresh_melt",
+ "SELECT"
+ " coin_pub"
+ ",coin_sig"
+ ",amount_with_fee_val"
+ ",amount_with_fee_frac"
+ ",amount_with_fee_curr"
+ ",melt_fee_val "
+ ",melt_fee_frac "
+ ",melt_fee_curr "
+ " FROM refresh_melts"
+ " WHERE session_hash=$1 AND oldcoin_index=$2",
+ 2, NULL);
+
+ /* Query the 'refresh_melts' by coin public key */
+ PREPARE ("get_refresh_melt_by_coin",
+ "SELECT"
+ " session_hash"
+ /* ",oldcoin_index" // not needed */
+ ",coin_sig"
+ ",amount_with_fee_val"
+ ",amount_with_fee_frac"
+ ",amount_with_fee_curr"
+ ",melt_fee_val "
+ ",melt_fee_frac "
+ ",melt_fee_curr "
+ " FROM refresh_melts"
+ " WHERE coin_pub=$1",
+ 1, NULL);
+
+ /* Used in #postgres_insert_refresh_commit_links() to
+ store commitments */
+ 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);
+
+ /* Used in #postgres_get_refresh_commit_links() to
+ retrieve original commitments during /refresh/reveal */
+ 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);
+
+ /* Used in #postgres_insert_refresh_commit_coins() to
+ store coin commitments. */
+ PREPARE ("insert_refresh_commit_coin",
+ "INSERT INTO refresh_commit_coin "
+ "(session_hash"
+ ",cnc_index"
+ ",newcoin_index"
+ ",link_vector_enc"
+ ",coin_ev"
+ ") VALUES "
+ "($1, $2, $3, $4, $5);",
+ 5, NULL);
+
+ /* Used in #postgres_get_refresh_commit_coins() to
+ retrieve the original coin envelopes, to either be
+ verified or signed. */
+ 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);
+
+ /* Store information about a /deposit the exchange is to execute.
+ Used in #postgres_insert_deposit(). */
+ PREPARE ("insert_deposit",
+ "INSERT INTO deposits "
+ "(coin_pub"
+ ",denom_pub"
+ ",denom_sig"
+ ",transaction_id"
+ ",amount_with_fee_val"
+ ",amount_with_fee_frac"
+ ",amount_with_fee_curr"
+ ",deposit_fee_val"
+ ",deposit_fee_frac"
+ ",deposit_fee_curr"
+ ",timestamp"
+ ",refund_deadline"
+ ",wire_deadline"
+ ",merchant_pub"
+ ",h_contract"
+ ",h_wire"
+ ",coin_sig"
+ ",wire"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10,"
+ " $11, $12, $13, $14, $15, $16, $17, $18);",
+ 18, NULL);
+
+ /* Fetch an existing deposit request, used to ensure idempotency
+ during /deposit processing. Used in #postgres_have_deposit(). */
+ PREPARE ("get_deposit",
+ "SELECT"
+ " amount_with_fee_val"
+ ",amount_with_fee_frac"
+ ",amount_with_fee_curr"
+ ",timestamp"
+ ",refund_deadline"
+ ",wire_deadline"
+ ",h_contract"
+ ",h_wire"
+ " FROM deposits"
+ " WHERE ("
+ " (coin_pub=$1) AND"
+ " (transaction_id=$2) AND"
+ " (merchant_pub=$3)"
+ " )",
+ 3, NULL);
+
+ /* Fetch an existing deposit request.
+ Used in #postgres_wire_lookup_deposit_wtid(). */
+ PREPARE ("get_deposit_for_wtid",
+ "SELECT"
+ " amount_with_fee_val"
+ ",amount_with_fee_frac"
+ ",amount_with_fee_curr"
+ ",deposit_fee_val"
+ ",deposit_fee_frac"
+ ",deposit_fee_curr"
+ ",wire_deadline"
+ " FROM deposits"
+ " WHERE ("
+ " (coin_pub=$1) AND"
+ " (transaction_id=$2) AND"
+ " (merchant_pub=$3) AND"
+ " (h_contract=$4) AND"
+ " (h_wire=$5)"
+ " )",
+ 5, NULL);
+
+ /* Used in #postgres_get_ready_deposit() */
+ PREPARE ("deposits_get_ready",
+ "SELECT"
+ " serial_id"
+ ",amount_with_fee_val"
+ ",amount_with_fee_frac"
+ ",amount_with_fee_curr"
+ ",deposit_fee_val"
+ ",deposit_fee_frac"
+ ",deposit_fee_curr"
+ ",wire_deadline"
+ ",transaction_id"
+ ",h_contract"
+ ",wire"
+ ",merchant_pub"
+ ",coin_pub"
+ " FROM deposits"
+ " WHERE"
+ " tiny=false AND"
+ " done=false AND"
+ " wire_deadline<$1"
+ " ORDER BY wire_deadline ASC"
+ " LIMIT 1",
+ 1, NULL);
+
+ /* Used in #postgres_iterate_matching_deposits() */
+ PREPARE ("deposits_iterate_matching",
+ "SELECT"
+ " serial_id"
+ ",amount_with_fee_val"
+ ",amount_with_fee_frac"
+ ",amount_with_fee_curr"
+ ",deposit_fee_val"
+ ",deposit_fee_frac"
+ ",deposit_fee_curr"
+ ",wire_deadline"
+ ",transaction_id"
+ ",h_contract"
+ ",coin_pub"
+ " FROM deposits"
+ " WHERE"
+ " merchant_pub=$1 AND"
+ " h_wire=$2 AND"
+ " done=false"
+ " ORDER BY wire_deadline ASC"
+ " LIMIT " TALER_EXCHANGEDB_MATCHING_DEPOSITS_LIMIT_STR,
+ 2, NULL);
+
+ /* Used in #postgres_mark_deposit_tiny() */
+ PREPARE ("mark_deposit_tiny",
+ "UPDATE deposits"
+ " SET tiny=true"
+ " WHERE serial_id=$1",
+ 1, NULL);
+
+ /* Used in #postgres_mark_deposit_done() */
+ PREPARE ("mark_deposit_done",
+ "UPDATE deposits"
+ " SET done=true"
+ " WHERE serial_id=$1",
+ 1, NULL);
+
+ /* Used in #postgres_get_coin_transactions() to obtain information
+ about how a coin has been spend with /deposit requests. */
+ PREPARE ("get_deposit_with_coin_pub",
+ "SELECT"
+ " denom_pub"
+ ",denom_sig"
+ ",transaction_id"
+ ",amount_with_fee_val"
+ ",amount_with_fee_frac"
+ ",amount_with_fee_curr"
+ ",deposit_fee_val"
+ ",deposit_fee_frac"
+ ",deposit_fee_curr"
+ ",timestamp"
+ ",refund_deadline"
+ ",merchant_pub"
+ ",h_contract"
+ ",h_wire"
+ ",wire"
+ ",coin_sig"
+ " FROM deposits"
+ " WHERE coin_pub=$1",
+ 1, NULL);
+
+ /* Used in #postgres_insert_refresh_out() to store the
+ generated signature(s) for future requests, i.e. /refresh/link */
+ PREPARE ("insert_refresh_out",
+ "INSERT INTO refresh_out "
+ "(session_hash"
+ ",newcoin_index"
+ ",ev_sig"
+ ") VALUES "
+ "($1, $2, $3)",
+ 3, NULL);
+
+ /* Used in #postgres_get_link_data_list(). We use the session_hash
+ to obtain the "noreveal_index" for that session, and then select
+ the encrypted link vectors (link_vector_enc) and the
+ corresponding signatures (ev_sig) and the denomination keys from
+ the respective tables (namely refresh_melts and refresh_order)
+ using the session_hash as the primary filter (on join) and the
+ 'noreveal_index' to constrain the selection on the commitment.
+ We also want to get the triplet for each of the newcoins, so we
+ have another constraint to ensure we get each triplet with
+ matching "newcoin_index" values. NOTE: This may return many
+ results, both for different sessions and for the different coins
+ being exchangeed in the refresh ops. NOTE: There may be more
+ efficient ways to express the same query. */
+ PREPARE ("get_link",
+ "SELECT link_vector_enc,ev_sig,ro.denom_pub"
+ " FROM refresh_melts 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_out rc USING (session_hash)"
+ " WHERE ro.session_hash=$1"
+ " AND ro.newcoin_index=rcc.newcoin_index"
+ " AND ro.newcoin_index=rc.newcoin_index"
+ " AND rcc.cnc_index=rs.noreveal_index",
+ 1, NULL);
+
+ /* Used in #postgres_get_transfer(). Given the public key of a
+ melted coin, we obtain the corresponding encrypted link secret
+ and the transfer public key. This is done by first finding
+ the session_hash(es) of all sessions the coin was melted into,
+ and then constraining the result to the selected "noreveal_index"
+ and the transfer public key to the corresponding index of the
+ old coin.
+ NOTE: This may (in theory) return multiple results, one per session
+ that the old coin was melted into. */
+ PREPARE ("get_transfer",
+ "SELECT transfer_pub,link_secret_enc,session_hash"
+ " FROM refresh_melts 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",
+ 1, NULL);
+
+ /* Used in #postgres_lookup_wire_transfer */
+ PREPARE ("lookup_transactions",
+ "SELECT"
+ " h_contract"
+ ",h_wire"
+ ",coin_pub"
+ ",merchant_pub"
+ ",transaction_id"
+ ",execution_time"
+ ",coin_amount_val"
+ ",coin_amount_frac"
+ ",coin_amount_curr"
+ ",coin_fee_val"
+ ",coin_fee_frac"
+ ",coin_fee_curr"
+ " FROM aggregation_tracking"
+ " WHERE wtid_raw=$1",
+ 1, NULL);
+
+ /* Used in #postgres_wire_lookup_deposit_wtid */
+ PREPARE ("lookup_deposit_wtid",
+ "SELECT"
+ " wtid_raw"
+ ",execution_time"
+ ",coin_amount_val"
+ ",coin_amount_frac"
+ ",coin_amount_curr"
+ ",coin_fee_val"
+ ",coin_fee_frac"
+ ",coin_fee_curr"
+ " FROM aggregation_tracking"
+ " WHERE"
+ " coin_pub=$1 AND"
+ " h_contract=$2 AND"
+ " h_wire=$3 AND"
+ " transaction_id=$4 AND"
+ " merchant_pub=$5",
+ 5, NULL);
+
+ /* Used in #postgres_insert_aggregation_tracking */
+ PREPARE ("insert_aggregation_tracking",
+ "INSERT INTO aggregation_tracking "
+ "(h_contract"
+ ",h_wire"
+ ",coin_pub"
+ ",merchant_pub"
+ ",transaction_id"
+ ",wtid_raw"
+ ",execution_time"
+ ",coin_amount_val"
+ ",coin_amount_frac"
+ ",coin_amount_curr"
+ ",coin_fee_val"
+ ",coin_fee_frac"
+ ",coin_fee_curr"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)",
+ 13, NULL);
+
+
+ /* Used in #postgres_wire_prepare_data_insert() to store
+ wire transfer information before actually committing it with the bank */
+ PREPARE ("wire_prepare_data_insert",
+ "INSERT INTO prewire "
+ "(type"
+ ",buf"
+ ") VALUES "
+ "($1, $2)",
+ 2, NULL);
+
+ /* Used in #postgres_wire_prepare_data_mark_finished() */
+ PREPARE ("wire_prepare_data_mark_done",
+ "UPDATE prewire"
+ " SET finished=true"
+ " WHERE serial_id=$1",
+ 1, NULL);
+
+ /* Used in #postgres_wire_prepare_data_get() */
+ PREPARE ("wire_prepare_data_get",
+ "SELECT"
+ " serial_id"
+ ",buf"
+ " FROM prewire"
+ " WHERE"
+ " type=$1 AND"
+ " finished=false"
+ " ORDER BY serial_id ASC"
+ " LIMIT 1",
+ 1, NULL);
+
+ return GNUNET_OK;
+#undef PREPARE
+}
+
+
+/**
+ * Close thread-local database connection when a thread is destroyed.
+ *
+ * @param cls closure we get from pthreads (the db handle)
+ */
+static void
+db_conn_destroy (void *cls)
+{
+ struct TALER_EXCHANGEDB_Session *session = cls;
+ PGconn *db_conn = session->conn;
+
+ if (NULL != db_conn)
+ PQfinish (db_conn);
+ GNUNET_free (session);
+}
+
+
+/**
+ * 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_EXCHANGEDB_Session *
+postgres_get_session (void *cls,
+ int temporary)
+{
+ struct PostgresClosure *pc = cls;
+ PGconn *db_conn;
+ struct TALER_EXCHANGEDB_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;
+ }
+ PQsetNoticeReceiver (db_conn,
+ &pq_notice_receiver_cb,
+ NULL);
+ PQsetNoticeProcessor (db_conn,
+ &pq_notice_processor_cb,
+ 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_EXCHANGEDB_Session);
+ session->conn = db_conn;
+ if (0 != pthread_setspecific (pc->db_conn_threadlocal,
+ session))
+ {
+ GNUNET_break (0);
+ PQfinish (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_EXCHANGEDB_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_EXCHANGEDB_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_EXCHANGEDB_Session *session)
+{
+ PGresult *result;
+
+ result = PQexec (session->conn,
+ "COMMIT");
+ if (PGRES_COMMAND_OK !=
+ PQresultStatus (result))
+ {
+ const char *sqlstate;
+
+ sqlstate = PQresultErrorField (result,
+ PG_DIAG_SQLSTATE);
+ if (NULL == sqlstate)
+ {
+ /* very unexpected... */
+ GNUNET_break (0);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ /* 40P01: deadlock, 40001: serialization failure */
+ if ( (0 == strcmp (sqlstate,
+ "40P01")) ||
+ (0 == strcmp (sqlstate,
+ "40001")) )
+ {
+ /* These two can be retried and have a fair chance of working
+ the next time */
+ PQclear (result);
+ return GNUNET_NO;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Database commit failure: %s\n",
+ sqlstate);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Insert a denomination key's public information into the database for
+ * reference by auditors and other consistency checks.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param session connection to use
+ * @param denom_pub the public key used for signing coins of this denomination
+ * @param issue issuing information with value, fees and other info about the coin
+ * @return #GNUNET_OK on success; #GNUNET_SYSERR on failure
+ */
+static int
+postgres_insert_denomination_info (void *cls,
+ struct TALER_EXCHANGEDB_Session *session,
+ const struct TALER_DenominationPublicKey *denom_pub,
+ const struct TALER_EXCHANGEDB_DenominationKeyInformationP *issue)
+{
+ PGresult *result;
+ int ret;
+
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_rsa_public_key (denom_pub->rsa_public_key),
+ GNUNET_PQ_query_param_auto_from_type (&issue->properties.master),
+ GNUNET_PQ_query_param_auto_from_type (&issue->signature),
+ GNUNET_PQ_query_param_absolute_time_nbo (&issue->properties.start),
+ GNUNET_PQ_query_param_absolute_time_nbo (&issue->properties.expire_withdraw),
+ GNUNET_PQ_query_param_absolute_time_nbo (&issue->properties.expire_spend),
+ GNUNET_PQ_query_param_absolute_time_nbo (&issue->properties.expire_legal),
+ TALER_PQ_query_param_amount_nbo (&issue->properties.value),
+ TALER_PQ_query_param_amount_nbo (&issue->properties.fee_withdraw),
+ TALER_PQ_query_param_amount_nbo (&issue->properties.fee_deposit),
+ TALER_PQ_query_param_amount_nbo (&issue->properties.fee_refresh),
+ GNUNET_PQ_query_param_end
+ };
+ /* check fees match coin currency */
+ GNUNET_assert (GNUNET_YES ==
+ TALER_amount_cmp_currency_nbo (&issue->properties.value,
+ &issue->properties.fee_withdraw));
+ GNUNET_assert (GNUNET_YES ==
+ TALER_amount_cmp_currency_nbo (&issue->properties.value,
+ &issue->properties.fee_deposit));
+ GNUNET_assert (GNUNET_YES ==
+ TALER_amount_cmp_currency_nbo (&issue->properties.value,
+ &issue->properties.fee_refresh));
+
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "denomination_insert",
+ params);
+ if (PGRES_COMMAND_OK != PQresultStatus (result))
+ {
+ ret = GNUNET_SYSERR;
+ BREAK_DB_ERR (result);
+ }
+ else
+ {
+ ret = GNUNET_OK;
+ }
+ PQclear (result);
+ return ret;
+}
+
+
+/**
+ * Fetch information about a denomination key.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param session connection to use
+ * @param denom_pub the public key used for signing coins of this denomination
+ * @param[out] issue set to issue information with value, fees and other info about the coin, can be NULL
+ * @return #GNUNET_OK on success; #GNUNET_NO if no record was found, #GNUNET_SYSERR on failure
+ */
+static int
+postgres_get_denomination_info (void *cls,
+ struct TALER_EXCHANGEDB_Session *session,
+ const struct TALER_DenominationPublicKey *denom_pub,
+ struct TALER_EXCHANGEDB_DenominationKeyInformationP *issue)
+{
+ PGresult *result;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_rsa_public_key (denom_pub->rsa_public_key),
+ GNUNET_PQ_query_param_end
+ };
+
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "denomination_get",
+ params);
+ if (PGRES_TUPLES_OK != PQresultStatus (result))
+ {
+ QUERY_ERR (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ if (0 == PQntuples (result))
+ {
+ PQclear (result);
+ return GNUNET_NO;
+ }
+ if (1 != PQntuples (result))
+ {
+ GNUNET_break (0);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ if (NULL == issue)
+ {
+ PQclear (result);
+ return GNUNET_OK;
+ }
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("master_pub",
+ &issue->properties.master),
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ &issue->signature),
+ GNUNET_PQ_result_spec_absolute_time_nbo ("valid_from",
+ &issue->properties.start),
+ GNUNET_PQ_result_spec_absolute_time_nbo ("expire_withdraw",
+ &issue->properties.expire_withdraw),
+ GNUNET_PQ_result_spec_absolute_time_nbo ("expire_spend",
+ &issue->properties.expire_spend),
+ GNUNET_PQ_result_spec_absolute_time_nbo ("expire_legal",
+ &issue->properties.expire_legal),
+ TALER_PQ_result_spec_amount_nbo ("coin",
+ &issue->properties.value),
+ TALER_PQ_result_spec_amount_nbo ("fee_withdraw",
+ &issue->properties.fee_withdraw),
+ TALER_PQ_result_spec_amount_nbo ("fee_deposit",
+ &issue->properties.fee_deposit),
+ TALER_PQ_result_spec_amount_nbo ("fee_refresh",
+ &issue->properties.fee_refresh),
+ GNUNET_PQ_result_spec_end
+ };
+
+ EXITIF (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ 0));
+ }
+ PQclear (result);
+ return GNUNET_OK;
+
+ EXITIF_exit:
+ PQclear (result);
+ return GNUNET_SYSERR;
+}
+
+
+/**
+ * Get the summary of a reserve.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param session the database connection handle
+ * @param[in,out] 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_EXCHANGEDB_Session *session,
+ struct TALER_EXCHANGEDB_Reserve *reserve)
+{
+ PGresult *result;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type(&reserve->pub),
+ GNUNET_PQ_query_param_end
+ };
+
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "reserve_get",
+ 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 GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_result_spec_amount("current_balance", &reserve->balance),
+ GNUNET_PQ_result_spec_absolute_time("expiration_date", &reserve->expiry),
+ GNUNET_PQ_result_spec_end
+ };
+
+ EXITIF (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ 0));
+ }
+ 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
+reserves_update (void *cls,
+ struct TALER_EXCHANGEDB_Session *session,
+ const struct TALER_EXCHANGEDB_Reserve *reserve)
+{
+ PGresult *result;
+ int ret;
+
+ if (NULL == reserve)
+ return GNUNET_SYSERR;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_absolute_time (&reserve->expiry),
+ TALER_PQ_query_param_amount (&reserve->balance),
+ GNUNET_PQ_query_param_auto_from_type (&reserve->pub),
+ GNUNET_PQ_query_param_end
+ };
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "reserve_update",
+ params);
+ if (PGRES_COMMAND_OK != PQresultStatus(result))
+ {
+ QUERY_ERR (result);
+ ret = GNUNET_SYSERR;
+ }
+ else
+ {
+ ret = GNUNET_OK;
+ }
+ PQclear (result);
+ return ret;
+}
+
+
+/**
+ * Insert an incoming transaction into reserves. New reserves are also created
+ * through this function. Note that this API call starts (and stops) its
+ * own transaction scope (so the application must not do so).
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param session the database connection handle
+ * @param reserve_pub public key of the reserve
+ * @param balance the amount that has to be added to the reserve
+ * @param execution_time when was the amount added
+ * @param details bank transaction details justifying the increment,
+ * must be unique for each incoming transaction
+ * @return #GNUNET_OK upon success; #GNUNET_NO if the given
+ * @a details are already known for this @a reserve_pub,
+ * #GNUNET_SYSERR upon failures (DB error, incompatible currency)
+ */
+static int
+postgres_reserves_in_insert (void *cls,
+ struct TALER_EXCHANGEDB_Session *session,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_Amount *balance,
+ struct GNUNET_TIME_Absolute execution_time,
+ const json_t *details)
+{
+ PGresult *result;
+ int reserve_exists;
+ struct TALER_EXCHANGEDB_Reserve reserve;
+ struct GNUNET_TIME_Absolute expiry;
+
+ if (GNUNET_OK != postgres_start (cls,
+ session))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ reserve.pub = *reserve_pub;
+ reserve_exists = postgres_reserve_get (cls,
+ session,
+ &reserve);
+ if (GNUNET_SYSERR == reserve_exists)
+ {
+ GNUNET_break (0);
+ goto rollback;
+ }
+ expiry = GNUNET_TIME_absolute_add (execution_time,
+ TALER_IDLE_RESERVE_EXPIRATION_TIME);
+ if (GNUNET_NO == reserve_exists)
+ {
+ /* New reserve, create balance for the first time; we do this
+ before adding the actual transaction to "reserves_in", as
+ for a new reserve it can't be a duplicate 'add' operation,
+ and as the 'add' operation may need the reserve entry
+ as a foreign key. */
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ TALER_PQ_query_param_amount (balance),
+ GNUNET_PQ_query_param_absolute_time (&expiry),
+ GNUNET_PQ_query_param_end
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Reserve does not exist; creating a new one\n");
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "reserve_create",
+ params);
+ if (PGRES_COMMAND_OK != PQresultStatus(result))
+ {
+ QUERY_ERR (result);
+ PQclear (result);
+ goto rollback;
+ }
+ PQclear (result);
+ }
+ /* Create new incoming transaction, SQL "primary key" logic
+ is used to guard against duplicates. If a duplicate is
+ detected, we rollback (which really shouldn't undo
+ anything) and return #GNUNET_NO to indicate that this failure
+ is kind-of harmless (already executed). */
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&reserve.pub),
+ TALER_PQ_query_param_amount (balance),
+ TALER_PQ_query_param_json (details),
+ GNUNET_PQ_query_param_absolute_time (&execution_time),
+ GNUNET_PQ_query_param_end
+ };
+
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "reserves_in_add_transaction",
+ params);
+ }
+ if (PGRES_COMMAND_OK != PQresultStatus(result))
+ {
+ const char *efield;
+
+ efield = PQresultErrorField (result,
+ PG_DIAG_SQLSTATE);
+ if ( (PGRES_FATAL_ERROR == PQresultStatus(result)) &&
+ (NULL != strstr ("23505", /* unique violation */
+ efield)) )
+ {
+ /* This means we had the same reserve/justification/details
+ before */
+ PQclear (result);
+ postgres_rollback (cls,
+ session);
+ return GNUNET_NO;
+ }
+ QUERY_ERR (result);
+ PQclear (result);
+ goto rollback;
+ }
+ PQclear (result);
+
+ if (GNUNET_YES == reserve_exists)
+ {
+ /* If the reserve already existed, we need to still update the
+ balance; we do this after checking for duplication, as
+ otherwise we might have to actually pay the cost to roll this
+ back for duplicate transactions; like this, we should virtually
+ never actually have to rollback anything. */
+ struct TALER_EXCHANGEDB_Reserve updated_reserve;
+
+ updated_reserve.pub = reserve.pub;
+ if (GNUNET_OK !=
+ TALER_amount_add (&updated_reserve.balance,
+ &reserve.balance,
+ balance))
+ {
+ /* currency overflow or incompatible currency */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Attempt to deposit incompatible amount into reserve\n");
+ goto rollback;
+ }
+ updated_reserve.expiry = GNUNET_TIME_absolute_max (expiry,
+ reserve.expiry);
+ if (GNUNET_OK != reserves_update (cls,
+ session,
+ &updated_reserve))
+ goto rollback;
+ }
+ if (GNUNET_OK != postgres_commit (cls,
+ session))
+ return GNUNET_SYSERR;
+ return GNUNET_OK;
+
+ rollback:
+ postgres_rollback (cls,
+ session);
+ return GNUNET_SYSERR;
+}
+
+
+/**
+ * Locate the response for a /reserve/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 coin to be signed (will match
+ * `h_coin_envelope` in the @a collectable to be returned)
+ * @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_withdraw_info (void *cls,
+ struct TALER_EXCHANGEDB_Session *session,
+ const struct GNUNET_HashCode *h_blind,
+ struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable)
+{
+ PGresult *result;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_blind),
+ GNUNET_PQ_query_param_end
+ };
+ int ret;
+
+ ret = GNUNET_SYSERR;
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "get_withdraw_info",
+ params);
+
+ if (PGRES_TUPLES_OK != PQresultStatus (result))
+ {
+ QUERY_ERR (result);
+ goto cleanup;
+ }
+ if (0 == PQntuples (result))
+ {
+ ret = GNUNET_NO;
+ goto cleanup;
+ }
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_rsa_public_key ("denom_pub",
+ &collectable->denom_pub.rsa_public_key),
+ GNUNET_PQ_result_spec_rsa_signature ("denom_sig",
+ &collectable->sig.rsa_signature),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
+ &collectable->reserve_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ &collectable->reserve_pub),
+ TALER_PQ_result_spec_amount ("amount_with_fee",
+ &collectable->amount_with_fee),
+ TALER_PQ_result_spec_amount ("withdraw_fee",
+ &collectable->withdraw_fee),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result, rs, 0))
+ {
+ GNUNET_break (0);
+ goto cleanup;
+ }
+ }
+ collectable->h_coin_envelope = *h_blind;
+ ret = GNUNET_YES;
+
+ cleanup:
+ PQclear (result);
+ 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 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_withdraw_info (void *cls,
+ struct TALER_EXCHANGEDB_Session *session,
+ const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable)
+{
+ PGresult *result;
+ struct TALER_EXCHANGEDB_Reserve reserve;
+ int ret = GNUNET_SYSERR;
+ struct GNUNET_TIME_Absolute now;
+ struct GNUNET_TIME_Absolute expiry;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&collectable->h_coin_envelope),
+ GNUNET_PQ_query_param_rsa_public_key (collectable->denom_pub.rsa_public_key),
+ GNUNET_PQ_query_param_rsa_signature (collectable->sig.rsa_signature),
+ GNUNET_PQ_query_param_auto_from_type (&collectable->reserve_pub),
+ GNUNET_PQ_query_param_auto_from_type (&collectable->reserve_sig),
+ GNUNET_PQ_query_param_absolute_time (&now),
+ TALER_PQ_query_param_amount (&collectable->amount_with_fee),
+ TALER_PQ_query_param_amount (&collectable->withdraw_fee),
+ GNUNET_PQ_query_param_end
+ };
+
+ now = GNUNET_TIME_absolute_get ();
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "insert_withdraw_info",
+ params);
+ if (PGRES_COMMAND_OK != PQresultStatus (result))
+ {
+ QUERY_ERR (result);
+ goto cleanup;
+ }
+ reserve.pub = collectable->reserve_pub;
+ if (GNUNET_OK != postgres_reserve_get (cls,
+ session,
+ &reserve))
+ {
+ /* Should have been checked before we got here... */
+ GNUNET_break (0);
+ goto cleanup;
+ }
+ if (GNUNET_SYSERR ==
+ TALER_amount_subtract (&reserve.balance,
+ &reserve.balance,
+ &collectable->amount_with_fee))
+ {
+ /* Should have been checked before we got here... */
+ GNUNET_break (0);
+ goto cleanup;
+ }
+ expiry = GNUNET_TIME_absolute_add (now,
+ TALER_IDLE_RESERVE_EXPIRATION_TIME);
+ reserve.expiry = GNUNET_TIME_absolute_max (expiry,
+ reserve.expiry);
+ if (GNUNET_OK != reserves_update (cls,
+ session,
+ &reserve))
+ {
+ GNUNET_break (0);
+ goto cleanup;
+ }
+ ret = GNUNET_OK;
+ cleanup:
+ PQclear (result);
+ 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 TALER_EXCHANGEDB_ReserveHistory *
+postgres_get_reserve_history (void *cls,
+ struct TALER_EXCHANGEDB_Session *session,
+ const struct TALER_ReservePublicKeyP *reserve_pub)
+{
+ PGresult *result;
+ struct TALER_EXCHANGEDB_ReserveHistory *rh;
+ struct TALER_EXCHANGEDB_ReserveHistory *rh_tail;
+ int rows;
+ int ret;
+
+ rh = NULL;
+ rh_tail = NULL;
+ ret = GNUNET_SYSERR;
+ {
+ struct TALER_EXCHANGEDB_BankTransfer *bt;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ GNUNET_PQ_query_param_end
+ };
+
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "reserves_in_get_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 TALER_EXCHANGEDB_BankTransfer);
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_result_spec_amount ("balance",
+ &bt->amount),
+ GNUNET_PQ_result_spec_absolute_time ("execution_date",
+ &bt->execution_date),
+ TALER_PQ_result_spec_json ("details",
+ &bt->wire),
+ GNUNET_PQ_result_spec_end
+ };
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result, rs, --rows))
+ {
+ GNUNET_break (0);
+ GNUNET_free (bt);
+ PQclear (result);
+ goto cleanup;
+ }
+ }
+ bt->reserve_pub = *reserve_pub;
+ if (NULL != rh_tail)
+ {
+ rh_tail->next = GNUNET_new (struct TALER_EXCHANGEDB_ReserveHistory);
+ rh_tail = rh_tail->next;
+ }
+ else
+ {
+ rh_tail = GNUNET_new (struct TALER_EXCHANGEDB_ReserveHistory);
+ rh = rh_tail;
+ }
+ rh_tail->type = TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE;
+ rh_tail->details.bank = bt;
+ }
+ PQclear (result);
+ }
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ GNUNET_PQ_query_param_end
+ };
+
+ GNUNET_assert (NULL != rh);
+ GNUNET_assert (NULL != rh_tail);
+ GNUNET_assert (NULL == rh_tail->next);
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "get_reserves_out",
+ params);
+ if (PGRES_TUPLES_OK != PQresultStatus (result))
+ {
+ QUERY_ERR (result);
+ PQclear (result);
+ goto cleanup;
+ }
+ rows = PQntuples (result);
+ while (0 < rows)
+ {
+ struct TALER_EXCHANGEDB_CollectableBlindcoin *cbc;
+
+ cbc = GNUNET_new (struct TALER_EXCHANGEDB_CollectableBlindcoin);
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("h_blind_ev",
+ &cbc->h_coin_envelope),
+ GNUNET_PQ_result_spec_rsa_public_key ("denom_pub",
+ &cbc->denom_pub.rsa_public_key),
+ GNUNET_PQ_result_spec_rsa_signature ("denom_sig",
+ &cbc->sig.rsa_signature),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
+ &cbc->reserve_sig),
+ TALER_PQ_result_spec_amount ("amount_with_fee",
+ &cbc->amount_with_fee),
+ TALER_PQ_result_spec_amount ("withdraw_fee",
+ &cbc->withdraw_fee),
+ GNUNET_PQ_result_spec_end
+ };
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result, rs, --rows))
+ {
+ GNUNET_break (0);
+ GNUNET_free (cbc);
+ PQclear (result);
+ goto cleanup;
+ }
+ cbc->reserve_pub = *reserve_pub;
+ }
+ rh_tail->next = GNUNET_new (struct TALER_EXCHANGEDB_ReserveHistory);
+ rh_tail = rh_tail->next;
+ rh_tail->type = TALER_EXCHANGEDB_RO_WITHDRAW_COIN;
+ rh_tail->details.withdraw = cbc;
+ }
+ ret = GNUNET_OK;
+ PQclear (result);
+ }
+ cleanup:
+ 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 exact deposit is unknown to us
+ * #GNUNET_SYSERR on DB error
+ */
+static int
+postgres_have_deposit (void *cls,
+ struct TALER_EXCHANGEDB_Session *session,
+ const struct TALER_EXCHANGEDB_Deposit *deposit)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&deposit->coin.coin_pub),
+ GNUNET_PQ_query_param_uint64 (&deposit->transaction_id),
+ GNUNET_PQ_query_param_auto_from_type (&deposit->merchant_pub),
+ GNUNET_PQ_query_param_end
+ };
+ PGresult *result;
+
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "get_deposit",
+ 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;
+ }
+
+ /* Now we check that the other information in @a deposit
+ also matches, and if not report inconsistencies. */
+ {
+ struct TALER_EXCHANGEDB_Deposit deposit2;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_result_spec_amount ("amount_with_fee",
+ &deposit2.amount_with_fee),
+ GNUNET_PQ_result_spec_absolute_time ("timestamp",
+ &deposit2.timestamp),
+ GNUNET_PQ_result_spec_absolute_time ("refund_deadline",
+ &deposit2.refund_deadline),
+ GNUNET_PQ_result_spec_absolute_time ("wire_deadline",
+ &deposit2.wire_deadline),
+ GNUNET_PQ_result_spec_auto_from_type ("h_contract",
+ &deposit2.h_contract),
+ GNUNET_PQ_result_spec_auto_from_type ("h_wire",
+ &deposit2.h_wire),
+ GNUNET_PQ_result_spec_end
+ };
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result, rs, 0))
+ {
+ GNUNET_break (0);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ if ( (0 != TALER_amount_cmp (&deposit->amount_with_fee,
+ &deposit2.amount_with_fee)) ||
+ (deposit->timestamp.abs_value_us !=
+ deposit2.timestamp.abs_value_us) ||
+ (deposit->refund_deadline.abs_value_us !=
+ deposit2.refund_deadline.abs_value_us) ||
+ (0 != memcmp (&deposit->h_contract,
+ &deposit2.h_contract,
+ sizeof (struct GNUNET_HashCode))) ||
+ (0 != memcmp (&deposit->h_wire,
+ &deposit2.h_wire,
+ sizeof (struct GNUNET_HashCode))) )
+ {
+ /* Inconsistencies detected! Does not match! (We might want to
+ expand the API with a 'get_deposit' function to return the
+ original transaction details to be used for an error message
+ in the future!) #3838 */
+ PQclear (result);
+ return GNUNET_NO;
+ }
+ }
+ PQclear (result);
+ return GNUNET_YES;
+}
+
+
+/**
+ * Mark a deposit as tiny, thereby declaring that it cannot be
+ * executed by itself and should no longer be returned by
+ * @e iterate_ready_deposits()
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param session connection to the database
+ * @param deposit_rowid identifies the deposit row to modify
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
+ */
+static int
+postgres_mark_deposit_tiny (void *cls,
+ struct TALER_EXCHANGEDB_Session *session,
+ unsigned long long rowid)
+{
+ uint64_t serial_id = rowid;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ PGresult *result;
+
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "mark_deposit_tiny",
+ params);
+ if (PGRES_COMMAND_OK !=
+ PQresultStatus (result))
+ {
+ BREAK_DB_ERR (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Mark a deposit as done, thereby declaring that it cannot be
+ * executed at all anymore, and should no longer be returned by
+ * @e iterate_ready_deposits() or @e iterate_matching_deposits().
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param session connection to the database
+ * @param deposit_rowid identifies the deposit row to modify
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
+ */
+static int
+postgres_mark_deposit_done (void *cls,
+ struct TALER_EXCHANGEDB_Session *session,
+ unsigned long long rowid)
+{
+ uint64_t serial_id = rowid;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ PGresult *result;
+
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "mark_deposit_done",
+ params);
+ if (PGRES_COMMAND_OK !=
+ PQresultStatus (result))
+ {
+ BREAK_DB_ERR (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Obtain information about deposits that are ready to be executed.
+ * Such deposits must not be marked as "tiny" or "done", and the
+ * execution time must be in the past.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param session connection to the database
+ * @param deposit_cb function to call for ONE such deposit
+ * @param deposit_cb_cls closure for @a deposit_cb
+ * @return number of rows processed, 0 if none exist,
+ * #GNUNET_SYSERR on error
+ */
+static int
+postgres_get_ready_deposit (void *cls,
+ struct TALER_EXCHANGEDB_Session *session,
+ TALER_EXCHANGEDB_DepositIterator deposit_cb,
+ void *deposit_cb_cls)
+{
+ struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_end
+ };
+ PGresult *result;
+ unsigned int n;
+ int ret;
+
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "deposits_get_ready",
+ params);
+ if (PGRES_TUPLES_OK !=
+ PQresultStatus (result))
+ {
+ BREAK_DB_ERR (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ if (0 == (n = PQntuples (result)))
+ {
+ PQclear (result);
+ return 0;
+ }
+ GNUNET_break (1 == n);
+ {
+ struct TALER_Amount amount_with_fee;
+ struct TALER_Amount deposit_fee;
+ struct GNUNET_TIME_Absolute wire_deadline;
+ struct GNUNET_HashCode h_contract;
+ struct TALER_MerchantPublicKeyP merchant_pub;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ uint64_t transaction_id;
+ uint64_t serial_id;
+ json_t *wire;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("serial_id",
+ &serial_id),
+ GNUNET_PQ_result_spec_uint64 ("transaction_id",
+ &transaction_id),
+ TALER_PQ_result_spec_amount ("amount_with_fee",
+ &amount_with_fee),
+ TALER_PQ_result_spec_amount ("deposit_fee",
+ &deposit_fee),
+ GNUNET_PQ_result_spec_absolute_time ("wire_deadline",
+ &wire_deadline),
+ GNUNET_PQ_result_spec_auto_from_type ("h_contract",
+ &h_contract),
+ GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
+ &merchant_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
+ &coin_pub),
+ TALER_PQ_result_spec_json ("wire",
+ &wire),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result, rs, 0))
+ {
+ GNUNET_break (0);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ ret = deposit_cb (deposit_cb_cls,
+ serial_id,
+ &merchant_pub,
+ &coin_pub,
+ &amount_with_fee,
+ &deposit_fee,
+ transaction_id,
+ &h_contract,
+ wire_deadline,
+ wire);
+ GNUNET_PQ_cleanup_result (rs);
+ PQclear (result);
+ }
+ return (GNUNET_OK == ret) ? 1 : 0;
+}
+
+
+/**
+ * Obtain information about other pending deposits for the same
+ * destination. Those deposits must not already be "done".
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param session connection to the database
+ * @param h_wire destination of the wire transfer
+ * @param merchant_pub public key of the merchant
+ * @param deposit_cb function to call for each deposit
+ * @param deposit_cb_cls closure for @a deposit_cb
+ * @param limit maximum number of matching deposits to return
+ * @return number of rows processed, 0 if none exist,
+ * #GNUNET_SYSERR on error
+ */
+static int
+postgres_iterate_matching_deposits (void *cls,
+ struct TALER_EXCHANGEDB_Session *session,
+ const struct GNUNET_HashCode *h_wire,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ TALER_EXCHANGEDB_DepositIterator deposit_cb,
+ void *deposit_cb_cls,
+ uint32_t limit)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (merchant_pub),
+ GNUNET_PQ_query_param_auto_from_type (h_wire),
+ GNUNET_PQ_query_param_end
+ };
+ PGresult *result;
+ unsigned int i;
+ unsigned int n;
+
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "deposits_iterate_matching",
+ params);
+ if (PGRES_TUPLES_OK !=
+ PQresultStatus (result))
+ {
+ BREAK_DB_ERR (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ if (0 == (n = PQntuples (result)))
+ {
+ PQclear (result);
+ return 0;
+ }
+ if (n > limit)
+ n = limit;
+ for (i=0;i<n;i++)
+ {
+ struct TALER_Amount amount_with_fee;
+ struct TALER_Amount deposit_fee;
+ struct GNUNET_TIME_Absolute wire_deadline;
+ struct GNUNET_HashCode h_contract;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ uint64_t transaction_id;
+ uint64_t serial_id;
+ int ret;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("serial_id",
+ &serial_id),
+ GNUNET_PQ_result_spec_uint64 ("transaction_id",
+ &transaction_id),
+ TALER_PQ_result_spec_amount ("amount_with_fee",
+ &amount_with_fee),
+ TALER_PQ_result_spec_amount ("deposit_fee",
+ &deposit_fee),
+ GNUNET_PQ_result_spec_absolute_time ("wire_deadline",
+ &wire_deadline),
+ GNUNET_PQ_result_spec_auto_from_type ("h_contract",
+ &h_contract),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
+ &coin_pub),
+ GNUNET_PQ_result_spec_end
+ };
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result, rs, i))
+ {
+ GNUNET_break (0);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ ret = deposit_cb (deposit_cb_cls,
+ serial_id,
+ merchant_pub,
+ &coin_pub,
+ &amount_with_fee,
+ &deposit_fee,
+ transaction_id,
+ &h_contract,
+ wire_deadline,
+ NULL);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+ PQclear (result);
+ return i;
+}
+
+
+/**
+ * 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_EXCHANGEDB_Session *session,
+ const struct TALER_EXCHANGEDB_Deposit *deposit)
+{
+ PGresult *result;
+ int ret;
+
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&deposit->coin.coin_pub),
+ GNUNET_PQ_query_param_rsa_public_key (deposit->coin.denom_pub.rsa_public_key),
+ GNUNET_PQ_query_param_rsa_signature (deposit->coin.denom_sig.rsa_signature),
+ GNUNET_PQ_query_param_uint64 (&deposit->transaction_id),
+ TALER_PQ_query_param_amount (&deposit->amount_with_fee),
+ TALER_PQ_query_param_amount (&deposit->deposit_fee),
+ GNUNET_PQ_query_param_absolute_time (&deposit->timestamp),
+ GNUNET_PQ_query_param_absolute_time (&deposit->refund_deadline),
+ GNUNET_PQ_query_param_absolute_time (&deposit->wire_deadline),
+ GNUNET_PQ_query_param_auto_from_type (&deposit->merchant_pub),
+ GNUNET_PQ_query_param_auto_from_type (&deposit->h_contract),
+ GNUNET_PQ_query_param_auto_from_type (&deposit->h_wire),
+ GNUNET_PQ_query_param_auto_from_type (&deposit->csig),
+ TALER_PQ_query_param_json (deposit->wire),
+ GNUNET_PQ_query_param_end
+ };
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "insert_deposit",
+ params);
+ }
+ if (PGRES_COMMAND_OK != PQresultStatus (result))
+ {
+ BREAK_DB_ERR (result);
+ ret = GNUNET_SYSERR;
+ }
+ else
+ {
+ ret = GNUNET_OK;
+ }
+ PQclear (result);
+ 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[out] refresh_session where to store the result, can be NULL
+ * to just check if the session exists
+ * @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_EXCHANGEDB_Session *session,
+ const struct GNUNET_HashCode *session_hash,
+ struct TALER_EXCHANGEDB_RefreshSession *refresh_session)
+{
+ PGresult *result;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (session_hash),
+ GNUNET_PQ_query_param_end
+ };
+
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "get_refresh_session",
+ 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;
+ }
+ GNUNET_assert (1 == PQntuples (result));
+ if (NULL == refresh_session)
+ {
+ /* We're done if the caller is only interested in whether the
+ * session exists or not */
+ PQclear (result);
+ return GNUNET_YES;
+ }
+ memset (refresh_session,
+ 0,
+ sizeof (struct TALER_EXCHANGEDB_RefreshSession));
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint16 ("num_oldcoins",
+ &refresh_session->num_oldcoins),
+ GNUNET_PQ_result_spec_uint16 ("num_newcoins",
+ &refresh_session->num_newcoins),
+ GNUNET_PQ_result_spec_uint16 ("noreveal_index",
+ &refresh_session->noreveal_index),
+ GNUNET_PQ_result_spec_end
+ };
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result, rs, 0))
+ {
+ GNUNET_break (0);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ }
+ 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_EXCHANGEDB_Session *session,
+ const struct GNUNET_HashCode *session_hash,
+ const struct TALER_EXCHANGEDB_RefreshSession *refresh_session)
+{
+ PGresult *result;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (session_hash),
+ GNUNET_PQ_query_param_uint16 (&refresh_session->num_oldcoins),
+ GNUNET_PQ_query_param_uint16 (&refresh_session->num_newcoins),
+ GNUNET_PQ_query_param_uint16 (&refresh_session->noreveal_index),
+ GNUNET_PQ_query_param_end
+ };
+
+ result = GNUNET_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;
+}
+
+
+/**
+ * Insert a coin we know of into the DB. The coin can then be referenced by
+ * tables for deposits, lock and refresh functionality.
+ *
+ * @param cls plugin closure
+ * @param session the shared database session
+ * @param coin_info the public coin info
+ * @return #GNUNET_SYSERR upon error; #GNUNET_OK upon success
+ */
+static int
+insert_known_coin (void *cls,
+ struct TALER_EXCHANGEDB_Session *session,
+ const struct TALER_CoinPublicInfo *coin_info)
+{
+ PGresult *result;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&coin_info->coin_pub),
+ GNUNET_PQ_query_param_rsa_public_key (coin_info->denom_pub.rsa_public_key),
+ GNUNET_PQ_query_param_rsa_signature (coin_info->denom_sig.rsa_signature),
+ GNUNET_PQ_query_param_end
+ };
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "insert_known_coin",
+ params);
+ if (PGRES_COMMAND_OK != PQresultStatus (result))
+ {
+ BREAK_DB_ERR (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Retrieve the record for a known coin.
+ *
+ * @param cls the plugin closure
+ * @param session the database session handle
+ * @param coin_pub the public key of the coin to search for
+ * @param coin_info place holder for the returned coin information object
+ * @return #GNUNET_SYSERR upon error; #GNUNET_NO if no coin is found; #GNUNET_OK
+ * if upon succesfullying retrieving the record data info @a
+ * coin_info
+ */
+static int
+get_known_coin (void *cls,
+ struct TALER_EXCHANGEDB_Session *session,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ struct TALER_CoinPublicInfo *coin_info)
+{
+ PGresult *result;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (coin_pub),
+ GNUNET_PQ_query_param_end
+ };
+ int nrows;
+
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "get_known_coin",
+ params);
+ if (PGRES_TUPLES_OK != PQresultStatus (result))
+ {
+ BREAK_DB_ERR (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ nrows = PQntuples (result);
+ if (0 == nrows)
+ {
+ PQclear (result);
+ return GNUNET_NO;
+ }
+ GNUNET_assert (1 == nrows); /* due to primary key */
+ if (NULL == coin_info)
+ return GNUNET_YES;
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_rsa_public_key ("denom_pub",
+ &coin_info->denom_pub.rsa_public_key),
+ GNUNET_PQ_result_spec_rsa_signature ("denom_sig",
+ &coin_info->denom_sig.rsa_signature),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result, rs, 0))
+ {
+ PQclear (result);
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ PQclear (result);
+ coin_info->coin_pub = *coin_pub;
+ 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_EXCHANGEDB_Session *session,
+ uint16_t oldcoin_index,
+ const struct TALER_EXCHANGEDB_RefreshMelt *melt)
+{
+ PGresult *result;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&melt->coin.coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (&melt->session_hash),
+ GNUNET_PQ_query_param_uint16 (&oldcoin_index),
+ GNUNET_PQ_query_param_auto_from_type (&melt->coin_sig),
+ TALER_PQ_query_param_amount (&melt->amount_with_fee),
+ TALER_PQ_query_param_amount (&melt->melt_fee),
+ GNUNET_PQ_query_param_end
+ };
+ int ret;
+
+ /* check if the coin is already known */
+ ret = get_known_coin (cls,
+ session,
+ &melt->coin.coin_pub,
+ NULL);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_NO == ret) /* if not, insert it */
+ {
+ ret = insert_known_coin (cls,
+ session,
+ &melt->coin);
+ if (ret == GNUNET_SYSERR)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ /* insert the melt */
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "insert_refresh_melt",
+ params);
+ 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 session_hash session hash of the melt operation
+ * @param oldcoin_index index of the coin to retrieve
+ * @param melt melt data to fill in, can be NULL
+ * @return #GNUNET_OK on success
+ * #GNUNET_SYSERR on internal error
+ */
+static int
+postgres_get_refresh_melt (void *cls,
+ struct TALER_EXCHANGEDB_Session *session,
+ const struct GNUNET_HashCode *session_hash,
+ uint16_t oldcoin_index,
+ struct TALER_EXCHANGEDB_RefreshMelt *melt)
+{
+ PGresult *result;
+ struct TALER_CoinPublicInfo coin;
+ struct TALER_CoinSpendSignatureP coin_sig;
+ struct TALER_Amount amount_with_fee;
+ struct TALER_Amount melt_fee;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (session_hash),
+ GNUNET_PQ_query_param_uint16 (&oldcoin_index),
+ GNUNET_PQ_query_param_end
+ };
+ int nrows;
+
+ /* check if the melt record exists and get it */
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "get_refresh_melt",
+ params);
+ if (PGRES_TUPLES_OK != PQresultStatus (result))
+ {
+ BREAK_DB_ERR (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ nrows = PQntuples (result);
+ if (0 == nrows)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "get_refresh_melt() returned 0 matching rows\n");
+ PQclear (result);
+ return GNUNET_NO;
+ }
+ GNUNET_assert (1 == nrows); /* due to primary key constraint */
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("coin_pub", &coin.coin_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_sig", &coin_sig),
+ TALER_PQ_result_spec_amount ("amount_with_fee", &amount_with_fee),
+ TALER_PQ_result_spec_amount ("melt_fee", &melt_fee),
+ GNUNET_PQ_result_spec_end
+ };
+ if (GNUNET_OK != GNUNET_PQ_extract_result (result, rs, 0))
+ {
+ GNUNET_break (0);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+ }
+ /* fetch the coin info and denomination info */
+ if (GNUNET_OK != get_known_coin (cls,
+ session,
+ &coin.coin_pub,
+ &coin))
+ return GNUNET_SYSERR;
+ if (NULL == melt)
+ {
+ GNUNET_CRYPTO_rsa_signature_free (coin.denom_sig.rsa_signature);
+ GNUNET_CRYPTO_rsa_public_key_free (coin.denom_pub.rsa_public_key);
+ return GNUNET_OK;
+ }
+ melt->coin = coin;
+ melt->coin_sig = coin_sig;
+ melt->session_hash = *session_hash;
+ melt->amount_with_fee = amount_with_fee;
+ melt->melt_fee = melt_fee;
+ return GNUNET_OK;
+}
+
+
+/**
+ * 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_EXCHANGEDB_Session *session,
+ const struct GNUNET_HashCode *session_hash,
+ uint16_t num_newcoins,
+ const struct TALER_DenominationPublicKey *denom_pubs)
+{
+ unsigned int i;
+
+ for (i=0;i<(unsigned int) num_newcoins;i++)
+ {
+ uint16_t newcoin_off = (uint16_t) i;
+ PGresult *result;
+
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint16 (&newcoin_off),
+ GNUNET_PQ_query_param_auto_from_type (session_hash),
+ GNUNET_PQ_query_param_rsa_public_key (denom_pubs[i].rsa_public_key),
+ GNUNET_PQ_query_param_end
+ };
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "insert_refresh_order",
+ 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;
+}
+
+
+/**
+ * We allocated some @a denom_pubs information, but now need
+ * to abort. Free allocated memory.
+ *
+ * @param denom_pubs data to free (but not the array itself)
+ * @param denom_pubs_len length of @a denom_pubs array
+ */
+static void
+free_dpk_result (struct TALER_DenominationPublicKey *denom_pubs,
+ unsigned int denom_pubs_len)
+{
+ unsigned int i;
+
+ for (i=0;i<denom_pubs_len;i++)
+ {
+ GNUNET_CRYPTO_rsa_public_key_free (denom_pubs[i].rsa_public_key);
+ denom_pubs[i].rsa_public_key = NULL;
+ }
+}
+
+
+/**
+ * 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 num_newcoins size of the 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_EXCHANGEDB_Session *session,
+ const struct GNUNET_HashCode *session_hash,
+ uint16_t num_newcoins,
+ struct TALER_DenominationPublicKey *denom_pubs)
+{
+ unsigned int i;
+
+ for (i=0;i<(unsigned int) num_newcoins;i++)
+ {
+ uint16_t newcoin_off = (uint16_t) i;
+ PGresult *result;
+
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (session_hash),
+ GNUNET_PQ_query_param_uint16 (&newcoin_off),
+ GNUNET_PQ_query_param_end
+ };
+
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "get_refresh_order",
+ params);
+ }
+ if (PGRES_TUPLES_OK != PQresultStatus (result))
+ {
+ BREAK_DB_ERR (result);
+ PQclear (result);
+ free_dpk_result (denom_pubs, i);
+ return GNUNET_SYSERR;
+ }
+ if (0 == PQntuples (result))
+ {
+ PQclear (result);
+ /* FIXME: may want to distinguish between different error cases! */
+ free_dpk_result (denom_pubs, i);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_assert (1 == PQntuples (result));
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_rsa_public_key ("denom_pub",
+ &denom_pubs[i].rsa_public_key),
+ GNUNET_PQ_result_spec_end
+ };
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result, rs, 0))
+ {
+ PQclear (result);
+ GNUNET_break (0);
+ free_dpk_result (denom_pubs, i);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+ }
+ }
+ 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 cnc_index cut and choose 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_EXCHANGEDB_Session *session,
+ const struct GNUNET_HashCode *session_hash,
+ uint16_t cnc_index,
+ uint16_t num_newcoins,
+ const struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coins)
+{
+ char *rle;
+ size_t rle_size;
+ PGresult *result;
+ unsigned int i;
+ uint16_t coin_off;
+
+ for (i=0;i<(unsigned int) num_newcoins;i++)
+ {
+ rle = TALER_refresh_link_encrypted_encode (commit_coins[i].refresh_link,
+ &rle_size);
+ if (NULL == rle)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ coin_off = (uint16_t) i;
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (session_hash),
+ GNUNET_PQ_query_param_uint16 (&cnc_index),
+ GNUNET_PQ_query_param_uint16 (&coin_off),
+ GNUNET_PQ_query_param_fixed_size (rle,
+ rle_size),
+ GNUNET_PQ_query_param_fixed_size (commit_coins[i].coin_ev,
+ commit_coins[i].coin_ev_size),
+ GNUNET_PQ_query_param_end
+ };
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "insert_refresh_commit_coin",
+ params);
+ }
+ GNUNET_free (rle);
+ if (PGRES_COMMAND_OK != PQresultStatus (result))
+ {
+ BREAK_DB_ERR (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ if (0 != strcmp ("1", PQcmdTuples (result)))
+ {
+ GNUNET_break (0);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * We allocated some @a commit_coin information, but now need
+ * to abort. Free allocated memory.
+ *
+ * @param commit_coins data to free (but not the array itself)
+ * @param commit_coins_len length of @a commit_coins array
+ */
+static void
+free_cc_result (struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coins,
+ unsigned int commit_coins_len)
+{
+ unsigned int i;
+
+ for (i=0;i<commit_coins_len;i++)
+ {
+ GNUNET_free (commit_coins[i].refresh_link);
+ commit_coins[i].refresh_link = NULL;
+ GNUNET_free (commit_coins[i].coin_ev);
+ commit_coins[i].coin_ev = NULL;
+ commit_coins[i].coin_ev_size = 0;
+ }
+}
+
+
+/**
+ * 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 cnc_index set index (1st dimension)
+ * @param num_newcoins size of the @a commit_coins array
+ * @param[out] commit_coins array of coin commitments 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_EXCHANGEDB_Session *session,
+ const struct GNUNET_HashCode *session_hash,
+ uint16_t cnc_index,
+ uint16_t num_newcoins,
+ struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coins)
+{
+ unsigned int i;
+
+ for (i=0;i<(unsigned int) num_newcoins;i++)
+ {
+ uint16_t newcoin_off = (uint16_t) i;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (session_hash),
+ GNUNET_PQ_query_param_uint16 (&cnc_index),
+ GNUNET_PQ_query_param_uint16 (&newcoin_off),
+ GNUNET_PQ_query_param_end
+ };
+ void *c_buf;
+ size_t c_buf_size;
+ void *rl_buf;
+ size_t rl_buf_size;
+ struct TALER_RefreshLinkEncrypted *rl;
+ PGresult *result;
+
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "get_refresh_commit_coin",
+ params);
+ if (PGRES_TUPLES_OK != PQresultStatus (result))
+ {
+ BREAK_DB_ERR (result);
+ PQclear (result);
+ free_cc_result (commit_coins, i);
+ return GNUNET_SYSERR;
+ }
+ if (0 == PQntuples (result))
+ {
+ PQclear (result);
+ free_cc_result (commit_coins, i);
+ return GNUNET_NO;
+ }
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_variable_size ("link_vector_enc",
+ &rl_buf,
+ &rl_buf_size),
+ GNUNET_PQ_result_spec_variable_size ("coin_ev",
+ &c_buf,
+ &c_buf_size),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_YES !=
+ GNUNET_PQ_extract_result (result, rs, 0))
+ {
+ PQclear (result);
+ free_cc_result (commit_coins, i);
+ return GNUNET_SYSERR;
+ }
+ }
+ PQclear (result);
+ if (rl_buf_size < sizeof (struct TALER_CoinSpendPrivateKeyP))
+ {
+ GNUNET_free (c_buf);
+ GNUNET_free (rl_buf);
+ free_cc_result (commit_coins, i);
+ return GNUNET_SYSERR;
+ }
+ rl = TALER_refresh_link_encrypted_decode (rl_buf,
+ rl_buf_size);
+ GNUNET_free (rl_buf);
+ commit_coins[i].refresh_link = rl;
+ commit_coins[i].coin_ev = c_buf;
+ commit_coins[i].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 cnc_index cut and choose index (1st dimension)
+ * @param num_links size of the @a links array to return
+ * @param[out] links array of link information to store return
+ * @return #GNUNET_SYSERR on internal error, #GNUNET_OK on success
+ */
+static int
+postgres_insert_refresh_commit_links (void *cls,
+ struct TALER_EXCHANGEDB_Session *session,
+ const struct GNUNET_HashCode *session_hash,
+ uint16_t cnc_index,
+ uint16_t num_links,
+ const struct TALER_RefreshCommitLinkP *links)
+{
+ uint16_t i;
+
+ for (i=0;i<num_links;i++)
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (session_hash),
+ GNUNET_PQ_query_param_auto_from_type (&links[i].transfer_pub),
+ GNUNET_PQ_query_param_uint16 (&cnc_index),
+ GNUNET_PQ_query_param_uint16 (&i),
+ GNUNET_PQ_query_param_auto_from_type (&links[i].shared_secret_enc),
+ GNUNET_PQ_query_param_end
+ };
+
+ PGresult *result = GNUNET_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 cnc_index cut and choose index (1st dimension)
+ * @param num_links size of the @a commit_link array
+ * @param[out] links 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_EXCHANGEDB_Session *session,
+ const struct GNUNET_HashCode *session_hash,
+ uint16_t cnc_index,
+ uint16_t num_links,
+ struct TALER_RefreshCommitLinkP *links)
+{
+ uint16_t i;
+
+ for (i=0;i<num_links;i++)
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (session_hash),
+ GNUNET_PQ_query_param_uint16 (&cnc_index),
+ GNUNET_PQ_query_param_uint16 (&i),
+ GNUNET_PQ_query_param_end
+ };
+ PGresult *result;
+
+ result = GNUNET_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 GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("transfer_pub",
+ &links[i].transfer_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("link_secret_enc",
+ &links[i].shared_secret_enc),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_YES !=
+ GNUNET_PQ_extract_result (result, rs, 0))
+ {
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ }
+ PQclear (result);
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Get all of the information from the given melt commit operation.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param session database connection to use
+ * @param session_hash hash to identify refresh session
+ * @return NULL if the @a session_hash does not correspond to any known melt
+ * operation
+ */
+static struct TALER_EXCHANGEDB_MeltCommitment *
+postgres_get_melt_commitment (void *cls,
+ struct TALER_EXCHANGEDB_Session *session,
+ const struct GNUNET_HashCode *session_hash)
+{
+ struct TALER_EXCHANGEDB_RefreshSession rs;
+ struct TALER_EXCHANGEDB_MeltCommitment *mc;
+ uint16_t cnc_index;
+ unsigned int i;
+
+ if (GNUNET_OK !=
+ postgres_get_refresh_session (cls,
+ session,
+ session_hash,
+ &rs))
+ return NULL;
+ mc = GNUNET_new (struct TALER_EXCHANGEDB_MeltCommitment);
+ mc->num_newcoins = rs.num_newcoins;
+ mc->num_oldcoins = rs.num_oldcoins;
+ mc->melts = GNUNET_malloc (mc->num_oldcoins *
+ sizeof (struct TALER_EXCHANGEDB_RefreshMelt));
+ for (i=0;i<mc->num_oldcoins;i++)
+ if (GNUNET_OK !=
+ postgres_get_refresh_melt (cls,
+ session,
+ session_hash,
+ (uint16_t) i,
+ &mc->melts[i]))
+ goto cleanup;
+ mc->denom_pubs = GNUNET_malloc (mc->num_newcoins *
+ sizeof (struct TALER_DenominationPublicKey));
+ if (GNUNET_OK !=
+ postgres_get_refresh_order (cls,
+ session,
+ session_hash,
+ mc->num_newcoins,
+ mc->denom_pubs))
+ goto cleanup;
+ for (cnc_index=0;cnc_index<TALER_CNC_KAPPA;cnc_index++)
+ {
+ mc->commit_coins[cnc_index]
+ = GNUNET_malloc (mc->num_newcoins *
+ sizeof (struct TALER_EXCHANGEDB_RefreshCommitCoin));
+ if (GNUNET_OK !=
+ postgres_get_refresh_commit_coins (cls,
+ session,
+ session_hash,
+ cnc_index,
+ mc->num_newcoins,
+ mc->commit_coins[cnc_index]))
+ goto cleanup;
+ mc->commit_links[cnc_index]
+ = GNUNET_malloc (mc->num_oldcoins *
+ sizeof (struct TALER_RefreshCommitLinkP));
+ if (GNUNET_OK !=
+ postgres_get_refresh_commit_links (cls,
+ session,
+ session_hash,
+ cnc_index,
+ mc->num_oldcoins,
+ mc->commit_links[cnc_index]))
+ goto cleanup;
+ }
+ return mc;
+
+ cleanup:
+ common_free_melt_commitment (cls, mc);
+ return NULL;
+}
+
+
+/**
+ * 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_out (void *cls,
+ struct TALER_EXCHANGEDB_Session *session,
+ const struct GNUNET_HashCode *session_hash,
+ uint16_t newcoin_index,
+ const struct TALER_DenominationSignature *ev_sig)
+{
+ PGresult *result;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (session_hash),
+ GNUNET_PQ_query_param_uint16 (&newcoin_index),
+ GNUNET_PQ_query_param_rsa_signature (ev_sig->rsa_signature),
+ GNUNET_PQ_query_param_end
+ };
+
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "insert_refresh_out",
+ params);
+ 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 session_hash refresh session to get linkage data for
+ * @return all known link data for the session
+ */
+static struct TALER_EXCHANGEDB_LinkDataList *
+postgres_get_link_data_list (void *cls,
+ struct TALER_EXCHANGEDB_Session *session,
+ const struct GNUNET_HashCode *session_hash)
+{
+ struct TALER_EXCHANGEDB_LinkDataList *ldl;
+ struct TALER_EXCHANGEDB_LinkDataList *pos;
+ int i;
+ int nrows;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (session_hash),
+ GNUNET_PQ_query_param_end
+ };
+ PGresult *result;
+
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "get_link",
+ params);
+
+ ldl = NULL;
+ if (PGRES_TUPLES_OK != PQresultStatus (result))
+ {
+ BREAK_DB_ERR (result);
+ PQclear (result);
+ return NULL;
+ }
+ nrows = PQntuples (result);
+ if (0 == nrows)
+ {
+ PQclear (result);
+ return NULL;
+ }
+
+ for (i = 0; i < nrows; i++)
+ {
+ struct TALER_RefreshLinkEncrypted *link_enc;
+ struct GNUNET_CRYPTO_RsaPublicKey *denom_pub;
+ struct GNUNET_CRYPTO_RsaSignature *sig;
+ void *ld_buf;
+ size_t ld_buf_size;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_variable_size ("link_vector_enc",
+ &ld_buf,
+ &ld_buf_size),
+ GNUNET_PQ_result_spec_rsa_signature ("ev_sig",
+ &sig),
+ GNUNET_PQ_result_spec_rsa_public_key ("denom_pub",
+ &denom_pub),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_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_EddsaPrivateKey))
+ {
+ PQclear (result);
+ GNUNET_free (ld_buf);
+ common_free_link_data_list (cls,
+ ldl);
+ return NULL;
+ }
+ link_enc = TALER_refresh_link_encrypted_decode (ld_buf,
+ ld_buf_size);
+ GNUNET_free (ld_buf);
+ pos = GNUNET_new (struct TALER_EXCHANGEDB_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 tdc function to call for each session the coin was melted into
+ * @param tdc_cls closure for @a tdc
+ * @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_EXCHANGEDB_Session *session,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ TALER_EXCHANGEDB_TransferDataCallback tdc,
+ void *tdc_cls)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (coin_pub),
+ GNUNET_PQ_query_param_end
+ };
+ PGresult *result;
+ int nrows;
+ int i;
+
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "get_transfer",
+ params);
+ if (PGRES_TUPLES_OK !=
+ PQresultStatus (result))
+ {
+ BREAK_DB_ERR (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ nrows = PQntuples (result);
+ if (0 == nrows)
+ {
+ /* no matches found */
+ PQclear (result);
+ return GNUNET_NO;
+ }
+ for (i=0;i<nrows;i++)
+ {
+ struct GNUNET_HashCode session_hash;
+ struct TALER_TransferPublicKeyP transfer_pub;
+ struct TALER_EncryptedLinkSecretP shared_secret_enc;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("transfer_pub", &transfer_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("link_secret_enc", &shared_secret_enc),
+ GNUNET_PQ_result_spec_auto_from_type ("session_hash", &session_hash),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result, rs, 0))
+ {
+ PQclear (result);
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ tdc (tdc_cls,
+ &session_hash,
+ &transfer_pub,
+ &shared_secret_enc);
+ }
+ 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_EXCHANGEDB_TransactionList *
+postgres_get_coin_transactions (void *cls,
+ struct TALER_EXCHANGEDB_Session *session,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub)
+{
+ struct TALER_EXCHANGEDB_TransactionList *head;
+
+ head = NULL;
+ /* check deposits */
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&coin_pub->eddsa_pub),
+ GNUNET_PQ_query_param_end
+ };
+ int nrows;
+ int i;
+ PGresult *result;
+ struct TALER_EXCHANGEDB_TransactionList *tl;
+
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "get_deposit_with_coin_pub",
+ params);
+ if (PGRES_TUPLES_OK != PQresultStatus (result))
+ {
+ QUERY_ERR (result);
+ PQclear (result);
+ goto cleanup;
+ }
+ nrows = PQntuples (result);
+ for (i = 0; i < nrows; i++)
+ {
+ struct TALER_EXCHANGEDB_Deposit *deposit;
+
+ deposit = GNUNET_new (struct TALER_EXCHANGEDB_Deposit);
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_rsa_public_key ("denom_pub",
+ &deposit->coin.denom_pub.rsa_public_key),
+ GNUNET_PQ_result_spec_rsa_signature ("denom_sig",
+ &deposit->coin.denom_sig.rsa_signature),
+ GNUNET_PQ_result_spec_uint64 ("transaction_id",
+ &deposit->transaction_id),
+ TALER_PQ_result_spec_amount ("amount_with_fee",
+ &deposit->amount_with_fee),
+ TALER_PQ_result_spec_amount ("deposit_fee",
+ &deposit->deposit_fee),
+ GNUNET_PQ_result_spec_absolute_time ("timestamp",
+ &deposit->timestamp),
+ GNUNET_PQ_result_spec_absolute_time ("refund_deadline",
+ &deposit->refund_deadline),
+ GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
+ &deposit->merchant_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("h_contract",
+ &deposit->h_contract),
+ GNUNET_PQ_result_spec_auto_from_type ("h_wire",
+ &deposit->h_wire),
+ TALER_PQ_result_spec_json ("wire",
+ &deposit->wire),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+ &deposit->csig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result, rs, i))
+ {
+ GNUNET_break (0);
+ GNUNET_free (deposit);
+ PQclear (result);
+ goto cleanup;
+ }
+ deposit->coin.coin_pub = *coin_pub;
+ }
+ tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
+ tl->next = head;
+ tl->type = TALER_EXCHANGEDB_TT_DEPOSIT;
+ tl->details.deposit = deposit;
+ head = tl;
+ continue;
+ }
+ PQclear (result);
+ }
+ /* Handle refreshing */
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&coin_pub->eddsa_pub),
+ GNUNET_PQ_query_param_end
+ };
+ int nrows;
+ int i;
+ PGresult *result;
+ struct TALER_EXCHANGEDB_TransactionList *tl;
+
+ /* check if the melt record exists and get it */
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "get_refresh_melt_by_coin",
+ params);
+ if (PGRES_TUPLES_OK != PQresultStatus (result))
+ {
+ BREAK_DB_ERR (result);
+ PQclear (result);
+ goto cleanup;
+ }
+ nrows = PQntuples (result);
+ for (i=0;i<nrows;i++)
+ {
+ struct TALER_EXCHANGEDB_RefreshMelt *melt;
+
+ melt = GNUNET_new (struct TALER_EXCHANGEDB_RefreshMelt);
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("session_hash",
+ &melt->session_hash),
+ /* oldcoin_index not needed */
+ GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+ &melt->coin_sig),
+ TALER_PQ_result_spec_amount ("amount_with_fee",
+ &melt->amount_with_fee),
+ TALER_PQ_result_spec_amount ("melt_fee",
+ &melt->melt_fee),
+ GNUNET_PQ_result_spec_end
+ };
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result, rs, 0))
+ {
+ GNUNET_break (0);
+ GNUNET_free (melt);
+ PQclear (result);
+ goto cleanup;
+ }
+ melt->coin.coin_pub = *coin_pub;
+ }
+ tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
+ tl->next = head;
+ tl->type = TALER_EXCHANGEDB_TT_REFRESH_MELT;
+ tl->details.melt = melt;
+ head = tl;
+ continue;
+ }
+ PQclear (result);
+ }
+ return head;
+ cleanup:
+ if (NULL != head)
+ common_free_coin_transaction_list (cls,
+ head);
+ return NULL;
+}
+
+
+/**
+ * Lookup the list of Taler transactions that were aggregated
+ * into a wire transfer by the respective @a wtid.
+ *
+ * @param cls closure
+ * @param session database connection
+ * @param wtid the raw wire transfer identifier we used
+ * @param cb function to call on each transaction found
+ * @param cb_cls closure for @a cb
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on database errors,
+ * #GNUNET_NO if we found no results
+ */
+static int
+postgres_lookup_wire_transfer (void *cls,
+ struct TALER_EXCHANGEDB_Session *session,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ TALER_EXCHANGEDB_WireTransferDataCallback cb,
+ void *cb_cls)
+{
+ PGresult *result;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (wtid),
+ GNUNET_PQ_query_param_end
+ };
+ int nrows;
+ int i;
+
+ /* check if the melt record exists and get it */
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "lookup_transactions",
+ params);
+ if (PGRES_TUPLES_OK != PQresultStatus (result))
+ {
+ BREAK_DB_ERR (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ nrows = PQntuples (result);
+ if (0 == nrows)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "lookup_wire_transfer() returned 0 matching rows\n");
+ PQclear (result);
+ return GNUNET_NO;
+ }
+ for (i=0;i<nrows;i++)
+ {
+ struct GNUNET_HashCode h_contract;
+ struct GNUNET_HashCode h_wire;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_MerchantPublicKeyP merchant_pub;
+ uint64_t transaction_id;
+ struct GNUNET_TIME_Absolute exec_time;
+ struct TALER_Amount coin_amount;
+ struct TALER_Amount coin_fee;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("h_contract", &h_contract),
+ GNUNET_PQ_result_spec_auto_from_type ("h_wire", &h_wire),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_pub", &coin_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("merchant_pub", &merchant_pub),
+ GNUNET_PQ_result_spec_uint64 ("transaction_id", &transaction_id),
+ GNUNET_PQ_result_spec_absolute_time ("execution_time", &exec_time),
+ TALER_PQ_result_spec_amount ("coin_amount", &coin_amount),
+ TALER_PQ_result_spec_amount ("coin_fee", &coin_fee),
+ GNUNET_PQ_result_spec_end
+ };
+ if (GNUNET_OK != GNUNET_PQ_extract_result (result, rs, i))
+ {
+ GNUNET_break (0);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ cb (cb_cls,
+ &merchant_pub,
+ &h_wire,
+ &h_contract,
+ transaction_id,
+ &coin_pub,
+ &coin_amount,
+ &coin_fee);
+ }
+ PQclear (result);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Try to find the wire transfer details for a deposit operation.
+ * If we did not execute the deposit yet, return when it is supposed
+ * to be executed.
+ *
+ * @param cls closure
+ * @param session database connection
+ * @param h_contract hash of the contract
+ * @param h_wire hash of merchant wire details
+ * @param coin_pub public key of deposited coin
+ * @param merchant_pub merchant public key
+ * @param transaction_id transaction identifier
+ * @param cb function to call with the result
+ * @param cb_cls closure to pass to @a cb
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on DB errors,
+ * #GNUNET_NO if nothing was found
+ */
+static int
+postgres_wire_lookup_deposit_wtid (void *cls,
+ struct TALER_EXCHANGEDB_Session *session,
+ const struct GNUNET_HashCode *h_contract,
+ const struct GNUNET_HashCode *h_wire,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ uint64_t transaction_id,
+ TALER_EXCHANGEDB_DepositWtidCallback cb,
+ void *cb_cls)
+{
+ PGresult *result;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (h_contract),
+ GNUNET_PQ_query_param_auto_from_type (h_wire),
+ GNUNET_PQ_query_param_uint64 (&transaction_id),
+ GNUNET_PQ_query_param_auto_from_type (merchant_pub),
+ GNUNET_PQ_query_param_end
+ };
+ int nrows;
+
+ /* check if the melt record exists and get it */
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "lookup_deposit_wtid",
+ params);
+ if (PGRES_TUPLES_OK != PQresultStatus (result))
+ {
+ BREAK_DB_ERR (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ nrows = PQntuples (result);
+ if (0 == nrows)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "lookup_deposit_wtid returned 0 matching rows\n");
+ PQclear (result);
+
+ /* Check if transaction exists in deposits, so that we just
+ do not have a WTID yet, if so, do call the CB with a NULL wtid
+ and return GNUNET_YES! */
+ {
+ struct GNUNET_PQ_QueryParam params2[] = {
+ GNUNET_PQ_query_param_auto_from_type (coin_pub),
+ GNUNET_PQ_query_param_uint64 (&transaction_id),
+ GNUNET_PQ_query_param_auto_from_type (merchant_pub),
+ GNUNET_PQ_query_param_auto_from_type (h_contract),
+ GNUNET_PQ_query_param_auto_from_type (h_wire),
+ GNUNET_PQ_query_param_end
+ };
+
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "get_deposit_for_wtid",
+ params2);
+ if (PGRES_TUPLES_OK != PQresultStatus (result))
+ {
+ BREAK_DB_ERR (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ }
+ nrows = PQntuples (result);
+ if (0 == nrows)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "get_deposit_for_wtid returned 0 matching rows\n");
+ PQclear (result);
+ return GNUNET_NO;
+ }
+
+ /* Ok, we're aware of the transaction, but it has not yet been
+ executed */
+ {
+ struct GNUNET_TIME_Absolute exec_time;
+ struct TALER_Amount coin_amount;
+ struct TALER_Amount coin_fee;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_result_spec_amount ("amount_with_fee", &coin_amount),
+ TALER_PQ_result_spec_amount ("deposit_fee", &coin_fee),
+ GNUNET_PQ_result_spec_absolute_time ("wire_deadline", &exec_time),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK != GNUNET_PQ_extract_result (result, rs, 0))
+ {
+ GNUNET_break (0);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ cb (cb_cls,
+ NULL,
+ &coin_amount,
+ &coin_fee,
+ exec_time);
+ PQclear (result);
+ return GNUNET_YES;
+ }
+ }
+ if (1 != nrows)
+ {
+ GNUNET_break (0);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ {
+ struct TALER_WireTransferIdentifierRawP wtid;
+ struct GNUNET_TIME_Absolute exec_time;
+ struct TALER_Amount coin_amount;
+ struct TALER_Amount coin_fee;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("wtid_raw", &wtid),
+ GNUNET_PQ_result_spec_absolute_time ("execution_time", &exec_time),
+ TALER_PQ_result_spec_amount ("coin_amount", &coin_amount),
+ TALER_PQ_result_spec_amount ("coin_fee", &coin_fee),
+ GNUNET_PQ_result_spec_end
+ };
+ if (GNUNET_OK != GNUNET_PQ_extract_result (result, rs, 0))
+ {
+ GNUNET_break (0);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ cb (cb_cls,
+ &wtid,
+ &coin_amount,
+ &coin_fee,
+ exec_time);
+ }
+ PQclear (result);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called to insert aggregation information into the DB.
+ *
+ * @param cls closure
+ * @param session database connection
+ * @param wtid the raw wire transfer identifier we used
+ * @param merchant_pub public key of the merchant (should be same for all callbacks with the same @e cls)
+ * @param h_wire hash of wire transfer details of the merchant (should be same for all callbacks with the same @e cls)
+ * @param h_contract which contract was this payment about
+ * @param transaction_id merchant's transaction ID for the payment
+ * @param coin_pub which public key was this payment about
+ * @param coin_value amount contributed by this coin in total
+ * @param coin_fee deposit fee charged by exchange for this coin
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on DB errors
+ */
+static int
+postgres_insert_aggregation_tracking (void *cls,
+ struct TALER_EXCHANGEDB_Session *session,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct GNUNET_HashCode *h_wire,
+ const struct GNUNET_HashCode *h_contract,
+ uint64_t transaction_id,
+ struct GNUNET_TIME_Absolute execution_time,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_Amount *coin_value,
+ const struct TALER_Amount *coin_fee)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_contract),
+ GNUNET_PQ_query_param_auto_from_type (h_wire),
+ GNUNET_PQ_query_param_auto_from_type (coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (merchant_pub),
+ GNUNET_PQ_query_param_uint64 (&transaction_id),
+ GNUNET_PQ_query_param_auto_from_type (wtid),
+ GNUNET_PQ_query_param_absolute_time (&execution_time),
+ TALER_PQ_query_param_amount (coin_value),
+ TALER_PQ_query_param_amount (coin_fee),
+ GNUNET_PQ_query_param_end
+ };
+ PGresult *result;
+
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "insert_aggregation_tracking",
+ 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);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called to insert wire transfer commit data into the DB.
+ *
+ * @param cls closure
+ * @param session database connection
+ * @param type type of the wire transfer (i.e. "sepa")
+ * @param buf buffer with wire transfer preparation data
+ * @param buf_size number of bytes in @a buf
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on DB errors
+ */
+static int
+postgres_wire_prepare_data_insert (void *cls,
+ struct TALER_EXCHANGEDB_Session *session,
+ const char *type,
+ const char *buf,
+ size_t buf_size)
+{
+ PGresult *result;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (type),
+ GNUNET_PQ_query_param_fixed_size (buf, buf_size),
+ GNUNET_PQ_query_param_end
+ };
+
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "wire_prepare_data_insert",
+ params);
+ if (PGRES_COMMAND_OK != PQresultStatus (result))
+ {
+ BREAK_DB_ERR (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called to mark wire transfer commit data as finished.
+ *
+ * @param cls closure
+ * @param session database connection
+ * @param rowid which entry to mark as finished
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on DB errors
+ */
+static int
+postgres_wire_prepare_data_mark_finished (void *cls,
+ struct TALER_EXCHANGEDB_Session *session,
+ unsigned long long rowid)
+{
+ uint64_t serial_id = rowid;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ PGresult *result;
+
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "wire_prepare_data_mark_done",
+ params);
+ if (PGRES_COMMAND_OK !=
+ PQresultStatus (result))
+ {
+ BREAK_DB_ERR (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called to get an unfinished wire transfer
+ * preparation data. Fetches at most one item.
+ *
+ * @param cls closure
+ * @param session database connection
+ * @param type type fo the wire transfer (i.e. "sepa")
+ * @param cb function to call for ONE unfinished item
+ * @param cb_cls closure for @a cb
+ * @return #GNUNET_OK on success,
+ * #GNUNET_NO if there are no entries,
+ * #GNUNET_SYSERR on DB errors
+ */
+static int
+postgres_wire_prepare_data_get (void *cls,
+ struct TALER_EXCHANGEDB_Session *session,
+ const char *type,
+ TALER_EXCHANGEDB_WirePreparationCallback cb,
+ void *cb_cls)
+{
+ PGresult *result;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (type),
+ GNUNET_PQ_query_param_end
+ };
+
+ result = GNUNET_PQ_exec_prepared (session->conn,
+ "wire_prepare_data_get",
+ params);
+ if (PGRES_TUPLES_OK != PQresultStatus (result))
+ {
+ QUERY_ERR (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ if (0 == PQntuples (result))
+ {
+ PQclear (result);
+ return GNUNET_NO;
+ }
+ if (1 != PQntuples (result))
+ {
+ GNUNET_break (0);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+
+ {
+ uint64_t serial_id;
+ void *buf = NULL;
+ size_t buf_size;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("serial_id",
+ &serial_id),
+ GNUNET_PQ_result_spec_variable_size ("buf",
+ &buf,
+ &buf_size),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ 0))
+ {
+ GNUNET_break (0);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ cb (cb_cls,
+ serial_id,
+ buf,
+ buf_size);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+ PQclear (result);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Initialize Postgres database subsystem.
+ *
+ * @param cls a configuration instance
+ * @return NULL on error, otherwise a `struct TALER_EXCHANGEDB_Plugin`
+ */
+void *
+libtaler_plugin_exchangedb_postgres_init (void *cls)
+{
+ struct GNUNET_CONFIGURATION_Handle *cfg = cls;
+ struct PostgresClosure *pg;
+ struct TALER_EXCHANGEDB_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");
+ GNUNET_free (pg);
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "exchangedb-postgres",
+ "db_conn_str",
+ &pg->connection_cfg_str))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchangedb-postgres",
+ "db_conn_str");
+ GNUNET_free (pg);
+ return NULL;
+ }
+ plugin = GNUNET_new (struct TALER_EXCHANGEDB_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->insert_denomination_info = &postgres_insert_denomination_info;
+ plugin->get_denomination_info = &postgres_get_denomination_info;
+ plugin->reserve_get = &postgres_reserve_get;
+ plugin->reserves_in_insert = &postgres_reserves_in_insert;
+ plugin->get_withdraw_info = &postgres_get_withdraw_info;
+ plugin->insert_withdraw_info = &postgres_insert_withdraw_info;
+ plugin->get_reserve_history = &postgres_get_reserve_history;
+ plugin->free_reserve_history = &common_free_reserve_history;
+ plugin->have_deposit = &postgres_have_deposit;
+ plugin->mark_deposit_tiny = &postgres_mark_deposit_tiny;
+ plugin->mark_deposit_done = &postgres_mark_deposit_done;
+ plugin->get_ready_deposit = &postgres_get_ready_deposit;
+ plugin->iterate_matching_deposits = &postgres_iterate_matching_deposits;
+ 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->get_melt_commitment = &postgres_get_melt_commitment;
+ plugin->free_melt_commitment = &common_free_melt_commitment;
+ plugin->insert_refresh_out = &postgres_insert_refresh_out;
+ 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->get_coin_transactions = &postgres_get_coin_transactions;
+ plugin->free_coin_transaction_list = &common_free_coin_transaction_list;
+ plugin->lookup_wire_transfer = &postgres_lookup_wire_transfer;
+ plugin->wire_lookup_deposit_wtid = &postgres_wire_lookup_deposit_wtid;
+ plugin->insert_aggregation_tracking = &postgres_insert_aggregation_tracking;
+ plugin->wire_prepare_data_insert = &postgres_wire_prepare_data_insert;
+ plugin->wire_prepare_data_mark_finished = &postgres_wire_prepare_data_mark_finished;
+ plugin->wire_prepare_data_get = &postgres_wire_prepare_data_get;
+ return plugin;
+}
+
+
+/**
+ * Shutdown Postgres database subsystem.
+ *
+ * @param cls a `struct TALER_EXCHANGEDB_Plugin`
+ * @return NULL (always)
+ */
+void *
+libtaler_plugin_exchangedb_postgres_done (void *cls)
+{
+ struct TALER_EXCHANGEDB_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_exchangedb_postgres.c */
diff --git a/src/exchangedb/test-exchange-db-postgres.conf b/src/exchangedb/test-exchange-db-postgres.conf
new file mode 100644
index 000000000..0822bab44
--- /dev/null
+++ b/src/exchangedb/test-exchange-db-postgres.conf
@@ -0,0 +1,8 @@
+[exchange]
+#The DB plugin to use
+DB = postgres
+
+[exchangedb-postgres]
+
+#The connection string the plugin has to use for connecting to the database
+DB_CONN_STR = postgres:///talercheck
diff --git a/src/exchangedb/test_exchangedb.c b/src/exchangedb/test_exchangedb.c
new file mode 100644
index 000000000..f2c473fd9
--- /dev/null
+++ b/src/exchangedb/test_exchangedb.c
@@ -0,0 +1,980 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015, 2016 GNUnet e.V.
+
+ 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 exchangedb/test_exchangedb.c
+ * @brief test cases for DB interaction functions
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ */
+#include "platform.h"
+#include "taler_exchangedb_lib.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_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"
+
+static struct TALER_EXCHANGEDB_Plugin *plugin;
+
+/**
+ * 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
+ * @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_EXCHANGEDB_Session *session,
+ const struct TALER_ReservePublicKeyP *pub,
+ uint64_t value,
+ uint32_t fraction,
+ const char *currency)
+{
+ struct TALER_EXCHANGEDB_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));
+
+ return GNUNET_OK;
+ drop:
+ return GNUNET_SYSERR;
+}
+
+
+struct DenomKeyPair
+{
+ struct TALER_DenominationPrivateKey priv;
+ struct TALER_DenominationPublicKey pub;
+};
+
+
+/**
+ * Destroy a denomination key pair. The key is not necessarily removed from the DB.
+ *
+ * @param dkp the keypair to destroy
+ */
+static void
+destroy_denom_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);
+}
+
+
+/**
+ * Create a denominaiton key pair by registering the denomination in the DB.
+ *
+ * @param size the size of the denomination key
+ * @param session the DB session
+ * @return the denominaiton key pair; NULL upon error
+ */
+static struct DenomKeyPair *
+create_denom_key_pair (unsigned int size,
+ struct TALER_EXCHANGEDB_Session *session,
+ const struct TALER_Amount *value,
+ const struct TALER_Amount *fee_withdraw,
+ const struct TALER_Amount *fee_deposit,
+ const struct TALER_Amount *fee_refresh)
+{
+ struct DenomKeyPair *dkp;
+ struct TALER_EXCHANGEDB_DenominationKeyIssueInformation dki;
+
+ 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);
+
+ /* Using memset() as fields like master key and signature
+ are not properly initialized for this test. */
+ memset (&dki,
+ 0,
+ sizeof (struct TALER_EXCHANGEDB_DenominationKeyIssueInformation));
+ dki.denom_pub = dkp->pub;
+ dki.issue.properties.start = GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_get ());
+ dki.issue.properties.expire_withdraw = GNUNET_TIME_absolute_hton
+ (GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get (),
+ GNUNET_TIME_UNIT_HOURS));
+ dki.issue.properties.expire_spend = GNUNET_TIME_absolute_hton
+ (GNUNET_TIME_absolute_add
+ (GNUNET_TIME_absolute_get (),
+ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_HOURS, 2)));
+ dki.issue.properties.expire_legal = GNUNET_TIME_absolute_hton
+ (GNUNET_TIME_absolute_add
+ (GNUNET_TIME_absolute_get (),
+ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_HOURS, 3)));
+ TALER_amount_hton (&dki.issue.properties.value, value);
+ TALER_amount_hton (&dki.issue.properties.fee_withdraw, fee_withdraw);
+ TALER_amount_hton (&dki.issue.properties.fee_deposit, fee_deposit);
+ TALER_amount_hton (&dki.issue.properties.fee_refresh, fee_refresh);
+ GNUNET_CRYPTO_rsa_public_key_hash (dkp->pub.rsa_public_key,
+ &dki.issue.properties.denom_hash);
+ if (GNUNET_OK !=
+ plugin->insert_denomination_info (plugin->cls,
+ session,
+ &dki.denom_pub,
+ &dki.issue))
+ {
+ GNUNET_break(0);
+ destroy_denom_key_pair (dkp);
+ return NULL;
+ }
+ return dkp;
+}
+
+static struct TALER_Amount value;
+static struct TALER_Amount fee_withdraw;
+static struct TALER_Amount fee_deposit;
+static struct TALER_Amount fee_refresh;
+static struct TALER_Amount amount_with_fee;
+
+static void
+free_refresh_commit_coins_array(struct TALER_EXCHANGEDB_RefreshCommitCoin
+ *commit_coins,
+ unsigned int size)
+{
+ unsigned int cnt;
+ struct TALER_EXCHANGEDB_RefreshCommitCoin *ccoin;
+ struct TALER_RefreshLinkEncrypted *rlink;
+
+ for (cnt = 0; cnt < size; cnt++)
+ {
+ ccoin = &commit_coins[cnt];
+ GNUNET_free_non_null (ccoin->coin_ev);
+ rlink = (struct TALER_RefreshLinkEncrypted *) ccoin->refresh_link;
+ GNUNET_free_non_null (rlink);
+ }
+ GNUNET_free (commit_coins);
+}
+
+#define MELT_NEW_COINS 5
+
+static int
+test_refresh_commit_coins (struct TALER_EXCHANGEDB_Session *session,
+ struct TALER_EXCHANGEDB_RefreshSession *refresh_session,
+ const struct GNUNET_HashCode *session_hash)
+{
+ struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coins;
+ struct TALER_EXCHANGEDB_RefreshCommitCoin *ret_commit_coins;
+ struct TALER_EXCHANGEDB_RefreshCommitCoin *a_ccoin;
+ struct TALER_RefreshLinkEncrypted *a_rlink;
+ struct TALER_EXCHANGEDB_RefreshCommitCoin *b_ccoin;
+ struct TALER_RefreshLinkEncrypted *b_rlink;
+ size_t size;
+ unsigned int cnt;
+ uint16_t cnc_index;
+ int ret;
+
+ #define COIN_ENC_MAX_SIZE 512
+ ret = GNUNET_SYSERR;
+ ret_commit_coins = NULL;
+ commit_coins = GNUNET_new_array (MELT_NEW_COINS,
+ struct TALER_EXCHANGEDB_RefreshCommitCoin);
+ cnc_index = (uint16_t) GNUNET_CRYPTO_random_u32
+ (GNUNET_CRYPTO_QUALITY_WEAK, GNUNET_MIN (MELT_NEW_COINS, UINT16_MAX));
+ for (cnt=0; cnt < MELT_NEW_COINS; cnt++)
+ {
+ struct TALER_EXCHANGEDB_RefreshCommitCoin *ccoin;
+ struct TALER_RefreshLinkEncrypted *rlink;
+ ccoin = &commit_coins[cnt];
+ size = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
+ COIN_ENC_MAX_SIZE);
+ rlink = GNUNET_malloc (sizeof (struct TALER_RefreshLinkEncrypted) + size);
+ ccoin->refresh_link = rlink;
+ ccoin->coin_ev_size = GNUNET_CRYPTO_random_u64
+ (GNUNET_CRYPTO_QUALITY_WEAK, COIN_ENC_MAX_SIZE);
+ ccoin->coin_ev = GNUNET_malloc (ccoin->coin_ev_size);
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ ccoin->coin_ev,
+ ccoin->coin_ev_size);
+ rlink->blinding_key_enc_size = size;
+ RND_BLK (&rlink->coin_priv_enc);
+ rlink->blinding_key_enc = (const char *) &rlink[1];
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ (void *)rlink->blinding_key_enc,
+ rlink->blinding_key_enc_size);
+ }
+ FAILIF (GNUNET_OK !=
+ plugin->insert_refresh_commit_coins (plugin->cls,
+ session,
+ session_hash,
+ cnc_index,
+ MELT_NEW_COINS,
+ commit_coins));
+ ret_commit_coins = GNUNET_new_array (MELT_NEW_COINS,
+ struct TALER_EXCHANGEDB_RefreshCommitCoin);
+ FAILIF (GNUNET_OK !=
+ plugin->get_refresh_commit_coins (plugin->cls,
+ session,
+ session_hash,
+ cnc_index,
+ MELT_NEW_COINS,
+ ret_commit_coins));
+ /* compare the refresh commit coin arrays */
+ for (cnt = 0; cnt < MELT_NEW_COINS; cnt++)
+ {
+ a_ccoin = &commit_coins[cnt];
+ b_ccoin = &ret_commit_coins[cnt];
+ FAILIF (a_ccoin->coin_ev_size != b_ccoin->coin_ev_size);
+ FAILIF (0 != memcmp (a_ccoin->coin_ev,
+ a_ccoin->coin_ev,
+ a_ccoin->coin_ev_size));
+ a_rlink = a_ccoin->refresh_link;
+ b_rlink = b_ccoin->refresh_link;
+ FAILIF (a_rlink->blinding_key_enc_size != b_rlink->blinding_key_enc_size);
+ FAILIF (0 != memcmp (a_rlink->blinding_key_enc,
+ b_rlink->blinding_key_enc,
+ a_rlink->blinding_key_enc_size));
+ FAILIF (0 != memcmp (a_rlink->coin_priv_enc,
+ b_rlink->coin_priv_enc,
+ sizeof (a_rlink->coin_priv_enc)));
+ }
+ ret = GNUNET_OK;
+
+ drop:
+ if (NULL != ret_commit_coins)
+ free_refresh_commit_coins_array (ret_commit_coins, MELT_NEW_COINS);
+ if (NULL != commit_coins)
+ free_refresh_commit_coins_array (commit_coins, MELT_NEW_COINS);
+ return ret;
+}
+
+/**
+ * Function to test melting of coins as part of a refresh session
+ *
+ * @param session the database session
+ * @param refresh_session the refresh session
+ * @return #GNUNET_OK if everything went well; #GNUNET_SYSERR if not
+ */
+static int
+test_melting (struct TALER_EXCHANGEDB_Session *session)
+{
+#define MELT_OLD_COINS 10
+ struct TALER_EXCHANGEDB_RefreshSession refresh_session;
+ struct TALER_EXCHANGEDB_RefreshSession ret_refresh_session;
+ struct GNUNET_HashCode session_hash;
+ struct DenomKeyPair *dkp;
+ struct DenomKeyPair **new_dkp;
+ /* struct TALER_CoinPublicInfo *coins; */
+ struct TALER_EXCHANGEDB_RefreshMelt *melts;
+ struct TALER_DenominationPublicKey *new_denom_pubs;
+ struct TALER_DenominationPublicKey *ret_denom_pubs;
+ unsigned int cnt;
+ int ret;
+
+ ret = GNUNET_SYSERR;
+ RND_BLK (&refresh_session);
+ RND_BLK (&session_hash);
+ melts = NULL;
+ dkp = NULL;
+ new_dkp = NULL;
+ new_denom_pubs = NULL;
+ ret_denom_pubs = NULL;
+ /* create and test a refresh session */
+ refresh_session.num_oldcoins = MELT_OLD_COINS;
+ refresh_session.num_newcoins = 1;
+ refresh_session.noreveal_index = 1;
+ FAILIF (GNUNET_OK != plugin->create_refresh_session (plugin->cls,
+ session,
+ &session_hash,
+ &refresh_session));
+ FAILIF (GNUNET_OK != plugin->get_refresh_session (plugin->cls,
+ session,
+ &session_hash,
+ &ret_refresh_session));
+ FAILIF (0 != memcmp (&ret_refresh_session,
+ &refresh_session,
+ sizeof (refresh_session)));
+
+ /* create a denomination (value: 1; fraction: 100) */
+ dkp = create_denom_key_pair (512, session,
+ &value,
+ &fee_withdraw,
+ &fee_deposit,
+ &fee_refresh);
+ /* create MELT_OLD_COINS number of refresh melts */
+ melts = GNUNET_new_array (MELT_OLD_COINS, struct TALER_EXCHANGEDB_RefreshMelt);
+ for (cnt=0; cnt < MELT_OLD_COINS; cnt++)
+ {
+ struct GNUNET_HashCode hc;
+
+ RND_BLK (&melts[cnt].coin.coin_pub);
+ GNUNET_CRYPTO_hash (&melts[cnt].coin.coin_pub,
+ sizeof (melts[cnt].coin.coin_pub),
+ &hc);
+ melts[cnt].coin.denom_sig.rsa_signature =
+ GNUNET_CRYPTO_rsa_sign_fdh (dkp->priv.rsa_private_key,
+ &hc);
+ melts[cnt].coin.denom_pub = dkp->pub;
+ RND_BLK (&melts[cnt].coin_sig);
+ melts[cnt].session_hash = session_hash;
+ melts[cnt].amount_with_fee = amount_with_fee;
+ melts[cnt].melt_fee = fee_refresh;
+ FAILIF (GNUNET_OK != plugin->insert_refresh_melt (plugin->cls,
+ session,
+ cnt,
+ &melts[cnt]));
+ }
+ for (cnt = 0; cnt < MELT_OLD_COINS; cnt++)
+ {
+ struct TALER_EXCHANGEDB_RefreshMelt ret_melt;
+ FAILIF (GNUNET_OK != plugin->get_refresh_melt (plugin->cls,
+ session,
+ &session_hash,
+ cnt,
+ &ret_melt));
+ FAILIF (0 != GNUNET_CRYPTO_rsa_signature_cmp
+ (ret_melt.coin.denom_sig.rsa_signature,
+ melts[cnt].coin.denom_sig.rsa_signature));
+ FAILIF (0 != memcmp (&ret_melt.coin.coin_pub,
+ &melts[cnt].coin.coin_pub,
+ sizeof (ret_melt.coin.coin_pub)));
+ FAILIF (0 != GNUNET_CRYPTO_rsa_public_key_cmp
+ (ret_melt.coin.denom_pub.rsa_public_key,
+ melts[cnt].coin.denom_pub.rsa_public_key));
+ FAILIF (0 != memcmp (&ret_melt.coin_sig,
+ &melts[cnt].coin_sig,
+ sizeof (ret_melt.coin_sig)));
+ FAILIF (0 != memcmp (&ret_melt.session_hash,
+ &melts[cnt].session_hash,
+ sizeof (ret_melt.session_hash)));
+ FAILIF (0 != TALER_amount_cmp (&ret_melt.amount_with_fee,
+ &melts[cnt].amount_with_fee));
+ FAILIF (0 != TALER_amount_cmp (&ret_melt.melt_fee,
+ &melts[cnt].melt_fee));
+ GNUNET_CRYPTO_rsa_signature_free (ret_melt.coin.denom_sig.rsa_signature);
+ GNUNET_CRYPTO_rsa_public_key_free (ret_melt.coin.denom_pub.rsa_public_key);
+ }
+ new_dkp = GNUNET_new_array (MELT_NEW_COINS, struct DenomKeyPair *);
+ new_denom_pubs = GNUNET_new_array (MELT_NEW_COINS,
+ struct TALER_DenominationPublicKey);
+ for (cnt=0; cnt < MELT_NEW_COINS; cnt++)
+ {
+ new_dkp[cnt] = create_denom_key_pair (128, session,
+ &value,
+ &fee_withdraw,
+ &fee_deposit,
+ &fee_refresh);
+ new_denom_pubs[cnt]=new_dkp[cnt]->pub;
+ }
+ FAILIF (GNUNET_OK != plugin->insert_refresh_order (plugin->cls,
+ session,
+ &session_hash,
+ MELT_NEW_COINS,
+ new_denom_pubs));
+ ret_denom_pubs = GNUNET_new_array (MELT_NEW_COINS,
+ struct TALER_DenominationPublicKey);
+ FAILIF (GNUNET_OK != plugin->get_refresh_order (plugin->cls,
+ session,
+ &session_hash,
+ MELT_NEW_COINS,
+ ret_denom_pubs));
+ for (cnt=0; cnt < MELT_NEW_COINS; cnt++)
+ {
+ FAILIF (0 != GNUNET_CRYPTO_rsa_public_key_cmp
+ (ret_denom_pubs[cnt].rsa_public_key,
+ new_denom_pubs[cnt].rsa_public_key));
+ }
+ FAILIF (GNUNET_OK !=
+ test_refresh_commit_coins (session,
+ &refresh_session,
+ &session_hash));
+
+ ret = GNUNET_OK;
+
+ drop:
+ if (NULL != dkp)
+ destroy_denom_key_pair (dkp);
+ if (NULL != melts)
+ {
+ for (cnt = 0; cnt < MELT_OLD_COINS; cnt++)
+ GNUNET_CRYPTO_rsa_signature_free (melts[cnt].coin.denom_sig.rsa_signature);
+ GNUNET_free (melts);
+ }
+ for (cnt = 0;
+ (NULL != ret_denom_pubs) && (cnt < MELT_NEW_COINS)
+ && (NULL != ret_denom_pubs[cnt].rsa_public_key);
+ cnt++)
+ GNUNET_CRYPTO_rsa_public_key_free (ret_denom_pubs[cnt].rsa_public_key);
+ GNUNET_free_non_null (ret_denom_pubs);
+ GNUNET_free_non_null (new_denom_pubs);
+ for (cnt = 0;
+ (NULL != new_dkp) && (cnt < MELT_NEW_COINS) && (NULL != new_dkp[cnt]);
+ cnt++)
+ destroy_denom_key_pair (new_dkp[cnt]);
+ GNUNET_free_non_null (new_dkp);
+ return ret;
+}
+
+
+/**
+ * Callback that should never be called.
+ */
+static void
+cb_wt_never (void *cls,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct GNUNET_HashCode *h_wire,
+ const struct GNUNET_HashCode *h_contract,
+ uint64_t transaction_id,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_Amount *coin_value,
+ const struct TALER_Amount *coin_fee)
+{
+ GNUNET_assert (0); /* this statement should be unreachable */
+}
+
+
+/**
+ * Callback that should never be called.
+ */
+static void
+cb_wtid_never (void *cls,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_Amount *coin_contribution,
+ const struct TALER_Amount *coin_fee,
+ struct GNUNET_TIME_Absolute execution_time)
+{
+ GNUNET_assert (0);
+}
+
+
+static struct TALER_MerchantPublicKeyP merchant_pub_wt;
+static struct GNUNET_HashCode h_wire_wt;
+static struct GNUNET_HashCode h_contract_wt;
+static uint64_t transaction_id_wt;
+static struct TALER_CoinSpendPublicKeyP coin_pub_wt;
+static struct TALER_Amount coin_value_wt;
+static struct TALER_Amount coin_fee_wt;
+static struct TALER_Amount transfer_value_wt;
+static struct GNUNET_TIME_Absolute execution_time_wt;
+static struct TALER_WireTransferIdentifierRawP wtid_wt;
+
+
+/**
+ * Callback that should be called with the WT data.
+ */
+static void
+cb_wt_check (void *cls,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct GNUNET_HashCode *h_wire,
+ const struct GNUNET_HashCode *h_contract,
+ uint64_t transaction_id,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_Amount *coin_value,
+ const struct TALER_Amount *coin_fee)
+{
+ GNUNET_assert (cls == &cb_wt_never);
+ GNUNET_assert (0 == memcmp (merchant_pub,
+ &merchant_pub_wt,
+ sizeof (struct TALER_MerchantPublicKeyP)));
+ GNUNET_assert (0 == memcmp (h_wire,
+ &h_wire_wt,
+ sizeof (struct GNUNET_HashCode)));
+ GNUNET_assert (0 == memcmp (h_contract,
+ &h_contract_wt,
+ sizeof (struct GNUNET_HashCode)));
+ GNUNET_assert (transaction_id == transaction_id_wt);
+ GNUNET_assert (0 == memcmp (coin_pub,
+ &coin_pub_wt,
+ sizeof (struct TALER_CoinSpendPublicKeyP)));
+ GNUNET_assert (0 == TALER_amount_cmp (coin_value,
+ &coin_value_wt));
+ GNUNET_assert (0 == TALER_amount_cmp (coin_fee,
+ &coin_fee_wt));
+}
+
+
+/**
+ * Callback that should be called with the WT data.
+ */
+static void
+cb_wtid_check (void *cls,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_Amount *coin_contribution,
+ const struct TALER_Amount *coin_fee,
+ struct GNUNET_TIME_Absolute execution_time)
+{
+ GNUNET_assert (cls == &cb_wtid_never);
+ GNUNET_assert (0 == memcmp (wtid,
+ &wtid_wt,
+ sizeof (struct TALER_WireTransferIdentifierRawP)));
+ GNUNET_assert (execution_time.abs_value_us ==
+ execution_time_wt.abs_value_us);
+ GNUNET_assert (0 == TALER_amount_cmp (coin_contribution,
+ &coin_value_wt));
+ GNUNET_assert (0 == TALER_amount_cmp (coin_fee,
+ &coin_fee_wt));
+}
+
+
+/**
+ * Function called with details about deposits that
+ * have been made. Called in the test on the
+ * deposit given in @a cls.
+ *
+ * @param cls closure a `struct TALER_EXCHANGEDB_Deposit *`
+ * @param rowid unique ID for the deposit in our DB, used for marking
+ * it as 'tiny' or 'done'
+ * @param merchant_pub public key of the merchant
+ * @param coin_pub public key of the coin
+ * @param amount_with_fee amount that was deposited including fee
+ * @param deposit_fee amount the exchange gets to keep as transaction fees
+ * @param transaction_id unique transaction ID chosen by the merchant
+ * @param h_contract hash of the contract between merchant and customer
+ * @param wire_deadline by which the merchant adviced that he would like the
+ * wire transfer to be executed
+ * @param wire wire details for the merchant, NULL from iterate_matching_deposits()
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR if deposit does
+ * not match our expectations
+ */
+static int
+deposit_cb (void *cls,
+ unsigned long long rowid,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_Amount *amount_with_fee,
+ const struct TALER_Amount *deposit_fee,
+ uint64_t transaction_id,
+ const struct GNUNET_HashCode *h_contract,
+ struct GNUNET_TIME_Absolute wire_deadline,
+ const json_t *wire)
+{
+ struct TALER_EXCHANGEDB_Deposit *deposit = cls;
+ struct GNUNET_HashCode h_wire;
+
+ if (NULL != wire)
+ TALER_JSON_hash (wire, &h_wire);
+ if ( (0 != memcmp (merchant_pub,
+ &deposit->merchant_pub,
+ sizeof (struct TALER_MerchantPublicKeyP))) ||
+ (0 != TALER_amount_cmp (amount_with_fee,
+ &deposit->amount_with_fee)) ||
+ (0 != TALER_amount_cmp (deposit_fee,
+ &deposit->deposit_fee)) ||
+ (0 != memcmp (h_contract,
+ &deposit->h_contract,
+ sizeof (struct GNUNET_HashCode))) ||
+ (0 != memcmp (coin_pub,
+ &deposit->coin.coin_pub,
+ sizeof (struct TALER_CoinSpendPublicKeyP))) ||
+ (transaction_id != deposit->transaction_id) ||
+ ( (NULL != wire) &&
+ (0 != memcmp (&h_wire,
+ &deposit->h_wire,
+ sizeof (struct GNUNET_HashCode))) ) )
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Main function that will be run by the scheduler.
+ *
+ * @param cls closure with config
+ */
+static void
+run (void *cls)
+{
+ struct GNUNET_CONFIGURATION_Handle *cfg = cls;
+ struct TALER_EXCHANGEDB_Session *session;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct DenomKeyPair *dkp;
+ struct TALER_EXCHANGEDB_CollectableBlindcoin cbc;
+ struct TALER_EXCHANGEDB_CollectableBlindcoin cbc2;
+ struct TALER_EXCHANGEDB_ReserveHistory *rh;
+ struct TALER_EXCHANGEDB_ReserveHistory *rh_head;
+ struct TALER_EXCHANGEDB_BankTransfer *bt;
+ struct TALER_EXCHANGEDB_CollectableBlindcoin *withdraw;
+ struct TALER_EXCHANGEDB_Deposit deposit;
+ struct TALER_EXCHANGEDB_Deposit deposit2;
+ struct TALER_WireTransferIdentifierRawP wtid;
+ json_t *wire;
+ json_t *just;
+ 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 (NULL ==
+ (plugin = TALER_EXCHANGEDB_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);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":1.000010",
+ &value));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":0.000010",
+ &fee_withdraw));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":0.000010",
+ &fee_deposit));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":0.000010",
+ &fee_refresh));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":1.000010",
+ &amount_with_fee));
+
+ result = 4;
+ just = json_loads ("{ \"justification\":\"1\" }", 0, NULL);
+ FAILIF (GNUNET_OK !=
+ plugin->reserves_in_insert (plugin->cls,
+ session,
+ &reserve_pub,
+ &value,
+ GNUNET_TIME_absolute_get (),
+ just));
+ json_decref (just);
+ FAILIF (GNUNET_OK !=
+ check_reserve (session,
+ &reserve_pub,
+ value.value,
+ value.fraction,
+ value.currency));
+ just = json_loads ("{ \"justification\":\"2\" }", 0, NULL);
+ FAILIF (GNUNET_OK !=
+ plugin->reserves_in_insert (plugin->cls,
+ session,
+ &reserve_pub,
+ &value,
+ GNUNET_TIME_absolute_get (),
+ just));
+ json_decref (just);
+ FAILIF (GNUNET_OK !=
+ check_reserve (session,
+ &reserve_pub,
+ value.value * 2,
+ value.fraction * 2,
+ value.currency));
+ result = 5;
+ dkp = create_denom_key_pair (1024, session,
+ &value,
+ &fee_withdraw,
+ &fee_deposit,
+ &fee_refresh);
+ RND_BLK(&cbc.h_coin_envelope);
+ RND_BLK(&cbc.reserve_sig);
+ cbc.denom_pub = dkp->pub;
+ cbc.sig.rsa_signature
+ = GNUNET_CRYPTO_rsa_sign_fdh (dkp->priv.rsa_private_key,
+ &cbc.h_coin_envelope);
+ cbc.reserve_pub = reserve_pub;
+ cbc.amount_with_fee = value;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (CURRENCY, &cbc.withdraw_fee));
+ FAILIF (GNUNET_OK !=
+ plugin->insert_withdraw_info (plugin->cls,
+ session,
+ &cbc));
+ FAILIF (GNUNET_OK !=
+ check_reserve (session,
+ &reserve_pub,
+ value.value,
+ value.fraction,
+ value.currency));
+ FAILIF (GNUNET_YES !=
+ plugin->get_withdraw_info (plugin->cls,
+ session,
+ &cbc.h_coin_envelope,
+ &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)));
+ result = 6;
+ FAILIF (GNUNET_OK !=
+ GNUNET_CRYPTO_rsa_verify (&cbc.h_coin_envelope,
+ cbc2.sig.rsa_signature,
+ dkp->pub.rsa_public_key));
+ result = 7;
+ 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_EXCHANGEDB_RO_BANK_TO_EXCHANGE:
+ bt = rh_head->details.bank;
+ FAILIF (0 != memcmp (&bt->reserve_pub,
+ &reserve_pub,
+ sizeof (reserve_pub)));
+ /* this is the amount we trasferred twice*/
+ FAILIF (1 != bt->amount.value);
+ FAILIF (10 != bt->amount.fraction);
+ FAILIF (0 != strcmp (CURRENCY, bt->amount.currency));
+ FAILIF (NULL == bt->wire);
+ break;
+ case TALER_EXCHANGEDB_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,
+ &cbc.h_coin_envelope,
+ sizeof (cbc.h_coin_envelope)));
+ break;
+ }
+ }
+ FAILIF (3 != cnt);
+ /* Tests for deposits */
+ memset (&deposit, 0, sizeof (deposit));
+ 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);
+ wire = json_loads (json_wire_str, 0, NULL);
+ TALER_JSON_hash (wire,
+ &deposit.h_wire);
+ deposit.wire = wire;
+ deposit.transaction_id =
+ GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK, UINT64_MAX);
+ deposit.amount_with_fee = value;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (CURRENCY, &deposit.deposit_fee));
+ result = 8;
+ FAILIF (GNUNET_OK !=
+ plugin->insert_deposit (plugin->cls,
+ session, &deposit));
+ FAILIF (GNUNET_YES !=
+ plugin->have_deposit (plugin->cls,
+ session,
+ &deposit));
+ result = 9;
+ FAILIF (1 !=
+ plugin->iterate_matching_deposits (plugin->cls,
+ session,
+ &deposit.h_wire,
+ &deposit.merchant_pub,
+ &deposit_cb, &deposit,
+ 2));
+ result = 10;
+ deposit2 = 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));
+ deposit2.merchant_pub = 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));
+ FAILIF (GNUNET_OK != test_melting (session));
+
+ /* setup values for wire transfer aggregation data */
+ memset (&wtid, 42, sizeof (wtid));
+ memset (&merchant_pub_wt, 43, sizeof (merchant_pub_wt));
+ memset (&h_wire_wt, 44, sizeof (h_wire_wt));
+ memset (&h_contract_wt, 45, sizeof (h_contract_wt));
+ memset (&coin_pub_wt, 46, sizeof (coin_pub_wt));
+ transaction_id_wt = 47;
+ execution_time_wt = GNUNET_TIME_absolute_get ();
+ memset (&merchant_pub_wt, 48, sizeof (merchant_pub_wt));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY "KUDOS:1.000010",
+ &coin_value_wt));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY "KUDOS:0.000010",
+ &coin_fee_wt));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY "KUDOS:1.000000",
+ &transfer_value_wt));
+
+ FAILIF (GNUNET_NO !=
+ plugin->lookup_wire_transfer (plugin->cls,
+ session,
+ &wtid_wt,
+ &cb_wt_never,
+ NULL));
+ FAILIF (GNUNET_NO !=
+ plugin->wire_lookup_deposit_wtid (plugin->cls,
+ session,
+ &h_contract_wt,
+ &h_wire_wt,
+ &coin_pub_wt,
+ &merchant_pub_wt,
+ transaction_id_wt,
+ &cb_wtid_never,
+ NULL));
+ /* insert WT data */
+ FAILIF (GNUNET_OK !=
+ plugin->insert_aggregation_tracking (plugin->cls,
+ session,
+ &wtid_wt,
+ &merchant_pub_wt,
+ &h_wire_wt,
+ &h_contract_wt,
+ transaction_id_wt,
+ execution_time_wt,
+ &coin_pub_wt,
+ &coin_value_wt,
+ &coin_fee_wt));
+ FAILIF (GNUNET_OK !=
+ plugin->lookup_wire_transfer (plugin->cls,
+ session,
+ &wtid_wt,
+ &cb_wt_check,
+ &cb_wt_never));
+ FAILIF (GNUNET_OK !=
+ plugin->wire_lookup_deposit_wtid (plugin->cls,
+ session,
+ &h_contract_wt,
+ &h_wire_wt,
+ &coin_pub_wt,
+ &merchant_pub_wt,
+ transaction_id_wt,
+ &cb_wtid_check,
+ &cb_wtid_never));
+ 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_denom_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_EXCHANGEDB_plugin_unload (plugin);
+ plugin = NULL;
+}
+
+
+int
+main (int argc,
+ char *const argv[])
+{
+ const char *plugin_name;
+ char *config_filename;
+ char *testname;
+ struct GNUNET_CONFIGURATION_Handle *cfg;
+
+ result = -1;
+ if (NULL == (plugin_name = strrchr (argv[0], (int) '-')))
+ {
+ GNUNET_break (0);
+ return -1;
+ }
+ GNUNET_log_setup (argv[0],
+ "WARNING",
+ NULL);
+ plugin_name++;
+ (void) GNUNET_asprintf (&testname,
+ "test-exchange-db-%s", plugin_name);
+ (void) GNUNET_asprintf (&config_filename,
+ "%s.conf", testname);
+ cfg = GNUNET_CONFIGURATION_create ();
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_parse (cfg,
+ config_filename))
+ {
+ GNUNET_break (0);
+ GNUNET_free (config_filename);
+ GNUNET_free (testname);
+ return 2;
+ }
+ GNUNET_SCHEDULER_run (&run, cfg);
+ GNUNET_CONFIGURATION_destroy (cfg);
+ GNUNET_free (config_filename);
+ GNUNET_free (testname);
+ return result;
+}
diff --git a/src/exchangedb/test_exchangedb_deposits.c b/src/exchangedb/test_exchangedb_deposits.c
new file mode 100644
index 000000000..09c65b2b2
--- /dev/null
+++ b/src/exchangedb/test_exchangedb_deposits.c
@@ -0,0 +1,152 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014 GNUnet e.V.
+
+ 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 exchange/test_exchange_deposits.c
+ * @brief testcase for exchange deposits
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ */
+#include "platform.h"
+#include <libpq-fe.h>
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_pq_lib.h"
+#include "taler_exchangedb_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+#define EXCHANGE_CURRENCY "EUR"
+
+#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;
+
+/**
+ * The plugin.
+ */
+static struct TALER_EXCHANGEDB_Plugin *plugin;
+
+/**
+ * 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 TALER_EXCHANGEDB_Deposit *deposit;
+ uint64_t transaction_id;
+ struct TALER_EXCHANGEDB_Session *session;
+
+ deposit = NULL;
+ EXITIF (NULL == (plugin = TALER_EXCHANGEDB_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 TALER_EXCHANGEDB_Deposit) + sizeof (wire));
+ /* Makeup a random coin public key */
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ deposit,
+ sizeof (struct TALER_EXCHANGEDB_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 (EXCHANGE_CURRENCY) < sizeof (deposit->amount_with_fee.currency));
+ strcpy (deposit->amount_with_fee.currency, EXCHANGE_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);
+ if (NULL != plugin)
+ {
+ TALER_EXCHANGEDB_plugin_unload (plugin);
+ plugin = NULL;
+ }
+}
+
+
+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-exchange-deposits",
+ "testcase for exchange deposits",
+ options, &run, NULL))
+ return 3;
+ return (GNUNET_OK == result) ? 0 : 1;
+}
diff --git a/src/exchangedb/test_exchangedb_keyio.c b/src/exchangedb/test_exchangedb_keyio.c
new file mode 100644
index 000000000..2485da8ae
--- /dev/null
+++ b/src/exchangedb/test_exchangedb_keyio.c
@@ -0,0 +1,85 @@
+/*
+ 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 exchange/test_exchange_common.c
+ * @brief test cases for some functions in exchange/exchange_common.c
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ */
+#include "platform.h"
+#include "gnunet/gnunet_util_lib.h"
+#include "taler_signatures.h"
+#include "taler_exchangedb_lib.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_EXCHANGEDB_DenominationKeyIssueInformation dki;
+ char *enc;
+ size_t enc_size;
+ struct TALER_EXCHANGEDB_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 (struct TALER_MasterSignatureP));
+ 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_exchange_common")));
+ EXITIF (GNUNET_OK != TALER_EXCHANGEDB_denomination_key_write (tmpfile, &dki));
+ EXITIF (GNUNET_OK != TALER_EXCHANGEDB_denomination_key_read (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;
+}
diff --git a/src/exchangedb/test_perf_taler_exchangedb.c b/src/exchangedb/test_perf_taler_exchangedb.c
new file mode 100644
index 000000000..a4ec9591d
--- /dev/null
+++ b/src/exchangedb/test_perf_taler_exchangedb.c
@@ -0,0 +1,182 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015 GNUnet e.V.
+
+ 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 exchangedb/test_perf_taler_exchangedb.c
+ * @brief Exchange database performance analysis
+ * @author Nicolas Fournier
+ */
+#include "platform.h"
+#include "perf_taler_exchangedb_interpreter.h"
+#include "perf_taler_exchangedb_init.h"
+
+
+#define NB_DENOMINATION_INIT 2
+#define NB_DENOMINATION_SAVE 2
+
+#define NB_RESERVE_INIT 4
+#define NB_RESERVE_SAVE 1
+
+#define NB_DEPOSIT_INIT 1
+#define NB_DEPOSIT_SAVE 1
+
+#define NB_WITHDRAW_INIT 1
+#define NB_WITHDRAW_SAVE 1
+
+/**
+ * Allocate, copies and free all the data used in the interpreter
+ * Used to check for memory leaks
+ */
+static void
+test_allocate ()
+{
+ struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki, *dki_copy;
+ struct PERF_TALER_EXCHANGEDB_Reserve *reserve, *reserve_copy;
+ struct PERF_TALER_EXCHANGEDB_Coin *coin, *coin_copy;
+ struct TALER_EXCHANGEDB_Deposit *deposit, *deposit_copy;
+
+ dki = PERF_TALER_EXCHANGEDB_denomination_init ();
+ reserve = PERF_TALER_EXCHANGEDB_reserve_init ();
+ coin = PERF_TALER_EXCHANGEDB_coin_init (dki,
+ reserve);
+ deposit = PERF_TALER_EXCHANGEDB_deposit_init (coin);
+
+ dki_copy = PERF_TALER_EXCHANGEDB_denomination_copy (dki);
+ reserve_copy = PERF_TALER_EXCHANGEDB_reserve_copy (reserve);
+ coin_copy = PERF_TALER_EXCHANGEDB_coin_copy (coin);
+ deposit_copy = PERF_TALER_EXCHANGEDB_deposit_copy (deposit);
+
+ PERF_TALER_EXCHANGEDB_denomination_free (dki);
+ PERF_TALER_EXCHANGEDB_denomination_free (dki_copy);
+ PERF_TALER_EXCHANGEDB_reserve_free (reserve);
+ PERF_TALER_EXCHANGEDB_reserve_free (reserve_copy);
+ PERF_TALER_EXCHANGEDB_coin_free (coin);
+ PERF_TALER_EXCHANGEDB_coin_free (coin_copy);
+ PERF_TALER_EXCHANGEDB_deposit_free (deposit);
+ PERF_TALER_EXCHANGEDB_deposit_free (deposit_copy);
+}
+
+/**
+ * Runs the performances tests for the exchange database
+ * and logs the results using Gauger
+ */
+int
+main (int argc, char ** argv)
+{
+ int ret = 0;
+ struct PERF_TALER_EXCHANGEDB_Cmd init[] =
+ {
+ PERF_TALER_EXCHANGEDB_INIT_CMD_END ("init")
+ };
+ struct PERF_TALER_EXCHANGEDB_Cmd benchmark[] =
+ {
+ // Denomination used to create coins
+ PERF_TALER_EXCHANGEDB_INIT_CMD_DEBUG ("00 - Start of interpreter"),
+
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("01 - denomination loop",
+ NB_DENOMINATION_INIT),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_START_TRANSACTION ("01 - start transaction"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_DENOMINATION ("01 - denomination"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_DENOMINATION ("01 - insert",
+ "01 - denomination"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_COMMIT_TRANSACTION ("01 - commit transaction"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_SAVE_ARRAY ("01 - save denomination",
+ "01 - denomination loop",
+ "01 - denomination",
+ NB_DENOMINATION_SAVE),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("01 - denomination loop end",
+ "01 - denomination loop"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_DEBUG ("01 - init denomination complete"),
+ // End of initialization
+ // Reserve initialization
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("02 - init reserve loop",
+ NB_RESERVE_INIT),
+
+ PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_RESERVE ("02 - reserve"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_RESERVE ("02 - insert",
+ "02 - reserve"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_SAVE_ARRAY ("02 - save reserve",
+ "02 - init reserve loop",
+ "02 - reserve",
+ NB_RESERVE_SAVE),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("02 - init reserve end loop",
+ "02 - init reserve loop"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_DEBUG ("02 - reserve init complete"),
+ // End reserve init
+ // Withdrawal initialization
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("03 - init withdraw loop",
+ NB_WITHDRAW_INIT),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_START_TRANSACTION ("03 - start transaction"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY ("03 - denomination load",
+ "03 - init withdraw loop",
+ "01 - save denomination"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY ("03 - reserve load",
+ "03 - init withdraw loop",
+ "02 - save reserve"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_WITHDRAW ("03 - withdraw",
+ "03 - denomination load",
+ "03 - reserve load"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_WITHDRAW ("03 - insert withdraw",
+ "03 - withdraw"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_COMMIT_TRANSACTION ("03 - commit transaction"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_SAVE_ARRAY ("03 - coin array",
+ "03 - init withdraw loop",
+ "03 - withdraw",
+ NB_WITHDRAW_SAVE),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("03 - withdraw init end loop",
+ "03 - init withdraw loop"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_DEBUG ("03 - withdraw init complete"),
+ //End of withdrawal initialization
+ //Deposit initialization
+ PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("04 - time start"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("04 - deposit init loop",
+ NB_DEPOSIT_INIT),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_START_TRANSACTION ("04 - start transaction"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY ("04 - coin load",
+ "04 - deposit init loop",
+ "03 - coin array"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_DEPOSIT ("04 - deposit",
+ "04 - coin load"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_DEPOSIT ("04 - insert deposit",
+ "04 - deposit"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_COMMIT_TRANSACTION ("04 - commit transaction"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_SAVE_ARRAY ("04 - deposit array",
+ "04 - deposit init loop",
+ "04 - deposit",
+ NB_DEPOSIT_SAVE),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("04 - deposit init loop end",
+ "04 - deposit init loop"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("04 - time stop"),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_GAUGER ("04 - gauger",
+ "04 - time start",
+ "04 - time stop",
+ "TEST",
+ "time to insert a deposit",
+ "deposit/sec",
+ NB_DEPOSIT_SAVE),
+ PERF_TALER_EXCHANGEDB_INIT_CMD_DEBUG ("04 - deposit init complete"),
+ // End of deposit initialization
+ PERF_TALER_EXCHANGEDB_INIT_CMD_END ("end"),
+ };
+
+ test_allocate ();
+ ret = PERF_TALER_EXCHANGEDB_run_benchmark ("test-perf-taler-exchangedb",
+ "./test-exchange-db-postgres.conf",
+ init,
+ benchmark);
+ if (GNUNET_SYSERR == ret)
+ return 1;
+ return 0;
+}