diff options
author | Christian Grothoff <christian@grothoff.org> | 2016-03-01 15:35:04 +0100 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2016-03-01 15:35:04 +0100 |
commit | b5cba3251053c22bf1df46282f1dd0a4c46f6a38 (patch) | |
tree | b7495c3e47c40c57ff81045a4e43aa07a3b6c7b1 /src/exchangedb | |
parent | e406833eab7ca0835f9779abebada94592a85a7e (diff) |
renaming mint->exchange
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 000000000..d56d6676a --- /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 000000000..6b8ca24e3 --- /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 000000000..ebaef9cc0 --- /dev/null +++ b/src/exchangedb/exchangedb_plugin.c @@ -0,0 +1,87 @@ +/* + This file is part of TALER + Copyright (C) 2015 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file exchangedb/exchangedb_plugin.c + * @brief Logic to load database plugin + * @author Christian Grothoff + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + */ +#include "platform.h" +#include "taler_exchangedb_plugin.h" +#include <ltdl.h> + + +/** + * Initialize the plugin. + * + * @param cfg configuration to use + * @return #GNUNET_OK on success + */ +struct TALER_EXCHANGEDB_Plugin * +TALER_EXCHANGEDB_plugin_load (const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + char *plugin_name; + char *lib_name; + struct GNUNET_CONFIGURATION_Handle *cfg_dup; + struct TALER_EXCHANGEDB_Plugin *plugin; + + if (GNUNET_SYSERR == + GNUNET_CONFIGURATION_get_value_string (cfg, + "exchange", + "db", + &plugin_name)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "exchange", + "db"); + return NULL; + } + (void) GNUNET_asprintf (&lib_name, + "libtaler_plugin_exchangedb_%s", + plugin_name); + GNUNET_free (plugin_name); + cfg_dup = GNUNET_CONFIGURATION_dup (cfg); + plugin = GNUNET_PLUGIN_load (lib_name, cfg_dup); + if (NULL != plugin) + plugin->library_name = lib_name; + else + GNUNET_free (lib_name); + GNUNET_CONFIGURATION_destroy (cfg_dup); + return plugin; +} + + +/** + * Shutdown the plugin. + * + * @param plugin the plugin to unload + */ +void +TALER_EXCHANGEDB_plugin_unload (struct TALER_EXCHANGEDB_Plugin *plugin) +{ + char *lib_name; + + if (NULL == plugin) + return; + lib_name = plugin->library_name; + GNUNET_assert (NULL == GNUNET_PLUGIN_unload (lib_name, + plugin)); + GNUNET_free (lib_name); +} + + + +/* end of exchangedb_plugin.c */ diff --git a/src/exchangedb/perf_taler_exchangedb.c b/src/exchangedb/perf_taler_exchangedb.c new file mode 100644 index 000000000..6ff7f5331 --- /dev/null +++ b/src/exchangedb/perf_taler_exchangedb.c @@ -0,0 +1,358 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> + */ +/** + * @file exchangedb/perf_taler_exchangedb.c + * @brief Exchange database performance analysis + * @author Nicolas Fournier + */ +#include "platform.h" +#include "perf_taler_exchangedb_interpreter.h" + + +#define NB_DENOMINATION_INIT 15 +#define NB_DENOMINATION_SAVE 15 + +#define SMALL 1000 +#define BIG 10000 +#define BIGGER 100000 + +#define NB_RESERVE_INIT BIGGER +#define NB_RESERVE_SAVE BIG + +#define NB_DEPOSIT_INIT BIGGER +#define NB_DEPOSIT_SAVE BIG + +#define NB_WITHDRAW_INIT BIGGER +#define NB_WITHDRAW_SAVE BIG + +#define NB_REFRESH_INIT BIGGER +#define NB_REFRESH_SAVE BIG + +#define NB_MELT_INIT BIG +#define NB_MELT_SAVE SMALL + +/** + * Runs the performances tests for the exchange database + * and logs the results using Gauger + */ +int +main (int argc, char ** argv) +{ + int ret; + struct PERF_TALER_EXCHANGEDB_Cmd benchmark[] = + { + /* Denomination used to create coins */ + PERF_TALER_EXCHANGEDB_INIT_CMD_DEBUG ("Initializing database"), + + PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("01 - denomination loop", + NB_DENOMINATION_INIT), + PERF_TALER_EXCHANGEDB_INIT_CMD_START_TRANSACTION (""), + PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_DENOMINATION ("01 - denomination"), + PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_DENOMINATION ("01 - insert", + "01 - denomination"), + PERF_TALER_EXCHANGEDB_INIT_CMD_COMMIT_TRANSACTION (""), + PERF_TALER_EXCHANGEDB_INIT_CMD_SAVE_ARRAY ("01 - save denomination", + "01 - denomination loop", + "01 - denomination", + NB_DENOMINATION_SAVE), + PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("01 - end", + "01 - denomination loop"), + /* End of initialization */ + /* Reserve initialization */ + PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("02 - init reserve loop", + NB_RESERVE_INIT), + PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_RESERVE ("02 - reserve"), + PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_RESERVE ("02 - insert", + "02 - reserve"), + PERF_TALER_EXCHANGEDB_INIT_CMD_SAVE_ARRAY ("02 - save reserve", + "02 - init reserve loop", + "02 - reserve", + NB_RESERVE_SAVE), + PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("02 - end", + "02 - init reserve loop"), + /* End reserve init */ + /* Withdrawal initialization */ + PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("03 - init withdraw loop", + NB_WITHDRAW_INIT), + PERF_TALER_EXCHANGEDB_INIT_CMD_START_TRANSACTION (""), + PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY ("03 - denomination load", + "03 - init withdraw loop", + "01 - save denomination"), + PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY ("03 - reserve load", + "03 - init withdraw loop", + "02 - save reserve"), + PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_WITHDRAW ("03 - withdraw", + "03 - denomination load", + "03 - reserve load"), + PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_WITHDRAW ("03 - insert", + "03 - withdraw"), + PERF_TALER_EXCHANGEDB_INIT_CMD_COMMIT_TRANSACTION (""), + PERF_TALER_EXCHANGEDB_INIT_CMD_SAVE_ARRAY ("03 - save coin", + "03 - init withdraw loop", + "03 - withdraw", + NB_WITHDRAW_SAVE), + PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("03 - end", + "03 - init withdraw loop"), + /*End of withdrawal initialization */ + /*Deposit initialization */ + PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("04 - deposit init loop", + NB_DEPOSIT_INIT), + PERF_TALER_EXCHANGEDB_INIT_CMD_START_TRANSACTION (""), + PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY ("04 - coin load", + "04 - deposit init loop", + "03 - save coin"), + PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_DEPOSIT ("04 - deposit", + "04 - coin load"), + PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_DEPOSIT ("04 - insert", + "04 - deposit"), + PERF_TALER_EXCHANGEDB_INIT_CMD_COMMIT_TRANSACTION (""), + PERF_TALER_EXCHANGEDB_INIT_CMD_SAVE_ARRAY ("04 - deposit array", + "04 - deposit init loop", + "04 - deposit", + NB_DEPOSIT_SAVE), + PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("", + "04 - deposit init loop"), + /* End of deposit initialization */ + /* Session initialization */ + PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("05 - refresh session init loop", + NB_REFRESH_INIT), + PERF_TALER_EXCHANGEDB_INIT_CMD_START_TRANSACTION (""), + PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_REFRESH_SESSION ("05 - refresh session"), + PERF_TALER_EXCHANGEDB_INIT_CMD_SAVE_ARRAY ("05 - session array", + "05 - refresh session init loop", + "05 - refresh session", + NB_RESERVE_SAVE), + PERF_TALER_EXCHANGEDB_INIT_CMD_COMMIT_TRANSACTION (""), + PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("05 - end", + "05 - refresh session init loop"), + /* End of refresh session initialization */ + /* Refresh melt initialization */ + PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("06 - refresh melt init loop", + NB_MELT_INIT), + PERF_TALER_EXCHANGEDB_INIT_CMD_START_TRANSACTION (""), + /* TODO: initialize using coins & sessions created localy + * in order to make sure the same coin are not melted twice*/ + PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY ("06 - session hash", + "06 - refresh melt init loop", + "05 - session array"), + PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY ("06 - coin", + "06 - refresh melt init loop", + "03 - save coin"), + PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_REFRESH_MELT ("06 - refresh melt", + "06 - session hash", + "06 - coin"), + PERF_TALER_EXCHANGEDB_INIT_CMD_COMMIT_TRANSACTION (""), + PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("06 - end", + "06 - refresh melt init loop"), + /* End of refresh melt initialization */ + PERF_TALER_EXCHANGEDB_INIT_CMD_DEBUG ("End of initialization"), + + PERF_TALER_EXCHANGEDB_INIT_CMD_DEBUG ("Start of performances measuring"), + PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("21 - start"), + PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("21 - reserve insert measure", + NB_RESERVE_SAVE), + PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_RESERVE ("21 - reserve"), + PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_RESERVE ("21 - insert", + "21 - reserve"), + PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("", + "21 - reserve insert measure"), + PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("21 - stop"), + PERF_TALER_EXCHANGEDB_INIT_CMD_GAUGER ("21 - gauger", + "21 - start", + "21 - stop", + "POSTGRES", + "Number of reserve inserted per second", + "item/sec", + NB_RESERVE_SAVE), + PERF_TALER_EXCHANGEDB_INIT_CMD_DEBUG ("End of reserve insertion"), + + PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("22 - start"), + PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("22 - reserve load measure", + NB_RESERVE_SAVE), + PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY ("22 - reserve", + "22 - reserve load measure", + "02 - save reserve"), + PERF_TALER_EXCHANGEDB_INIT_CMD_GET_RESERVE ("22 - get", + "22 - reserve"), + PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("", + "22 - reserve load measure"), + PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("22 - stop"), + PERF_TALER_EXCHANGEDB_INIT_CMD_GAUGER ("", + "22 - start", + "22 - stop", + "POSTGRES", + "Number of reserve loaded per second", + "item/sec", + NB_RESERVE_SAVE), + PERF_TALER_EXCHANGEDB_INIT_CMD_DEBUG ("End of reserve retreival"), + + PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("23 - start"), + PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("23 - reserve history measure", + NB_RESERVE_SAVE), + PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY ("23 - reserve", + "23 - reserve history measure", + "02 - save reserve"), + PERF_TALER_EXCHANGEDB_INIT_CMD_GET_RESERVE_HISTORY ("", + "23 - reserve"), + PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("", + "23 - reserve history measure"), + PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("23 - stop"), + PERF_TALER_EXCHANGEDB_INIT_CMD_GAUGER ("", + "23 - start", + "23 - stop", + "POSTGRES", + "Number of reserve history loaded per second", + "item/sec", + NB_RESERVE_SAVE), + PERF_TALER_EXCHANGEDB_INIT_CMD_DEBUG ("End of reserve history access"), + + + PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("24 - start"), + PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("24 - withdraw insert measure", + NB_WITHDRAW_SAVE), + PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY ("24 - reserve", + "24 - withdraw insert measure", + "02 - save reserve"), + PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY ("24 - denomination", + "24 - withdraw insert measure", + "01 - save denomination"), + PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_WITHDRAW ("24 - withdraw", + "24 - denomination", + "24 - reserve"), + PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_WITHDRAW ("24 - insert", + "24 - withdraw"), + PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("", + "24 - withdraw insert measure"), + PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("24 - stop"), + PERF_TALER_EXCHANGEDB_INIT_CMD_GAUGER ("", + "24 - start", + "24 - stop", + "POSTGRES", + "Number of withdraw insert per second", + "item/sec", + NB_WITHDRAW_SAVE), + PERF_TALER_EXCHANGEDB_INIT_CMD_DEBUG ("End of withdraw insertion"), + + PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("25 - start"), + PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("25 - withdraw insert measure", + NB_RESERVE_SAVE), + PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY ("25 - coin", + "25 - withdraw insert measure", + "03 - save coin"), + PERF_TALER_EXCHANGEDB_INIT_CMD_GET_WITHDRAW ("", + "25 - coin"), + PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("", + "25 - withdraw insert measure"), + PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("25 - stop"), + PERF_TALER_EXCHANGEDB_INIT_CMD_GAUGER ("", + "25 - start", + "25 - stop", + "POSTGRES", + "Number of withdraw loaded per second", + "item/sec", + NB_RESERVE_SAVE), + PERF_TALER_EXCHANGEDB_INIT_CMD_DEBUG ("End of withdraw loading"), + + PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("26 - start"), + PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("26 - get coin transaction", + NB_WITHDRAW_SAVE), + PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY ("26 - coin", + "26 - get coin transaction", + "03 - save coin"), + PERF_TALER_EXCHANGEDB_INIT_CMD_GET_COIN_TRANSACTION("", + "26 - coin"), + PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("", + "26 - get coin transaction"), + PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("26 - end"), + PERF_TALER_EXCHANGEDB_INIT_CMD_GAUGER ("", + "26 - start", + "26 - end", + "POSTGRES", + "Number of coin transaction history loaded per second", + "item/sec", + NB_WITHDRAW_SAVE), + PERF_TALER_EXCHANGEDB_INIT_CMD_DEBUG ("End of transaction loading"), + + PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("27 - start"), + PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("27 - /reserve/withdraw", + NB_WITHDRAW_SAVE), + PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY ("27 - reserve", + "27 - /reserve/withdraw", + "02 - save reserve"), + PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY ("27 - dki", + "27 - /reserve/withdraw", + "01 - save denomination"), + PERF_TALER_EXCHANGEDB_INIT_CMD_WITHDRAW_SIGN ("", + "27 - dki", + "27 - reserve"), + PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("", + "27 - /reserve/withdraw"), + PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("27 - end"), + PERF_TALER_EXCHANGEDB_INIT_CMD_GAUGER ("", + "27 - start", + "27 - end", + "POSTGRES", + "Number of /reserve/withdraw per second", + "item/sec", + NB_WITHDRAW_SAVE), + PERF_TALER_EXCHANGEDB_INIT_CMD_DEBUG ("End of /reserve/withdraw"), + + PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("28 - start"), + PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("28 - /deposit", + NB_DEPOSIT_SAVE), + PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY ("28 - coin", + "28 - /deposit", + "03 - save coin"), + PERF_TALER_EXCHANGEDB_INIT_CMD_DEPOSIT ("28 - deposit", + "28 - coin"), + PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("", + "28 - /deposit"), + PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("28 - stop"), + PERF_TALER_EXCHANGEDB_INIT_CMD_GAUGER ("", + "28 - start", + "28 - stop", + "POSTGRES", + "Number of /deposit per second", + "item/sec", + NB_DEPOSIT_SAVE), + PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("29 - start"), + PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("29 - insert refresh session", + NB_REFRESH_SAVE), + PERF_TALER_EXCHANGEDB_INIT_CMD_START_TRANSACTION (""), + PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_REFRESH_SESSION (""), + PERF_TALER_EXCHANGEDB_INIT_CMD_COMMIT_TRANSACTION (""), + PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("", + "29 - insert refresh session"), + PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("29 - stop"), + PERF_TALER_EXCHANGEDB_INIT_CMD_GAUGER ("", + "29 - start", + "29 - stop", + "POSTGRES", + "Number of refresh session inserted per second", + "item/sec", + NB_REFRESH_SAVE), + PERF_TALER_EXCHANGEDB_INIT_CMD_END (""), + }; + + ret = PERF_TALER_EXCHANGEDB_run_benchmark ( + "perf-taler-exchangedb", + "./test-exchange-db-postgres.conf", + (struct PERF_TALER_EXCHANGEDB_Cmd []) {PERF_TALER_EXCHANGEDB_INIT_CMD_END("")}, + benchmark); + if (GNUNET_SYSERR == ret) + return 1; + return 0; +} diff --git a/src/exchangedb/perf_taler_exchangedb_init.c b/src/exchangedb/perf_taler_exchangedb_init.c new file mode 100644 index 000000000..2e613b3cf --- /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 000000000..0ff074108 --- /dev/null +++ b/src/exchangedb/perf_taler_exchangedb_init.h @@ -0,0 +1,257 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> + */ +/** + * @file exchangedb/perf_taler_exchangedb_init.h + * @brief Heler function for creating dummy inputs for the exchange database + * @author Nicolas Fournier + */ +#ifndef __PERF_TALER_EXCHANGEDB_INIT_H___ +#define __PERF_TALER_EXCHANGEDB_INIT_H___ + +#include "taler_exchangedb_plugin.h" + + +#define CURRENCY "EUR" + +/** + * All information about a reserve + */ +struct PERF_TALER_EXCHANGEDB_Reserve +{ + /** + * Information about a rserve available to the Exchange + */ + struct TALER_EXCHANGEDB_Reserve reserve; + + /** + * Private key of a reserve + */ + struct GNUNET_CRYPTO_EddsaPrivateKey private; +}; + + +/** + * All informations about a coin + */ +struct PERF_TALER_EXCHANGEDB_Coin +{ + /** + * Blinded coin, known by the exchange + */ + struct TALER_EXCHANGEDB_CollectableBlindcoin blind; + + /** + * Public key of the coin and othes informations + */ + struct TALER_CoinPublicInfo public_info; + + /** + * Private key of the coin + */ + struct GNUNET_CRYPTO_EddsaPrivateKey priv; +}; + + +/** + * Generate a dummy DenominationKeyInformation for testing purposes + * @return a dummy denomination key + */ +struct TALER_EXCHANGEDB_DenominationKeyIssueInformation * +PERF_TALER_EXCHANGEDB_denomination_init (void); + + +/** + * Copies the given denomination + * @param reserve the deposit copy + * @return a copy of @a deposit; NULL if error + */ +struct TALER_EXCHANGEDB_DenominationKeyIssueInformation * +PERF_TALER_EXCHANGEDB_denomination_copy ( + const struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki); + + +/** + * Free memory of a DenominationKeyIssueInformation + * @param dki pointer to the struct to free + */ +int +PERF_TALER_EXCHANGEDB_denomination_free ( + struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki); + + +/** + * Generate a dummy reserve for testing + * @return a reserve with 1000 EUR in it + */ +struct PERF_TALER_EXCHANGEDB_Reserve * +PERF_TALER_EXCHANGEDB_reserve_init (void); + + +/** + * Copies the given reserve + * @param reserve the reserve to copy + * @return a copy of @a reserve; NULL if error + */ +struct PERF_TALER_EXCHANGEDB_Reserve * +PERF_TALER_EXCHANGEDB_reserve_copy (const struct PERF_TALER_EXCHANGEDB_Reserve *reserve); + + +/** + * Free memory of a reserve + * @param reserve pointer to the structure to be freed + */ +int +PERF_TALER_EXCHANGEDB_reserve_free (struct PERF_TALER_EXCHANGEDB_Reserve *reserve); + + +/** + * Generate a dummy deposit for testing purposes + * @param dki the denomination key used to sign the key + */ +struct TALER_EXCHANGEDB_Deposit * +PERF_TALER_EXCHANGEDB_deposit_init ( + const struct PERF_TALER_EXCHANGEDB_Coin *coin); + + +/** + * Copies the given deposit + * @param reserve the deposit copy + * @return a copy of @a deposit; NULL if error + */ +struct TALER_EXCHANGEDB_Deposit * +PERF_TALER_EXCHANGEDB_deposit_copy (const struct TALER_EXCHANGEDB_Deposit *deposit); + + +/** + * Free memory of a deposit + * @param deposit pointer to the structure to free + */ +int +PERF_TALER_EXCHANGEDB_deposit_free (struct TALER_EXCHANGEDB_Deposit *deposit); + + +/** + * Generate a coin for testing purpuses + * @param dki denomination key used to sign the coin + * @param reserve reserve providing the money for the coin + * @return a randomly generated CollectableBlindcoin + */ +struct PERF_TALER_EXCHANGEDB_Coin * +PERF_TALER_EXCHANGEDB_coin_init ( + const struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki, + const struct PERF_TALER_EXCHANGEDB_Reserve *reserve); + + +/** + * Copies the given coin + * @param coin the coin to copy + * @return a copy of coin; NULL if error + */ +struct PERF_TALER_EXCHANGEDB_Coin * +PERF_TALER_EXCHANGEDB_coin_copy ( + const struct PERF_TALER_EXCHANGEDB_Coin *coin); + + +/** + * Liberate memory of @a coin + * @param coin pointer to the structure to free + */ +int +PERF_TALER_EXCHANGEDB_coin_free ( + struct PERF_TALER_EXCHANGEDB_Coin *coin); + + +/** + * @return a randomly generated refresh session + */ +struct TALER_EXCHANGEDB_RefreshSession * +PERF_TALER_EXCHANGEDB_refresh_session_init (void); + + +/** + * @return #GNUNET_OK if the copy was successful, #GNUNET_SYSERR if it wasn't + */ +int +PERF_TALER_EXCHANGEDB_refresh_session_copy (struct TALER_EXCHANGEDB_RefreshSession *session, + struct TALER_EXCHANGEDB_RefreshSession *copy); + + +/** + * Frees memory of a refresh_session + */ +int +PERF_TALER_EXCHANGEDB_refresh_session_free ( + struct TALER_EXCHANGEDB_RefreshSession *refresh_session); + + +/** + * Create a melt operation + * + * @param session the refresh session + * @param dki the denomination the melted coin uses + * @return a pointer to a #TALER_EXCHANGEDB_RefreshMelt + */ +struct TALER_EXCHANGEDB_RefreshMelt * +PERF_TALER_EXCHANGEDB_refresh_melt_init (struct GNUNET_HashCode *session, + struct PERF_TALER_EXCHANGEDB_Coin *coin); + + +/** + * Copies the internals of a #TALER_EXCHANGEDB_RefreshMelt + * + * @param melt the refresh melt to copy + * @return an copy of @ melt + */ +struct TALER_EXCHANGEDB_RefreshMelt * +PERF_TALER_EXCHANGEDB_refresh_melt_copy (const struct TALER_EXCHANGEDB_RefreshMelt *melt); + + +/** + * Free the internal memory of a #TALER_EXCHANGEDB_RefreshMelt + * + * @param melt the #TALER_EXCHANGEDB_RefreshMelt to free + * @return #GNUNET_OK if the operation was successful, #GNUNET_SYSERROR + */ +int +PERF_TALER_EXCHANGEDB_refresh_melt_free (struct TALER_EXCHANGEDB_RefreshMelt *melt); + + +/** + * Create a #TALER_EXCHANGEDB_RefreshCommitCoin + */ +struct TALER_EXCHANGEDB_RefreshCommitCoin * +PERF_TALER_EXCHANGEDB_refresh_commit_coin_init (void); + + +/** + * Copies a #TALER_EXCHANGEDB_RefreshCommitCoin + * + * @param commit_coin the commit to copy + * @return a copy of @a commit_coin + */ +struct TALER_EXCHANGEDB_RefreshCommitCoin * +PERF_TALER_EXCHANGEDB_refresh_commit_coin_copy (struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coin); + + +/** + * Free a #TALER_EXCHANGEDB_RefreshCommitCoin + * + * @param commit_coin the coin to free + */ +void +PERF_TALER_EXCHANGEDB_refresh_commit_coin_free (struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coin); + +#endif diff --git a/src/exchangedb/perf_taler_exchangedb_interpreter.c b/src/exchangedb/perf_taler_exchangedb_interpreter.c new file mode 100644 index 000000000..75b32cb60 --- /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 000000000..a83251c60 --- /dev/null +++ b/src/exchangedb/perf_taler_exchangedb_interpreter.h @@ -0,0 +1,1319 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file exchangedb/perf_taler_exchangedb_interpreter.h + * @brief Library for performance analysis of the Taler database + * @author Nicolas Fournier + * + * This library contains functions and macro alowing Taler performance analysis + * to be written with ease. + * To do so, create a #PERF_TALER_EXCHANGEDB_Cmd array and fill it with the commands + * to execute in chronological order. Some command have an exposed variable wich + * can be reused in other commands. + * Macros are available to make the use much easier so feel free to use them + * to initialize your own command array. + */ + +#ifndef __PERF_TALER_EXCHANGEDB_INTERPRETER_H__ +#define __PERF_TALER_EXCHANGEDB_INTERPRETER_H__ + +#include <sys/time.h> +#include "taler_exchangedb_plugin.h" + + +#define PERF_TALER_EXCHANGEDB_NB_DENOMINATION_INIT 10 +#define PERF_TALER_EXCHANGEDB_NB_DENOMINATION_SAVE 10 + +#define PERF_TALER_EXCHANGEDB_NB_RESERVE_INIT 100 +#define PERF_TALER_EXCHANGEDB_NB_RESERVE_SAVE 10 + +#define PERF_TALER_EXCHANGEDB_NB_DEPOSIT_INIT 100 +#define PERF_TALER_EXCHANGEDB_NB_DEPOSIT_SAVE 10 + +#define PERF_TALER_EXCHANGEDB_NB_WITHDRAW_INIT 100 +#define PERF_TALER_EXCHANGEDB_NB_WITHDRAW_SAVE 10 + + +/** + * Marks the end of the command chain + * + * @param _label The label of the command + */ +#define PERF_TALER_EXCHANGEDB_INIT_CMD_END(_label) \ +{ \ + .command = PERF_TALER_EXCHANGEDB_CMD_END, \ + .label = _label, \ + .exposed.type = PERF_TALER_EXCHANGEDB_NONE \ +} + + +/** + * Prints @ _label to stdout + * + * @param _label The label of the command, + * will be logged each time the command runs + */ +#define PERF_TALER_EXCHANGEDB_INIT_CMD_DEBUG(_label) \ +{ \ + .command = PERF_TALER_EXCHANGEDB_CMD_DEBUG, \ + .label = _label, \ + .exposed.type = PERF_TALER_EXCHANGEDB_NONE \ +} + +/** + * The begining of a loop + * + * @param _label the label of the loop + * @param _iter the number of iterations of the loop + */ +#define PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP(_label, _iter) \ +{ \ + .command = PERF_TALER_EXCHANGEDB_CMD_LOOP , \ + .label = _label , \ + .exposed.type = PERF_TALER_EXCHANGEDB_NONE , \ + .details.loop = { \ + .max_iterations = _iter , \ + .curr_iteration = 0 } \ +} + +/** + * Marks the end of the loop @_label_loop + * + * @param _label the label of the command + * @param _label_loop the label of the loop closed by this command + */ +#define PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP(_label, _label_loop) \ +{\ + .command = PERF_TALER_EXCHANGEDB_CMD_END_LOOP , \ + .label = _label , \ + .exposed.type = PERF_TALER_EXCHANGEDB_NONE , \ + .details.end_loop.label_loop = _label_loop \ +} + +/** + * Saves the time of execution to use for logging with Gauger + * + * @param _label the label of the command + */ +#define PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME(_label) \ +{ \ + .command = PERF_TALER_EXCHANGEDB_CMD_GET_TIME, \ + .label = _label, \ + .exposed.type = PERF_TALER_EXCHANGEDB_TIME \ +} + +/** + * Commits the duration between @a _label_start and @a _label_stop + * to Gauger with @a _description explaining what was measured. + * + * @param _label the label of this command + * @param _label_start label of the start of the measurment + * @param _label_stop label of the end of the measurment + * @param _description description of the measure displayed in Gauger + * @param _unit the unit of the data measured, typicly something/sec + * @param _divide number of measurments in the interval + */ +#define PERF_TALER_EXCHANGEDB_INIT_CMD_GAUGER(_label, _label_start, _label_stop, _category, _description, _unit, _divide) \ +{ \ + .command = PERF_TALER_EXCHANGEDB_CMD_GAUGER, \ + .label = _label, \ + .exposed.type = PERF_TALER_EXCHANGEDB_NONE, \ + .details.gauger = { \ + .label_start = _label_start, \ + .label_stop = _label_stop, \ + .category = _category, \ + .description = _description, \ + .unit = _unit, \ + .divide = _divide, \ + } \ +} + +/** + * Initiate a database transaction + * + * @param _label the label of the command + */ +#define PERF_TALER_EXCHANGEDB_INIT_CMD_START_TRANSACTION(_label) \ +{ \ + .command = PERF_TALER_EXCHANGEDB_CMD_START_TRANSACTION, \ + .label = _label, \ + .exposed.type = PERF_TALER_EXCHANGEDB_NONE, \ +} + +/** + * Commits a database transaction + * + * @param _label the label of the command + */ +#define PERF_TALER_EXCHANGEDB_INIT_CMD_COMMIT_TRANSACTION(_label) \ +{ \ + .command = PERF_TALER_EXCHANGEDB_CMD_COMMIT_TRANSACTION, \ + .label = _label, \ + .exposed.type = PERF_TALER_EXCHANGEDB_NONE, \ +} + +/** + * Abort the current transaction + * + * @param _label the label of the command + */ +#define PERF_TALER_EXCHANGEDB_INIT_CMD_ABORT_TRANSACTION(_label) \ +{ \ + .command = PERF_TALER_EXCHANGEDB_CMD_ABORT_TRANSACTION, \ + .label = _label, + +/** + * Saves randomly selected items from @a _label_save + * Saved items can latter be access using #PERF_TALER_EXCHANGEDB_CMD_LOAD_ARRAY + * + * @param _label the label of the command, used by other commands to reference it + * @param _label_loop the label of the loop the array iterates over + * @param _label_save the label of the command which outout is saved by this command + * @param _nb_saved the total number of items to be saved + */ +#define PERF_TALER_EXCHANGEDB_INIT_CMD_SAVE_ARRAY(_label, _label_loop, _label_save, _nb_saved) \ +{ \ + .command = PERF_TALER_EXCHANGEDB_CMD_SAVE_ARRAY, \ + .label = _label, \ + .exposed.type = PERF_TALER_EXCHANGEDB_NONE, \ + .details.save_array = { \ + .label_loop = _label_loop, \ + .label_save = _label_save, \ + .nb_saved = _nb_saved, \ + } \ +} + +/** + * Loads data from a #PERF_TALER_EXCHANGEDB_CMD_SAVE_ARRAY to allow other + * commands to access it + * + * @param _label the label of this command, referenced by commands to access it's outpout + * @param _label_loop the label of the loop to iterate over + * @param _label_save the label of the #PERF_TALER_EXCHANGEDB_CMD_SAVE_ARRAY providing data + */ +#define PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY(_label, _label_loop, _label_save) \ +{ \ + .command = PERF_TALER_EXCHANGEDB_CMD_LOAD_ARRAY, \ + .label = _label, \ + .exposed.type = PERF_TALER_EXCHANGEDB_NONE, \ + .details.load_array = { \ + .label_loop = _label_loop, \ + .label_save = _label_save \ + } \ +} + +/** + * Create a denomination key to use + * Exposes a #PERF_TALER_EXCHANGEDB_DENOMINATION_INFO to be used by other commands + * @exposed #PERF_TALER_EXCHANGEDB_DENOMINATION_INFO + * + * @param _label the label of this command + */ +#define PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_DENOMINATION(_label) \ +{ \ + .command = PERF_TALER_EXCHANGEDB_CMD_CREATE_DENOMINATION, \ + .label = _label, \ + .exposed.type = PERF_TALER_EXCHANGEDB_DENOMINATION_INFO, \ +} + +/** + * Inserts informations about a denomination key in the database + * + * @param _label the label of this command + * @param _label_denom the label of the denomination to insert + */ +#define PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_DENOMINATION(_label, _label_denom) \ +{ \ + .command = PERF_TALER_EXCHANGEDB_CMD_INSERT_DENOMINATION, \ + .label = _label, \ + .exposed.type = PERF_TALER_EXCHANGEDB_NONE, \ + .details.insert_denomination.label_denom = _label_denom, \ +} + +/** + * Polls the database about informations regarding a specific denomination key + * + * @param _label the label of this command + * @param _label_denom the label of the command providing information about the denomination key + */ +#define PERF_TALER_EXCHANGEDB_INIT_CMD_GET_DENOMINATION(_label, _label_denom) \ +{ \ + .command = PERF_TALER_EXCHANGEDB_CMD_GET_DENOMINATION, \ + .label = _label, \ + .exposed.type = PERF_TALER_EXCHANGEDB_NONE, \ + .details.get_denomination.label_denom = _label_denom \ +} + +/** + * Create a reserve to be used later + * Exposes a #PERF_TALER_EXCHANGEDB_RESERVE + * + * @param _label the label of the command + */ +#define PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_RESERVE(_label) \ +{ \ + .command = PERF_TALER_EXCHANGEDB_CMD_CREATE_RESERVE, \ + .label = _label, \ + .exposed.type = PERF_TALER_EXCHANGEDB_RESERVE \ +} + +/** + * Insert a new reserve in the database containing 1000 Euros + * + * @param _label the name of this command + * @param _label_reserve the label of the reserve to insert + */ +#define PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_RESERVE(_label, _label_reserve) \ +{ \ + .command = PERF_TALER_EXCHANGEDB_CMD_INSERT_RESERVE, \ + .label = _label, \ + .exposed.type = PERF_TALER_EXCHANGEDB_NONE, \ + .details.insert_reserve.label_reserve = _label_reserve \ +} + +/** + * Polls the database for a secific reserve's details + * + * @param _label the label of this command + * @param _label_reserve the reserve to poll + */ +#define PERF_TALER_EXCHANGEDB_INIT_CMD_GET_RESERVE(_label, _label_reserve) \ +{ \ + .command = PERF_TALER_EXCHANGEDB_CMD_GET_RESERVE, \ + .label = _label, \ + .exposed.type = PERF_TALER_EXCHANGEDB_NONE, \ + .details.get_reserve.label_reserve = _label_reserve \ +} + +/** + * Polls the database for the history of a reserve + * + * @param _label the label of the command + * @param _label_reserve the reserve to examine + */ +#define PERF_TALER_EXCHANGEDB_INIT_CMD_GET_RESERVE_HISTORY(_label, _label_reserve) \ +{ \ + .command = PERF_TALER_EXCHANGEDB_CMD_GET_RESERVE_HISTORY, \ + .label = _label, \ + .exposed.type = PERF_TALER_EXCHANGEDB_NONE, \ + .details.get_reserve_history.label_reserve = _label_reserve \ +} + +/** + * Creates a coin to be used later + * + * @param _label the label of this command + * @param _label_dki denomination key used to sign the coin + * @param _label_reserve reserve used to emmit the coin + */ +#define PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_WITHDRAW(_label, _label_dki, _label_reserve) \ +{ \ + .command = PERF_TALER_EXCHANGEDB_CMD_CREATE_WITHDRAW, \ + .label = _label, \ + .exposed.type = PERF_TALER_EXCHANGEDB_COIN, \ + .details.create_withdraw = {\ + .label_dki = _label_dki, \ + .label_reserve = _label_reserve, \ + } \ +} + +/** + * Inserts informations about a withdrawal in the database + * + * @exposes #PERF_TALER_EXCHANGEDB_COIN + * + * @param _label the label of this command + * @param _label_coin the coin to insert + */ +#define PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_WITHDRAW(_label, _label_coin) \ +{ \ + .command = PERF_TALER_EXCHANGEDB_CMD_INSERT_WITHDRAW, \ + .label = _label, \ + .exposed.type = PERF_TALER_EXCHANGEDB_NONE, \ + .details.insert_withdraw.label_coin = _label_coin\ +} + + +/** + * Polls the database about informations regarding a specific withdrawal + * + * @param _label the label of this command + * @param _label_coin the coin to check + */ +#define PERF_TALER_EXCHANGEDB_INIT_CMD_GET_WITHDRAW(_label, _label_coin) \ +{ \ + .command = PERF_TALER_EXCHANGEDB_CMD_GET_WITHDRAW, \ + .label = _label, \ + .exposed.type = PERF_TALER_EXCHANGEDB_NONE, \ + .details.get_withdraw.label_coin = _label_coin, \ +} + + +/** + * The /reserve/withdraw api call + * + * Exposes #PERF_TALER_EXCHANGEDB_COIN + * + * @param _label the label of this command + * @param _label_dki the denomination of the created coin + * @param _label_reserve the reserve used to provide currency + */ +#define PERF_TALER_EXCHANGEDB_INIT_CMD_WITHDRAW_SIGN(_label, _label_dki, _label_reserve) \ + PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_WITHDRAW (_label "withdraw", \ + _label_dki, \ + _label_reserve), \ + PERF_TALER_EXCHANGEDB_INIT_CMD_GET_DENOMINATION(_label "withdraw info", \ + _label_dki), \ + PERF_TALER_EXCHANGEDB_INIT_CMD_GET_RESERVE_HISTORY(_label "reserve_history", \ + _label_reserve), \ + PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_WITHDRAW(_label "insert withdraw", \ + _label "withdraw") + +/** + * Create a deposit for use later + * @exposes #PERF_TALER_EXCHANGEDB_DEPOSIT + * + * @param _label the label of this command + * @param _label_coin the coin used to pay + */ +#define PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_DEPOSIT(_label, _label_coin) \ +{ \ + .command = PERF_TALER_EXCHANGEDB_CMD_CREATE_DEPOSIT, \ + .label = _label, \ + .exposed.type = PERF_TALER_EXCHANGEDB_DEPOSIT, \ + .details.create_deposit.label_coin = _label_coin, \ +} + +/** + * Insert a deposit into the database + * + * @param _label the label of this command + * @param _label_deposit the deposit inseerted + */ +#define PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_DEPOSIT(_label, _label_deposit) \ +{ \ + .command = PERF_TALER_EXCHANGEDB_CMD_INSERT_DEPOSIT,\ + .label = _label, \ + .exposed.type = PERF_TALER_EXCHANGEDB_NONE, \ + .details.insert_deposit.label_deposit = _label_deposit, \ +} + +/** + * Check if a deposit is in the database + * + * @param _label the label of this command + * @param _label_deposit the deposit to use + */ +#define PERF_TALER_EXCHANGEDB_INIT_CMD_GET_DEPOSIT(_label, _label_deposit) \ +{ \ + .command = PERF_TALER_EXCHANGEDB_CMD_GET_DEPOSIT, \ + .label = _label, \ + .exposed.type = PERF_TALER_EXCHANGEDB_NONE, \ + .details.get_deposit.label_deposit = _label_deposit \ +} + +/** + * Access the transaction history of a coin + * + * @param _label the label of the command + * @param _label_coin the coin which history is checked + */ +#define PERF_TALER_EXCHANGEDB_INIT_CMD_GET_COIN_TRANSACTION(_label, _label_coin) \ +{ \ + .command = PERF_TALER_EXCHANGEDB_CMD_GET_COIN_TRANSACTION, \ + .label = _label, \ + .exposed.type = PERF_TALER_EXCHANGEDB_NONE, \ + .details.get_coin_transaction.label_coin = _label_coin \ +} + +/** + * The /deposit api call + * + * @param _label the label of the command + * @param _label_coin the coin used for the deposit + */ +#define PERF_TALER_EXCHANGEDB_INIT_CMD_DEPOSIT(_label, _label_coin) \ + PERF_TALER_EXCHANGEDB_INIT_CMD_GET_COIN_TRANSACTION (_label "coin history", \ + _label_coin), \ + PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_DEPOSIT (_label "deposit", \ + _label_coin), \ + PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_DEPOSIT (_label "insert", \ + _label "deposit") +/** + * Insert informations about a refresh session + * melts one coin into another + * + * @param _label the label of the command + */ +#define PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_REFRESH_SESSION(_label) \ +{ \ + .command = PERF_TALER_EXCHANGEDB_CMD_CREATE_REFRESH_SESSION, \ + .label = _label, \ + .exposed.type = PERF_TALER_EXCHANGEDB_REFRESH_HASH \ +} + +/** + * Get informations about a refresh session + * + * @param _label the label of the command + * @param _label_hash the label of the hash to search + */ +#define PERF_TALER_EXCHANGEDB_INIT_CMD_GET_REFRESH_SESSION(_label, \ + _label_hash) \ +{ \ + .command = PERF_TALER_EXCHANGEDB_CMD_GET_REFRESH_SESSION, \ + .label = _label, \ + .exposed.type = PERF_TALER_EXCHANGEDB_NONE \ +} + +/** + * Insert a melt operation in the database + * + * @param _label the label of the command + * @param _label_hash the label of the hash of the session + * @param _label_coin the label of the coin to melt + */ +#define PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_REFRESH_MELT(_label, \ + _label_hash, \ + _label_coin) \ +{ \ + .command = PERF_TALER_EXCHANGEDB_CMD_INSERT_REFRESH_MELT, \ + .label = _label, \ + .details.insert_refresh_melt.label_hash = _label_hash, \ + .details.insert_refresh_melt.label_coin = _label_coin, \ + .exposed.type = PERF_TALER_EXCHANGEDB_NONE \ +} + +/** + * Get informations about a melt operation + * + * @param _label the label of the command + * @param _label_hash the label of the hash of the refresh session + */ +#define PERF_TALER_EXCHANGEDB_INIT_CMD_GET_REFRESH_MELT(_label, \ + _label_hash) \ +{ \ + .command = PERF_TALER_EXCHANGEDB_CMD_GET_REFRESH_MELT, \ + .label = _label, \ + .detail.get_refresh_melt.label_hash = _label_hash, \ + .exposed.type = PERF_TALER_EXCHANGEDB_NONE \ +} + +/** + * The type of data stored in #PERF_TALER_EXCHANGEDB_Memory + */ +enum PERF_TALER_EXCHANGEDB_Type +{ + PERF_TALER_EXCHANGEDB_NONE, + PERF_TALER_EXCHANGEDB_TIME, + PERF_TALER_EXCHANGEDB_DENOMINATION_INFO, + PERF_TALER_EXCHANGEDB_RESERVE, + PERF_TALER_EXCHANGEDB_COIN, + PERF_TALER_EXCHANGEDB_DEPOSIT, + PERF_TALER_EXCHANGEDB_REFRESH_HASH, + PERF_TALER_EXCHANGEDB_REFRESH_MELT +}; + + +/** + * Structure used to handle several data type + */ +struct PERF_TALER_EXCHANGEDB_Data +{ + enum PERF_TALER_EXCHANGEDB_Type type; + + /** + * Storage for a variety of data type + * The data saved should match #type + */ + union PERF_TALER_EXCHANGEDB_Memory + { + /** #PERF_TALER_EXCHANGEDB_TIME */ + struct GNUNET_TIME_Absolute *time; + /** #PERF_TALER_EXCHANGEDB_DEPOSIT */ + struct TALER_EXCHANGEDB_Deposit *deposit; + /** #PERF_TALER_EXCHANGEDB_COIN */ + struct PERF_TALER_EXCHANGEDB_Coin *coin; + /** #PERF_TALER_EXCHANGEDB_RESERVE */ + struct PERF_TALER_EXCHANGEDB_Reserve *reserve; + /** #PERF_TALER_EXCHANGEDB_DENOMINATION_INFO */ + struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki; + /** #PERF_TALER_EXCHANGEDB_REFRESH_HASH */ + struct GNUNET_HashCode *session_hash; + /** #PERF_TALER_EXCHANGEDB_REFRESH_MELT */ + struct TALER_EXCHANGEDB_RefreshMelt *refresh_melt; + } data; +}; + + +/** + * Name of the command + */ +enum PERF_TALER_EXCHANGEDB_CMD_Name +{ + /** + * All comand chain must hace this as their last command + */ + PERF_TALER_EXCHANGEDB_CMD_END, + + /** + * Prints it's label + */ + PERF_TALER_EXCHANGEDB_CMD_DEBUG, + + /** + * Define the start of al command chain loop + */ + PERF_TALER_EXCHANGEDB_CMD_LOOP, + + /** + * Define the end of a command chain loop + */ + PERF_TALER_EXCHANGEDB_CMD_END_LOOP, + + /** + * Save the time at which the command was executed + */ + PERF_TALER_EXCHANGEDB_CMD_GET_TIME, + + /** + * Upload performance to Gauger + */ + PERF_TALER_EXCHANGEDB_CMD_GAUGER, + + /** + * Start a new session + */ + PERF_TALER_EXCHANGEDB_CMD_NEW_SESSION, + + /** + * Start a database transaction + */ + PERF_TALER_EXCHANGEDB_CMD_START_TRANSACTION, + + /** + * End a database transaction + */ + PERF_TALER_EXCHANGEDB_CMD_COMMIT_TRANSACTION, + + /** + * Abort a transaction started with #PERF_TALER_EXCHANGEDB_CMD_START_TRANSACTION + */ + PERF_TALER_EXCHANGEDB_CMD_ABORT_TRANSACTION, + + /** + * Saves random deposits from a loop + */ + PERF_TALER_EXCHANGEDB_CMD_SAVE_ARRAY, + + /** + * Load items saved earlier in a #PERF_TALER_EXCHANGEDB_CMD_SAVE_ARRAY + * The items are loaded in a random order, but all of them will be loaded + */ + PERF_TALER_EXCHANGEDB_CMD_LOAD_ARRAY, + + /** + * Loads a random item from a #PERF_TALER_EXCHANGEDB_CMD_SAVE_ARRAY + * A random item is loaded each time the command is run + */ + PERF_TALER_EXCHANGEDB_CMD_LOAD_RANDOM, + + /** + * Create a denomination to be used later + */ + PERF_TALER_EXCHANGEDB_CMD_CREATE_DENOMINATION, + + /** + * Insert informations about a denomination key in the database + */ + PERF_TALER_EXCHANGEDB_CMD_INSERT_DENOMINATION, + + /** + * Polls the database for informations about a specific denomination key + */ + PERF_TALER_EXCHANGEDB_CMD_GET_DENOMINATION, + + /** + * Create a reserve to be used later + */ + PERF_TALER_EXCHANGEDB_CMD_CREATE_RESERVE, + + /** + * Insert currency in a reserve / Create a reserve + */ + PERF_TALER_EXCHANGEDB_CMD_INSERT_RESERVE, + + /** + * Get Informations about a reserve + */ + PERF_TALER_EXCHANGEDB_CMD_GET_RESERVE, + + /** + * Get the history of a reserve + */ + PERF_TALER_EXCHANGEDB_CMD_GET_RESERVE_HISTORY, + + /** + * Create a withdrawal to be used later + */ + PERF_TALER_EXCHANGEDB_CMD_CREATE_WITHDRAW, + + /** + * Insert informations about a withdrawal in the database + */ + PERF_TALER_EXCHANGEDB_CMD_INSERT_WITHDRAW, + + /** + * Pulls informations about a withdrawal from the database + */ + PERF_TALER_EXCHANGEDB_CMD_GET_WITHDRAW, + + /** + * Get the list of all transactions the coin has been in + */ + PERF_TALER_EXCHANGEDB_CMD_GET_COIN_TRANSACTION, + + /** + * Create a deposit to be used later + */ + PERF_TALER_EXCHANGEDB_CMD_CREATE_DEPOSIT, + + /** + * Insert a deposit into the database + */ + PERF_TALER_EXCHANGEDB_CMD_INSERT_DEPOSIT, + + /** + * Check if a deposit is in the database + */ + PERF_TALER_EXCHANGEDB_CMD_GET_DEPOSIT, + + /** + * Create a refresh session + * The number of melted coins is 1, + * The number of exchangeed coins is 1 + */ + PERF_TALER_EXCHANGEDB_CMD_CREATE_REFRESH_SESSION, + + /** + * Get a refresh session informations + */ + PERF_TALER_EXCHANGEDB_CMD_GET_REFRESH_SESSION, + + /** + * Insert a refresh melt + */ + PERF_TALER_EXCHANGEDB_CMD_INSERT_REFRESH_MELT, + + /** + * Get informations about a refresh melt operation + */ + PERF_TALER_EXCHANGEDB_CMD_GET_REFRESH_MELT, + + /** + * Insert a melt refresh order + */ + PERF_TALER_EXCHANGEDB_CMD_INSERT_REFRESH_ORDER, + + /** + * Get informations about a refresh order + */ + PERF_TALER_EXCHANGEDB_CMD_GET_REFRESH_ORDER, + + /** + * Insert refresh commit coin + */ + PERF_TALER_EXCHANGEDB_CMD_INSERT_REFRESH_COMMIT_COIN, + + /** + * Get refresh commit coin + */ + PERF_TALER_EXCHANGEDB_CMD_GET_REFRESH_COMMIT_COIN, + + /** + * Insert refresh commit link + */ + PERF_TALER_EXCHANGEDB_CMD_INSERT_REFRESH_COMMIT_LINK, + + /** + * Get refresh commit link + */ + PERF_TALER_EXCHANGEDB_CMD_GET_REFRESH_COMMIT_LINK, + + /** + * Get information avout the melt commit + */ + PERF_TALER_EXCHANGEDB_CMD_GET_MELT_COMMITMENT, + + /** + * Insert a new coin into the database after a melt operation + */ + PERF_TALER_EXCHANGEDB_CMD_INSERT_REFRESH_OUT, + + /** + * Get the link data list of a coin + */ + PERF_TALER_EXCHANGEDB_CMD_GET_LINK_DATA_LIST, + + /** + * Get the shared secret and the transfere public key + */ + PERF_TALER_EXCHANGEDB_CMD_GET_TRANSFER + +}; + + +/** + * Contains extra data required for any command + */ +union PERF_TALER_EXCHANGEDB_CMD_Details +{ + /** + * Extra data requiered for the #PERF_TALER_EXCHANGEDB_CMD_LOOP command + */ + struct PERF_TALER_EXCHANGEDB_CMD_loopDetails + { + /** + * Maximum number of iteration in the loop + */ + const unsigned int max_iterations; + + /** + * The current iteration of the loop + */ + unsigned int curr_iteration; + } loop; + + /** + * Extra data requiered by the #PERF_TALER_EXCHANGEDB_CMD_END_LOOP command + */ + struct PERF_TALER_EXCHANGEDB_CMD_endLoopDetails + { + /** + * Label of the loop closed by the command + */ + const char *label_loop; + unsigned int index_loop; + } end_loop; + + /** + * Details about the #PERF_TALER_EXCHANGEDB_CMD_GAUGER command + */ + struct PERF_TALER_EXCHANGEDB_CMD_gaugerDetails + { + /** + * Label of the starting timestamp + */ + const char *label_start; + unsigned int index_start; + + /** + * Label of the ending timestamp + */ + const char *label_stop; + unsigned int index_stop; + + /** + * The category of the measurment + */ + const char *category; + + /** + * Description of the metric, used in Gauger + */ + const char *description; + + /** + * The name of the metric beeing used + */ + const char *unit; + + /** + * Constant the result needs to be divided by + * to get the result per unit + */ + float divide; + } gauger; + + /** + * Contains extra data requiered by the #PERF_TALER_EXCHANGEDB_CMD_SAVE_ARRAY command + */ + struct PERF_TALER_EXCHANGEDB_CMD_saveArrayDetails + { + /** + * Number of items to save + */ + unsigned int nb_saved; + + /** + * Number of items already saved + */ + unsigned int index; + + /** + * Label of the loop it is attached to + */ + const char *label_loop; + unsigned int index_loop; + + /** + * Label of the command exposing the item + */ + const char *label_save; + unsigned int index_save; + + /** + * Array of data saved + */ + struct PERF_TALER_EXCHANGEDB_Data *data_saved; + + /** + * Type of the data that will be stored in @a data_saved, for + * 'static' type checking. + */ + enum PERF_TALER_EXCHANGEDB_Type type_saved; + + } save_array; + + /** + * Extra data required for the #PERF_TALER_EXCHANGEDB_CMD_LOAD_ARRAY command + */ + struct PERF_TALER_EXCHANGEDB_CMD_loadArrayDetails + { + /** + * The loop in which the command is located + */ + const char *label_loop; + unsigned int index_loop; + + /** + * Label of the command where the items were saved + */ + const char *label_save; + unsigned int index_save; + + /** + * A permutation array used to randomize the order the items are loaded in + */ + unsigned int *permutation; + } load_array; + + /** + * Contains data for the #PERF_TALER_EXCHANGEDB_CMD_LOAD_RANDOM command + */ + struct PERF_TALER_EXCHANGEDB_CMD_loadRandomDetails + { + /** + * The label of the #PERF_TALER_EXCHANGEDB_CMD_SAVE_ARRAY the items will be extracted from + */ + const char *label_save; + unsigned int index_save; + } load_random; + + /** + * Extra data requiered by the #PERF_TALER_EXCHANGEDB_CMD_INSERT_DENOMINATION command + */ + struct PERF_TALER_EXCHANGEDB_CMD_insertDenominationDetails + { + /** + * The label of the source of the denomination to insert + */ + const char *label_denom; + unsigned int index_denom; + } insert_denomination; + + /** + * Extra data requiered by the #PERF_TALER_EXCHANGEDB_CMD_GET_DENOMINATION command + */ + struct PERF_TALER_EXCHANGEDB_CMD_getDenominationDetails + { + /** + * The label of the source of the denomination to check + */ + const char *label_denom; + unsigned int index_denom; + } get_denomination; + + /** + * Extra data requiered for the #PERF_TALER_EXCHANGEDB_CMD_INSERT_RESERVE command + */ + struct PERF_TALER_EXCHANGEDB_CMD_insertReserveDetails + { + /** + * The label of the source of the reserve to insert + */ + const char *label_reserve; + unsigned int index_reserve; + } insert_reserve; + + /** + * Extra data requiered for the #PERF_TALER_EXCHANGEDB_CMD_GET_RESERVE command + */ + struct PERF_TALER_EXCHANGEDB_CMD_getReserveDetails + { + /** + * The label of the source of the reserve to check + */ + const char *label_reserve; + unsigned int index_reserve; + } get_reserve; + + /** + * Extra data requiered for the #PERF_TALER_EXCHANGEDB_CMD_GET_RESERVE_HISTORY command + */ + struct PERF_TALER_EXCHANGEDB_CMD_getReserveHistoryDetails + { + /** + * The label of the source of the reserve to check + */ + const char *label_reserve; + unsigned int index_reserve; + } get_reserve_history; + + /** + * Extra data related to the #PERF_TALER_EXCHANGEDB_CMD_CREATE_WITHDRAW command + */ + struct PERF_TALER_EXCHANGEDB_CMD_createWithdrawDetails + { + /** + * label of the denomination key used to sign the coin + */ + const char *label_dki; + unsigned int index_dki; + + /** + * label of the reserve the money to exchange the coin comes from + */ + const char *label_reserve; + unsigned int index_reserve; + } create_withdraw; + + /** + * data requiered for the #PERF_TALER_EXCHANGEDB_CMD_INSERT_WITHDRAW + */ + struct PERF_TALER_EXCHANGEDB_CMD_insertWithdrawDetails + { + /** + * label of the source for the coin information + */ + const char *label_coin; + unsigned int index_coin; + } insert_withdraw; + + /** + * data requiered for the #PERF_TALER_EXCHANGEDB_CMD_GET_WITHDRAW + */ + struct PERF_TALER_EXCHANGEDB_CMD_getWithdraw + { + /** + * label of the source for the coin information + */ + const char *label_coin; + unsigned int index_coin; + } get_withdraw; + + /** + * Data requiered for the #PERF_TALER_EXCHANGEDB_CMD_GET_COIN_TRANSACTION command + */ + struct PERF_TALER_EXCHANGEDB_CMD_getCoinTransactionDetails + { + /** + * The coin which history is checked + */ + const char *label_coin; + unsigned int index_coin; + } get_coin_transaction; + + /** + * Data used by the #PERF_TALER_EXCHANGEDB_CMD_CREATE_DEPOSIT command + */ + struct PERF_TALER_EXCHANGEDB_CMD_createDepositDetails + { + /** + * Label of the source where the reserve used to create the coin is + */ + const char *label_coin; + unsigned int index_coin; + } create_deposit; + + /** + * Extra data requiered for the #PERF_TALER_EXCHANGEDB_CMD_INSERT_DEPOSIT command + */ + struct PERF_TALER_EXCHANGEDB_CMD_insertDepositDetails + { + /** + * The label of the source of the deposit to check + */ + const char *label_deposit; + unsigned int index_deposit; + } insert_deposit; + + /** + * Extra data requiered for the #PERF_TALER_EXCHANGEDB_CMD_GET_DEPOSIT command + */ + struct PERF_TALER_EXCHANGEDB_CMD_getDepositDetails + { + /** + * The label of the source of the deposit to check + */ + const char *label_deposit; + unsigned int index_deposit; + } get_deposit; + + /** + * Data requiered for the #PERF_TALER_EXCHANGEDB_CMD_GET_REFRESH_SESSION command + */ + struct PERF_TALER_EXCHANGEDB_CMD_getRefreshSessionDetails + { + /** + * label of the source of the hash of the session + */ + const char *label_hash; + unsigned int index_hash; + } get_refresh_session; + + /** + * Data requiered for the #PERF_TALER_EXCHANGEDB_CMD_INSERT_REFRESH_MELT command + */ + struct PERF_TALER_EXCHANGEDB_CMD_insertRefreshMeltDetails + { + /** + * The label of the hash of the refresh session + */ + const char *label_hash; + unsigned int index_hash; + + /** + * The label of the coin to melt + */ + const char *label_coin; + unsigned int index_coin; + } insert_refresh_melt; + + /** + * Data requiered for the #PERF_TALER_EXCHANGEDB_CMD_GET_REFRESH_MELT command + */ + struct PERF_TALER_EXCHANGEDB_CMD_getRefreshMeltDetails + { + /** + * The label of the hash of the session + */ + const char *label_hash; + unsigned int index_hash; + } get_refresh_melt; + + /** + * Data requiered for the #PERF_TALER_EXCHANGEDB_CMD_INSERT_REFRESH_ORDER command + */ + struct PERF_TALER_EXCHANGEDB_CMD_insertRefreshOrderDetails + { + /** + * The refresh session hash + */ + const char *label_hash; + unsigned int index_hash; + + /** + * The new coin denomination + */ + const char *label_denom; + unsigned int index_denom; + } insert_refresh_order; + + /** + * Data requiered for the #PERF_TALER_EXCHANGEDB_CMD_GET_REFRESH_ORDER command + */ + struct PERF_TALER_EXCHANGEDB_CMD_getRefreshOrderDetails + { + /** + * The session hash + */ + const char *label_hash; + unsigned int index_hash; + + } get_refresh_order; + + /** + * Data requiered for the #PERF_TALER_EXCHANGEDB_CMD_INSERT_REFRESH_COMMIT_COIN command + */ + struct PERF_TALER_EXCHANGEDB_CMD_insertRefreshCommitCoinDetails + { + /** + * The refresh session hash + */ + const char *label_hash; + unsigned int index_hash; + + } insert_refresh_commit_coin; + + /** + * Data requiered for the #PERF_TALER_EXCHANGEDB_CMD_GET_REFRESH_COMMIT_COIN command + */ + struct PERF_TALER_EXCHANGEDB_CMD_getRefreshCommitCoinDetails + { + /** + * The refresh session hash + */ + const char *label_hash; + unsigned int index_hash; + + } get_refresh_commit_coin; + + /** + * Data requiered for the #PERF_TALER_EXCHANGEDB_CMD_INSERT_REFRESH_COMMIT_LINK command + */ + struct PERF_TALER_EXCHANGEDB_CMD_insertRefreshCommitLinkDetails + { + /** + * The refresh session hash + */ + const char *label_hash; + unsigned int index_hash; + + } insert_refresh_commit_link; + + /** + * Data requiered by the #PERF_TALER_EXCHANGEDB_CMD_GET_REFRESH_COMMIT_LINK command + */ + struct PERF_TALER_EXCHANGEDB_CMD_getRefreshCommitLinkDetails + { + /** + * The refresh session hash + */ + const char *label_hash; + unsigned int index_hash; + } get_refresh_commit_link; + + /** + * Data requiered for the #PERF_TALER_EXCHANGEDB_CMD_GET_MELT_COMMITMENT command + */ + struct PERF_TALER_EXCHANGEDB_CMD_getMeltCommitmentDaetails + { + /** + * The refresh session hash + */ + const char *label_hash; + unsigned int index_hash; + } get_melt_commitment; + + /** + * Data requiered by the #PERF_TALER_EXCHANGEDB_CMD_INSERT_REFRESH_OUT command + */ + struct PERF_TALER_EXCHANGEDB_CMD_insertRefreshOutDetails + { + /** + * The refresh session hash + */ + const char *label_hash; + unsigned int index_hash; + } insert_refresh_out; + + /** + * Data requiered by the #PERF_TALER_EXCHANGEDB_CMD_GET_LINK_DATA_LIST command + */ + struct PERF_TALER_EXCHANGEDB_CMD_getLinkDataListDetails + { + /** + * The refresh session hash + */ + const char *label_hash; + unsigned int index_hash; + } get_link_data_list; + + /** + * Data requiered by the #PERF_TALER_EXCHANGEDB_CMD_GET_TRANSFER command + */ + struct PERF_TALER_EXCHANGEDB_CMD_getTransferDetails + { + /** + * The refresh session hash + */ + const char *label_hash; + unsigned int index_hash; + } get_transfer; + +}; + + +/** + * Command to be interpreted. + */ +struct PERF_TALER_EXCHANGEDB_Cmd +{ + /** + * Type of the command + */ + enum PERF_TALER_EXCHANGEDB_CMD_Name command; + + /** + * Label to refer to the command + */ + const char *label; + + /** + * Command specific data + */ + union PERF_TALER_EXCHANGEDB_CMD_Details details; + + /** + * Data easily accessible + */ + struct PERF_TALER_EXCHANGEDB_Data exposed; +}; + + +/** + * Run a benchmark + * + * @param benchmark_name the name of the benchmark, displayed in the logs + * @param configuration_file path to the taler configuration file to use + * @param init the commands to use for the database initialisation, + * if #NULL the standard initialization is used + * @param benchmark the commands for the benchmark + * @return GNUNET_OK upon success; GNUNET_SYSERR upon failure + */ +int +PERF_TALER_EXCHANGEDB_run_benchmark (const char *benchmark_name, + const char *configuration_file, + struct PERF_TALER_EXCHANGEDB_Cmd *init, + struct PERF_TALER_EXCHANGEDB_Cmd *benchmark); + + +/** + * Runs the command array @a cmd + * using @a db_plugin to connect to the database + * + * @param db_plugin the connection to the database + * @param cmd the commands to run + */ +int +PERF_TALER_EXCHANGEDB_interpret( + struct TALER_EXCHANGEDB_Plugin *db_plugin, + struct PERF_TALER_EXCHANGEDB_Cmd cmd[]); + + +/** + * Check if the given command array is syntaxicly correct + * This will check if the label are corrects but will not check if + * they are pointing to an apropriate command. + * + * @param cmd the command array to check + * @return #GNUNET_OK is @a cmd is correct; #GNUNET_SYSERR if it is'nt + */ +int +PERF_TALER_EXCHANGEDB_check (const struct PERF_TALER_EXCHANGEDB_Cmd *cmd); + +#endif diff --git a/src/exchangedb/perf_taler_exchangedb_values.h b/src/exchangedb/perf_taler_exchangedb_values.h new file mode 100644 index 000000000..c3b50fea2 --- /dev/null +++ b/src/exchangedb/perf_taler_exchangedb_values.h @@ -0,0 +1,25 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> + */ +/** + * @file exchangedb/perf_taler_exchangedb_values.h + * @brief Values for tweaking the performance analysis + * @author Nicolas Fournier + */ +#ifndef __PERF_TALER_EXCHANGEDB__VALUES_H__ +#define __PERF_TALER_EXCHANGEDB__VALUES_H__ + + +#endif diff --git a/src/exchangedb/plugin_exchangedb_common.c b/src/exchangedb/plugin_exchangedb_common.c new file mode 100644 index 000000000..c8e689cfd --- /dev/null +++ b/src/exchangedb/plugin_exchangedb_common.c @@ -0,0 +1,162 @@ +/* + This file is part of TALER + Copyright (C) 2015 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file exchangedb/plugin_exchangedb_common.c + * @brief Functions shared across plugins, this file is meant to be + * included in each plugin. + * @author Christian Grothoff + */ + +/** + * Free memory associated with the given reserve history. + * + * @param cls the @e cls of this struct with the plugin-specific state (unused) + * @param rh history to free. + */ +static void +common_free_reserve_history (void *cls, + struct TALER_EXCHANGEDB_ReserveHistory *rh) +{ + struct TALER_EXCHANGEDB_BankTransfer *bt; + struct TALER_EXCHANGEDB_CollectableBlindcoin *cbc; + struct TALER_EXCHANGEDB_ReserveHistory *backref; + + while (NULL != rh) + { + switch(rh->type) + { + case TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE: + bt = rh->details.bank; + if (NULL != bt->wire) + json_decref (bt->wire); + GNUNET_free (bt); + break; + case TALER_EXCHANGEDB_RO_WITHDRAW_COIN: + cbc = rh->details.withdraw; + GNUNET_CRYPTO_rsa_signature_free (cbc->sig.rsa_signature); + GNUNET_CRYPTO_rsa_public_key_free (cbc->denom_pub.rsa_public_key); + GNUNET_free (cbc); + break; + } + backref = rh; + rh = rh->next; + GNUNET_free (backref); + } +} + + +/** + * Free memory of the link data list. + * + * @param cls the @e cls of this struct with the plugin-specific state (unused) + * @param ldl link data list to release + */ +static void +common_free_link_data_list (void *cls, + struct TALER_EXCHANGEDB_LinkDataList *ldl) +{ + struct TALER_EXCHANGEDB_LinkDataList *next; + + while (NULL != ldl) + { + next = ldl->next; + GNUNET_free (ldl->link_data_enc); + GNUNET_free (ldl); + ldl = next; + } +} + + +/** + * Free linked list of transactions. + * + * @param cls the @e cls of this struct with the plugin-specific state (unused) + * @param list list to free + */ +static void +common_free_coin_transaction_list (void *cls, + struct TALER_EXCHANGEDB_TransactionList *list) +{ + struct TALER_EXCHANGEDB_TransactionList *next; + + while (NULL != list) + { + next = list->next; + + switch (list->type) + { + case TALER_EXCHANGEDB_TT_DEPOSIT: + json_decref (list->details.deposit->wire); + GNUNET_CRYPTO_rsa_public_key_free (list->details.deposit->coin.denom_pub.rsa_public_key); + GNUNET_CRYPTO_rsa_signature_free (list->details.deposit->coin.denom_sig.rsa_signature); + GNUNET_free (list->details.deposit); + break; + case TALER_EXCHANGEDB_TT_REFRESH_MELT: + GNUNET_free (list->details.melt); + break; + } + GNUNET_free (list); + list = next; + } +} + + +/** + * Free melt commitment data. + * + * @param cls the @e cls of this struct with the plugin-specific state (unused) + * @param mc data structure to free + */ +static void +common_free_melt_commitment (void *cls, + struct TALER_EXCHANGEDB_MeltCommitment *mc) +{ + unsigned int i; + unsigned int k; + + if (NULL != mc->melts) + { + for (i=0;i<mc->num_oldcoins;i++) + { + GNUNET_CRYPTO_rsa_signature_free (mc->melts[i].coin.denom_sig.rsa_signature); + GNUNET_CRYPTO_rsa_public_key_free (mc->melts[i].coin.denom_pub.rsa_public_key); + } + GNUNET_free (mc->melts); + } + if (NULL != mc->denom_pubs) + { + for (i=0;i<mc->num_newcoins;i++) + if (NULL != mc->denom_pubs[i].rsa_public_key) + GNUNET_CRYPTO_rsa_public_key_free (mc->denom_pubs[i].rsa_public_key); + GNUNET_free (mc->denom_pubs); + } + for (k=0;k<TALER_CNC_KAPPA;k++) + { + if (NULL != mc->commit_coins[k]) + { + for (i=0;i<mc->num_newcoins;i++) + { + GNUNET_free (mc->commit_coins[k][i].refresh_link); + GNUNET_free (mc->commit_coins[k][i].coin_ev); + } + GNUNET_free (mc->commit_coins[k]); + } + GNUNET_free_non_null (mc->commit_links[k]); + } + GNUNET_free (mc); +} + +/* end of plugin_exchangedb_common.c */ diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c new file mode 100644 index 000000000..0395c208e --- /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 000000000..0822bab44 --- /dev/null +++ b/src/exchangedb/test-exchange-db-postgres.conf @@ -0,0 +1,8 @@ +[exchange] +#The DB plugin to use +DB = postgres + +[exchangedb-postgres] + +#The connection string the plugin has to use for connecting to the database +DB_CONN_STR = postgres:///talercheck diff --git a/src/exchangedb/test_exchangedb.c b/src/exchangedb/test_exchangedb.c new file mode 100644 index 000000000..df1adf561 --- /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 000000000..09c65b2b2 --- /dev/null +++ b/src/exchangedb/test_exchangedb_deposits.c @@ -0,0 +1,152 @@ +/* + This file is part of TALER + Copyright (C) 2014 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange/test_exchange_deposits.c + * @brief testcase for exchange deposits + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + */ +#include "platform.h" +#include <libpq-fe.h> +#include <gnunet/gnunet_util_lib.h> +#include "taler_pq_lib.h" +#include "taler_exchangedb_lib.h" +#include "taler_exchangedb_plugin.h" + +#define EXCHANGE_CURRENCY "EUR" + +#define DB_URI "postgres:///taler" + +#define break_db_err(result) do { \ + GNUNET_break(0); \ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Database failure: %s\n", PQresultErrorMessage (result)); \ + } while (0) + +/** + * Shorthand for exit jumps. + */ +#define EXITIF(cond) \ + do { \ + if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ + } while (0) + + +/** + * Should we not interact with a temporary table? + */ +static int persistent; + +/** + * Testcase result + */ +static int result; + +/** + * The plugin. + */ +static struct TALER_EXCHANGEDB_Plugin *plugin; + +/** + * Main function that will be run by the scheduler. + * + * @param cls closure + * @param args remaining command-line arguments + * @param cfgfile name of the configuration file used (for saving, can be NULL!) + * @param cfg configuration + */ +static void +run (void *cls, + char *const *args, + const char *cfgfile, + const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + static const char wire[] = "{" + "\"type\":\"SEPA\"," + "\"IBAN\":\"DE67830654080004822650\"," + "\"NAME\":\"GNUNET E.V\"," + "\"BIC\":\"GENODEF1SRL\"" + "}"; + struct TALER_EXCHANGEDB_Deposit *deposit; + uint64_t transaction_id; + struct TALER_EXCHANGEDB_Session *session; + + deposit = NULL; + EXITIF (NULL == (plugin = TALER_EXCHANGEDB_plugin_load (cfg))); + EXITIF (GNUNET_OK != + plugin->create_tables (plugin->cls, + ! persistent)); + session = plugin->get_session (plugin->cls, + ! persistent); + EXITIF (NULL == session); + deposit = GNUNET_malloc (sizeof (struct TALER_EXCHANGEDB_Deposit) + sizeof (wire)); + /* Makeup a random coin public key */ + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, + deposit, + sizeof (struct TALER_EXCHANGEDB_Deposit)); + /* Makeup a random 64bit transaction ID */ + transaction_id = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK, + UINT64_MAX); + deposit->transaction_id = GNUNET_htonll (transaction_id); + /* Random amount */ + deposit->amount_with_fee.value = + htonl (GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, UINT32_MAX)); + deposit->amount_with_fee.fraction = + htonl (GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, UINT32_MAX)); + GNUNET_assert (strlen (EXCHANGE_CURRENCY) < sizeof (deposit->amount_with_fee.currency)); + strcpy (deposit->amount_with_fee.currency, EXCHANGE_CURRENCY); + /* Copy wireformat */ + deposit->wire = json_loads (wire, 0, NULL); + EXITIF (GNUNET_OK != + plugin->insert_deposit (plugin->cls, + session, + deposit)); + EXITIF (GNUNET_OK != + plugin->have_deposit (plugin->cls, + session, + deposit)); + result = GNUNET_OK; + + EXITIF_exit: + GNUNET_free_non_null (deposit); + if (NULL != plugin) + { + TALER_EXCHANGEDB_plugin_unload (plugin); + plugin = NULL; + } +} + + +int +main (int argc, + char *const argv[]) +{ + static const struct GNUNET_GETOPT_CommandLineOption options[] = { + {'T', "persist", NULL, + gettext_noop ("Use a persistent database table instead of a temporary one"), + GNUNET_NO, &GNUNET_GETOPT_set_one, &persistent}, + GNUNET_GETOPT_OPTION_END + }; + + + persistent = GNUNET_NO; + result = GNUNET_SYSERR; + if (GNUNET_OK != + GNUNET_PROGRAM_run (argc, argv, + "test-exchange-deposits", + "testcase for exchange deposits", + options, &run, NULL)) + return 3; + return (GNUNET_OK == result) ? 0 : 1; +} diff --git a/src/exchangedb/test_exchangedb_keyio.c b/src/exchangedb/test_exchangedb_keyio.c new file mode 100644 index 000000000..2485da8ae --- /dev/null +++ b/src/exchangedb/test_exchangedb_keyio.c @@ -0,0 +1,85 @@ +/* + This file is part of TALER + Copyright (C) 2014 GNUnet e. V. (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange/test_exchange_common.c + * @brief test cases for some functions in exchange/exchange_common.c + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + */ +#include "platform.h" +#include "gnunet/gnunet_util_lib.h" +#include "taler_signatures.h" +#include "taler_exchangedb_lib.h" + +#define RSA_KEY_SIZE 1024 + + +#define EXITIF(cond) \ + do { \ + if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ + } while (0) + + +int +main (int argc, + const char *const argv[]) +{ + struct TALER_EXCHANGEDB_DenominationKeyIssueInformation dki; + char *enc; + size_t enc_size; + struct TALER_EXCHANGEDB_DenominationKeyIssueInformation dki_read; + char *enc_read; + size_t enc_read_size; + char *tmpfile; + int ret; + + ret = 1; + enc = NULL; + enc_read = NULL; + tmpfile = NULL; + dki.denom_priv.rsa_private_key = NULL; + dki_read.denom_priv.rsa_private_key = NULL; + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, + &dki.issue.signature, + sizeof (struct TALER_MasterSignatureP)); + dki.denom_priv.rsa_private_key + = GNUNET_CRYPTO_rsa_private_key_create (RSA_KEY_SIZE); + enc_size = GNUNET_CRYPTO_rsa_private_key_encode (dki.denom_priv.rsa_private_key, + &enc); + EXITIF (NULL == (tmpfile = GNUNET_DISK_mktemp ("test_exchange_common"))); + EXITIF (GNUNET_OK != TALER_EXCHANGEDB_denomination_key_write (tmpfile, &dki)); + EXITIF (GNUNET_OK != TALER_EXCHANGEDB_denomination_key_read (tmpfile, &dki_read)); + enc_read_size = GNUNET_CRYPTO_rsa_private_key_encode (dki_read.denom_priv.rsa_private_key, + &enc_read); + EXITIF (enc_size != enc_read_size); + EXITIF (0 != memcmp (enc, + enc_read, + enc_size)); + ret = 0; + + EXITIF_exit: + GNUNET_free_non_null (enc); + if (NULL != tmpfile) + { + (void) unlink (tmpfile); + GNUNET_free (tmpfile); + } + GNUNET_free_non_null (enc_read); + if (NULL != dki.denom_priv.rsa_private_key) + GNUNET_CRYPTO_rsa_private_key_free (dki.denom_priv.rsa_private_key); + if (NULL != dki_read.denom_priv.rsa_private_key) + GNUNET_CRYPTO_rsa_private_key_free (dki_read.denom_priv.rsa_private_key); + return ret; +} diff --git a/src/exchangedb/test_perf_taler_exchangedb.c b/src/exchangedb/test_perf_taler_exchangedb.c new file mode 100644 index 000000000..a4ec9591d --- /dev/null +++ b/src/exchangedb/test_perf_taler_exchangedb.c @@ -0,0 +1,182 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> + */ +/** + * @file exchangedb/test_perf_taler_exchangedb.c + * @brief Exchange database performance analysis + * @author Nicolas Fournier + */ +#include "platform.h" +#include "perf_taler_exchangedb_interpreter.h" +#include "perf_taler_exchangedb_init.h" + + +#define NB_DENOMINATION_INIT 2 +#define NB_DENOMINATION_SAVE 2 + +#define NB_RESERVE_INIT 4 +#define NB_RESERVE_SAVE 1 + +#define NB_DEPOSIT_INIT 1 +#define NB_DEPOSIT_SAVE 1 + +#define NB_WITHDRAW_INIT 1 +#define NB_WITHDRAW_SAVE 1 + +/** + * Allocate, copies and free all the data used in the interpreter + * Used to check for memory leaks + */ +static void +test_allocate () +{ + struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki, *dki_copy; + struct PERF_TALER_EXCHANGEDB_Reserve *reserve, *reserve_copy; + struct PERF_TALER_EXCHANGEDB_Coin *coin, *coin_copy; + struct TALER_EXCHANGEDB_Deposit *deposit, *deposit_copy; + + dki = PERF_TALER_EXCHANGEDB_denomination_init (); + reserve = PERF_TALER_EXCHANGEDB_reserve_init (); + coin = PERF_TALER_EXCHANGEDB_coin_init (dki, + reserve); + deposit = PERF_TALER_EXCHANGEDB_deposit_init (coin); + + dki_copy = PERF_TALER_EXCHANGEDB_denomination_copy (dki); + reserve_copy = PERF_TALER_EXCHANGEDB_reserve_copy (reserve); + coin_copy = PERF_TALER_EXCHANGEDB_coin_copy (coin); + deposit_copy = PERF_TALER_EXCHANGEDB_deposit_copy (deposit); + + PERF_TALER_EXCHANGEDB_denomination_free (dki); + PERF_TALER_EXCHANGEDB_denomination_free (dki_copy); + PERF_TALER_EXCHANGEDB_reserve_free (reserve); + PERF_TALER_EXCHANGEDB_reserve_free (reserve_copy); + PERF_TALER_EXCHANGEDB_coin_free (coin); + PERF_TALER_EXCHANGEDB_coin_free (coin_copy); + PERF_TALER_EXCHANGEDB_deposit_free (deposit); + PERF_TALER_EXCHANGEDB_deposit_free (deposit_copy); +} + +/** + * Runs the performances tests for the exchange database + * and logs the results using Gauger + */ +int +main (int argc, char ** argv) +{ + int ret = 0; + struct PERF_TALER_EXCHANGEDB_Cmd init[] = + { + PERF_TALER_EXCHANGEDB_INIT_CMD_END ("init") + }; + struct PERF_TALER_EXCHANGEDB_Cmd benchmark[] = + { + // Denomination used to create coins + PERF_TALER_EXCHANGEDB_INIT_CMD_DEBUG ("00 - Start of interpreter"), + + PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("01 - denomination loop", + NB_DENOMINATION_INIT), + PERF_TALER_EXCHANGEDB_INIT_CMD_START_TRANSACTION ("01 - start transaction"), + PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_DENOMINATION ("01 - denomination"), + PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_DENOMINATION ("01 - insert", + "01 - denomination"), + PERF_TALER_EXCHANGEDB_INIT_CMD_COMMIT_TRANSACTION ("01 - commit transaction"), + PERF_TALER_EXCHANGEDB_INIT_CMD_SAVE_ARRAY ("01 - save denomination", + "01 - denomination loop", + "01 - denomination", + NB_DENOMINATION_SAVE), + PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("01 - denomination loop end", + "01 - denomination loop"), + PERF_TALER_EXCHANGEDB_INIT_CMD_DEBUG ("01 - init denomination complete"), + // End of initialization + // Reserve initialization + PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("02 - init reserve loop", + NB_RESERVE_INIT), + + PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_RESERVE ("02 - reserve"), + PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_RESERVE ("02 - insert", + "02 - reserve"), + PERF_TALER_EXCHANGEDB_INIT_CMD_SAVE_ARRAY ("02 - save reserve", + "02 - init reserve loop", + "02 - reserve", + NB_RESERVE_SAVE), + PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("02 - init reserve end loop", + "02 - init reserve loop"), + PERF_TALER_EXCHANGEDB_INIT_CMD_DEBUG ("02 - reserve init complete"), + // End reserve init + // Withdrawal initialization + PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("03 - init withdraw loop", + NB_WITHDRAW_INIT), + PERF_TALER_EXCHANGEDB_INIT_CMD_START_TRANSACTION ("03 - start transaction"), + PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY ("03 - denomination load", + "03 - init withdraw loop", + "01 - save denomination"), + PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY ("03 - reserve load", + "03 - init withdraw loop", + "02 - save reserve"), + PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_WITHDRAW ("03 - withdraw", + "03 - denomination load", + "03 - reserve load"), + PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_WITHDRAW ("03 - insert withdraw", + "03 - withdraw"), + PERF_TALER_EXCHANGEDB_INIT_CMD_COMMIT_TRANSACTION ("03 - commit transaction"), + PERF_TALER_EXCHANGEDB_INIT_CMD_SAVE_ARRAY ("03 - coin array", + "03 - init withdraw loop", + "03 - withdraw", + NB_WITHDRAW_SAVE), + PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("03 - withdraw init end loop", + "03 - init withdraw loop"), + PERF_TALER_EXCHANGEDB_INIT_CMD_DEBUG ("03 - withdraw init complete"), + //End of withdrawal initialization + //Deposit initialization + PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("04 - time start"), + PERF_TALER_EXCHANGEDB_INIT_CMD_LOOP ("04 - deposit init loop", + NB_DEPOSIT_INIT), + PERF_TALER_EXCHANGEDB_INIT_CMD_START_TRANSACTION ("04 - start transaction"), + PERF_TALER_EXCHANGEDB_INIT_CMD_LOAD_ARRAY ("04 - coin load", + "04 - deposit init loop", + "03 - coin array"), + PERF_TALER_EXCHANGEDB_INIT_CMD_CREATE_DEPOSIT ("04 - deposit", + "04 - coin load"), + PERF_TALER_EXCHANGEDB_INIT_CMD_INSERT_DEPOSIT ("04 - insert deposit", + "04 - deposit"), + PERF_TALER_EXCHANGEDB_INIT_CMD_COMMIT_TRANSACTION ("04 - commit transaction"), + PERF_TALER_EXCHANGEDB_INIT_CMD_SAVE_ARRAY ("04 - deposit array", + "04 - deposit init loop", + "04 - deposit", + NB_DEPOSIT_SAVE), + PERF_TALER_EXCHANGEDB_INIT_CMD_END_LOOP ("04 - deposit init loop end", + "04 - deposit init loop"), + PERF_TALER_EXCHANGEDB_INIT_CMD_GET_TIME ("04 - time stop"), + PERF_TALER_EXCHANGEDB_INIT_CMD_GAUGER ("04 - gauger", + "04 - time start", + "04 - time stop", + "TEST", + "time to insert a deposit", + "deposit/sec", + NB_DEPOSIT_SAVE), + PERF_TALER_EXCHANGEDB_INIT_CMD_DEBUG ("04 - deposit init complete"), + // End of deposit initialization + PERF_TALER_EXCHANGEDB_INIT_CMD_END ("end"), + }; + + test_allocate (); + ret = PERF_TALER_EXCHANGEDB_run_benchmark ("test-perf-taler-exchangedb", + "./test-exchange-db-postgres.conf", + init, + benchmark); + if (GNUNET_SYSERR == ret) + return 1; + return 0; +} |