diff options
Diffstat (limited to 'src/exchangedb')
| -rw-r--r-- | src/exchangedb/Makefile.am | 110 | ||||
| -rw-r--r-- | src/exchangedb/exchangedb_keyio.c | 558 | ||||
| -rw-r--r-- | src/exchangedb/exchangedb_plugin.c | 87 | ||||
| -rw-r--r-- | src/exchangedb/perf_taler_exchangedb.c | 358 | ||||
| -rw-r--r-- | src/exchangedb/perf_taler_exchangedb_init.c | 622 | ||||
| -rw-r--r-- | src/exchangedb/perf_taler_exchangedb_init.h | 257 | ||||
| -rw-r--r-- | src/exchangedb/perf_taler_exchangedb_interpreter.c | 1998 | ||||
| -rw-r--r-- | src/exchangedb/perf_taler_exchangedb_interpreter.h | 1319 | ||||
| -rw-r--r-- | src/exchangedb/perf_taler_exchangedb_values.h | 25 | ||||
| -rw-r--r-- | src/exchangedb/plugin_exchangedb_common.c | 162 | ||||
| -rw-r--r-- | src/exchangedb/plugin_exchangedb_postgres.c | 4295 | ||||
| -rw-r--r-- | src/exchangedb/test-exchange-db-postgres.conf | 8 | ||||
| -rw-r--r-- | src/exchangedb/test_exchangedb.c | 907 | ||||
| -rw-r--r-- | src/exchangedb/test_exchangedb_deposits.c | 152 | ||||
| -rw-r--r-- | src/exchangedb/test_exchangedb_keyio.c | 85 | ||||
| -rw-r--r-- | src/exchangedb/test_perf_taler_exchangedb.c | 182 | 
16 files changed, 11125 insertions, 0 deletions
| diff --git a/src/exchangedb/Makefile.am b/src/exchangedb/Makefile.am new file mode 100644 index 00000000..d56d6676 --- /dev/null +++ b/src/exchangedb/Makefile.am @@ -0,0 +1,110 @@ +# 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 + +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 + +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_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_interpreter.c +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_interpreter.c +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_keyio.c b/src/exchangedb/exchangedb_keyio.c new file mode 100644 index 00000000..6b8ca24e --- /dev/null +++ b/src/exchangedb/exchangedb_keyio.c @@ -0,0 +1,558 @@ +/* +  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/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_rsa_PrivateKey *priv; + +  if (GNUNET_OK != GNUNET_DISK_file_size (filename, +                                          &size, +                                          GNUNET_YES, +                                          GNUNET_YES)) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_INFO, +                "Skipping inaccessable denomination key file `%s'\n", +                filename); +    return GNUNET_SYSERR; +  } +  offset = sizeof (struct TALER_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; + +}; +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; +  unsigned int 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)) || +       (0 != (len = ((size - sizeof (struct AuditorFileHeaderP)) % +                     (sizeof (struct TALER_DenominationKeyValidityPS) + +                      sizeof (struct TALER_AuditorSignatureP))))) ) +  { +    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; +  } +  sigs = (const struct TALER_AuditorSignatureP *) &af[1]; +  dki = (const struct TALER_DenominationKeyValidityPS *) &sigs[len]; +  ret = aic->it (aic->it_cls, +                 &af->apub, +                 &af->mpub, +                 len, +                 sigs, +                 dki); +  GNUNET_free (af); +  return ret; +} + + +/** + * Call @a it with information for each auditor 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 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 char *exchange_base_dir, +                              TALER_EXCHANGEDB_AuditorIterator it, +                              void *it_cls) +{ +  char *dir; +  struct AuditorIterateContext aic; +  int ret; + +  GNUNET_asprintf (&dir, +                   "%s" DIR_SEPARATOR_STR TALER_EXCHANGEDB_DIR_AUDITORS, +                   exchange_base_dir); +  aic.it = it; +  aic.it_cls = it_cls; +  ret = GNUNET_DISK_directory_scan (dir, +                                    &auditor_iter, +                                    &aic); +  GNUNET_free (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 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 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; +  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; + 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 00000000..ebaef9cc --- /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 00000000..6ff7f533 --- /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 00000000..2e613b3c --- /dev/null +++ b/src/exchangedb/perf_taler_exchangedb_init.c @@ -0,0 +1,622 @@ +/* +   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; + +  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); +  coin->public_info.denom_sig.rsa_signature = +    GNUNET_CRYPTO_rsa_sign (dki->denom_priv.rsa_private_key, +                            &coin->public_info.coin_pub, +                            sizeof (struct TALER_CoinSpendPublicKeyP)); +  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 00000000..0ff07410 --- /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 00000000..75b32cb6 --- /dev/null +++ b/src/exchangedb/perf_taler_exchangedb_interpreter.c @@ -0,0 +1,1998 @@ +/* +   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_load (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 00000000..a83251c6 --- /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 00000000..c3b50fea --- /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 00000000..c8e689cf --- /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 00000000..0395c208 --- /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" +           " ORDER BY wire_deadline ASC" +           " LIMIT 1;", +           0, 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 $3", +           3, 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_YES != +            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_YES != +            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_PQ_QueryParam params[] = { +    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_uint32 (&limit), +    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_MerchantPublicKeyP merchant_pub; +    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 ("merchant_pub", +                                           &merchant_pub), +      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); +    PQclear (result); +    if (GNUNET_OK != ret) +      break; +  } +  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_rsa_PublicKey *denom_pub; +    struct GNUNET_CRYPTO_rsa_Signature *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_fixed_size (type, strlen (type) + 1), +    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_fixed_size (type, strlen (type) + 1), +    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 00000000..0822bab4 --- /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 00000000..df1adf56 --- /dev/null +++ b/src/exchangedb/test_exchangedb.c @@ -0,0 +1,907 @@ +/* +  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_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++) +  { +    RND_BLK (&melts[cnt].coin.coin_pub); +    melts[cnt].coin.denom_sig.rsa_signature = +        GNUNET_CRYPTO_rsa_sign (dkp->priv.rsa_private_key, +                                &melts[cnt].coin.coin_pub, +                                sizeof (melts[cnt].coin.coin_pub)); +    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)); +} + + +/** + * Main function that will be run by the scheduler. + * + * @param cls closure + * @param args remaining command-line arguments + * @param cfgfile name of the configuration file used (for saving, can be NULL!) + * @param cfg configuration + */ +static void +run (void *cls, +     char *const *args, +     const char *cfgfile, +     const struct GNUNET_CONFIGURATION_Handle *cfg) +{ +  struct TALER_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)); +  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 (dkp->priv.rsa_private_key, +                              &cbc.h_coin_envelope, +                              sizeof (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))); +  FAILIF (GNUNET_OK != +          GNUNET_CRYPTO_rsa_verify (&cbc.h_coin_envelope, +                                    cbc2.sig.rsa_signature, +                                    dkp->pub.rsa_public_key)); +  rh = plugin->get_reserve_history (plugin->cls, +                                    session, +                                    &reserve_pub); +  FAILIF (NULL == rh); +  rh_head = rh; +  for (cnt=0; NULL != rh_head; rh_head=rh_head->next, cnt++) +  { +    switch (rh_head->type) +    { +    case TALER_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); +  RND_BLK (&deposit.h_wire); +  wire = json_loads (json_wire_str, 0, NULL); +  deposit.wire = wire; +  deposit.transaction_id = +      GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK, UINT64_MAX); +  deposit.amount_with_fee = value; +  GNUNET_assert (GNUNET_OK == +                 TALER_amount_get_zero (CURRENCY, &deposit.deposit_fee)); +  FAILIF (GNUNET_OK != +          plugin->insert_deposit (plugin->cls, +                                  session, &deposit)); +  FAILIF (GNUNET_YES != +          plugin->have_deposit (plugin->cls, +                                session, +                                &deposit)); +  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[]) +{ +   static const struct GNUNET_GETOPT_CommandLineOption options[] = { +    GNUNET_GETOPT_OPTION_END +  }; +   char *argv2[] = { +     "test-exchange-db-<plugin_name>", /* will be replaced later */ +     "-c", "test-exchange-db-<plugin_name>.conf", /* will be replaced later */ +     NULL, +   }; +   const char *plugin_name; +   char *config_filename; +   char *testname; + +   result = -1; +   if (NULL == (plugin_name = strrchr (argv[0], (int) '-'))) +   { +     GNUNET_break (0); +     return -1; +   } +   plugin_name++; +   (void) GNUNET_asprintf (&testname, +                           "test-exchange-db-%s", plugin_name); +   (void) GNUNET_asprintf (&config_filename, +                           "%s.conf", testname); +   argv2[0] = argv[0]; +   argv2[2] = config_filename; +  if (GNUNET_OK != +      GNUNET_PROGRAM_run ((sizeof (argv2)/sizeof (char *)) - 1, argv2, +                          testname, +                          "Test cases for exchange database helper functions.", +                          options, &run, NULL)) +  { +    GNUNET_free (config_filename); +    GNUNET_free (testname); +    return 3; +  } +  GNUNET_free (config_filename); +  GNUNET_free (testname); +  return result; +} diff --git a/src/exchangedb/test_exchangedb_deposits.c b/src/exchangedb/test_exchangedb_deposits.c new file mode 100644 index 00000000..09c65b2b --- /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 00000000..2485da8a --- /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 00000000..a4ec9591 --- /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; +} | 
