diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 2 | ||||
-rw-r--r-- | src/auditor/Makefile.am | 39 | ||||
-rw-r--r-- | src/auditor/auditor.conf (renamed from src/exchange-tools/auditor.conf) | 6 | ||||
-rw-r--r-- | src/auditor/taler-auditor-sign.c (renamed from src/exchange-tools/taler-auditor-sign.c) | 0 | ||||
-rw-r--r-- | src/auditor/taler-auditor.c | 117 | ||||
-rw-r--r-- | src/auditordb/Makefile.am | 53 | ||||
-rw-r--r-- | src/auditordb/auditordb-postgres.conf | 3 | ||||
-rw-r--r-- | src/auditordb/auditordb_plugin.c | 88 | ||||
-rw-r--r-- | src/auditordb/plugin_auditordb_postgres.c | 776 | ||||
-rw-r--r-- | src/exchange-tools/Makefile.am | 10 | ||||
-rw-r--r-- | src/exchangedb/plugin_exchangedb_postgres.c | 12 | ||||
-rw-r--r-- | src/include/Makefile.am | 2 | ||||
-rw-r--r-- | src/include/taler_auditordb_lib.h | 47 | ||||
-rw-r--r-- | src/include/taler_auditordb_plugin.h | 135 |
14 files changed, 1272 insertions, 18 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 45ff87a09..2f158a1da 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -22,7 +22,7 @@ pkgcfg_DATA = \ EXTRA_DIST = \ taler.conf -SUBDIRS = include util json $(PQ_DIR) $(BANK_LIB) wire exchangedb exchange exchange-tools +SUBDIRS = include util json $(PQ_DIR) $(BANK_LIB) wire exchangedb exchange exchange-tools auditordb auditor if HAVE_LIBCURL SUBDIRS += exchange-lib benchmark else diff --git a/src/auditor/Makefile.am b/src/auditor/Makefile.am new file mode 100644 index 000000000..724b43205 --- /dev/null +++ b/src/auditor/Makefile.am @@ -0,0 +1,39 @@ +# This Makefile.am is in the public domain +AM_CPPFLAGS = -I$(top_srcdir)/src/include + +if USE_COVERAGE + AM_CFLAGS = --coverage -O0 + XLIB = -lgcov +endif + +pkgcfgdir = $(prefix)/share/taler/config.d/ + +pkgcfg_DATA = \ + auditor.conf + +bin_PROGRAMS = \ + taler-auditor \ + taler-auditor-sign + +taler_auditor_SOURCES = \ + taler-auditor.c +taler_auditor_LDADD = \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/util/libtalerutil.la \ + $(top_builddir)/src/wire/libtalerwire.la \ + $(top_builddir)/src/exchangedb/libtalerexchangedb.la \ + $(top_builddir)/src/auditordb/libtalerauditordb.la \ + -lgnunetutil + +taler_auditor_sign_SOURCES = \ + taler-auditor-sign.c +taler_auditor_sign_LDADD = \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/util/libtalerutil.la \ + $(top_builddir)/src/exchangedb/libtalerexchangedb.la \ + -lgnunetutil $(XLIB) + + + +EXTRA_DIST = \ + auditor.conf diff --git a/src/exchange-tools/auditor.conf b/src/auditor/auditor.conf index 7eb5f8ae9..22395cc6e 100644 --- a/src/exchange-tools/auditor.conf +++ b/src/auditor/auditor.conf @@ -1,8 +1,8 @@ -# This configuration file is in the public domain +# This file is in the public domain. # -# It cointains options for the auditor. - [auditor] +# Which database backend do we use for the auditor? +DB = postgres # Where do we store the auditor's private key? AUDITOR_PRIV_FILE = ${TALER_DATA_HOME}/auditor/offline-keys/auditor.priv diff --git a/src/exchange-tools/taler-auditor-sign.c b/src/auditor/taler-auditor-sign.c index 6e4fda754..6e4fda754 100644 --- a/src/exchange-tools/taler-auditor-sign.c +++ b/src/auditor/taler-auditor-sign.c diff --git a/src/auditor/taler-auditor.c b/src/auditor/taler-auditor.c new file mode 100644 index 000000000..1e9d1c2ef --- /dev/null +++ b/src/auditor/taler-auditor.c @@ -0,0 +1,117 @@ +/* + This file is part of TALER + Copyright (C) 2016 Inria + + 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, see <http://www.gnu.org/licenses/> +*/ +/** + * @file auditor/taler-auditor.c + * @brief audits an exchange database. + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include "taler_exchangedb_plugin.h" + + +/** + * Return value from main(). + */ +static int global_ret; + +/** + * Handle to access the exchange's database. + */ +static struct TALER_EXCHANGEDB_Plugin *edb; + +/** + * Handle to access the auditor's database. + */ +static struct TALER_AUDITORDB_Plugin *adb; + + +/** + * Main function that will be run. + * + * @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) +{ + if (NULL == + (edb = TALER_EXCHANGEDB_plugin_load (cfg))) + { + fprintf (stderr, + "Failed to initialize exchange database plugin.\n"); + global_ret = 1; + return; + } + if (NULL == + (adb = TALER_AUDITORDB_plugin_load (cfg))) + { + fprintf (stderr, + "Failed to initialize auditor database plugin.\n"); + global_ret = 1; + TALER_EXCHANGEDB_plugin_unload (edb); + return; + } + + + TALER_AUDITORDB_plugin_unload (adb); + TALER_EXCHANGEDB_plugin_unload (edb); +} + + +/** + * The main function of the database initialization tool. + * Used to initialize the Taler Exchange's database. + * + * @param argc number of arguments from the command line + * @param argv command line arguments + * @return 0 ok, 1 on error + */ +int +main (int argc, + char *const *argv) +{ + const struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_OPTION_END + }; + + /* force linker to link against libtalerutil; if we do + not do this, the linker may "optimize" libtalerutil + away and skip #TALER_OS_init(), which we do need */ + (void) TALER_project_data_default (); + GNUNET_assert (GNUNET_OK == + GNUNET_log_setup ("taler-auditor", + "INFO", + NULL)); + if (GNUNET_OK != + GNUNET_PROGRAM_run (argc, + argv, + "taler-auditor", + "Audit Taler exchange database", + options, + &run, + NULL)) + return 1; + return global_ret; +} + + +/* end of taler-auditor.c */ diff --git a/src/auditordb/Makefile.am b/src/auditordb/Makefile.am new file mode 100644 index 000000000..e8ec40272 --- /dev/null +++ b/src/auditordb/Makefile.am @@ -0,0 +1,53 @@ +# This Makefile.am is in the public domain +AM_CPPFLAGS = -I$(top_srcdir)/src/include -I$(top_srcdir)/src/pq/ $(POSTGRESQL_CPPFLAGS) + +if USE_COVERAGE + AM_CFLAGS = --coverage -O0 + XLIB = -lgcov +endif + +pkgcfgdir = $(prefix)/share/taler/config.d/ + +pkgcfg_DATA = \ + auditordb-postgres.conf + +EXTRA_DIST = \ + auditordb-postgres.conf + +plugindir = $(libdir)/taler + +if HAVE_POSTGRESQL +plugin_LTLIBRARIES = \ + libtaler_plugin_auditordb_postgres.la +endif + +libtaler_plugin_auditordb_postgres_la_SOURCES = \ + plugin_auditordb_postgres.c +libtaler_plugin_auditordb_postgres_la_LIBADD = \ + $(LTLIBINTL) +libtaler_plugin_auditordb_postgres_la_LDFLAGS = \ + $(TALER_PLUGIN_LDFLAGS) \ + $(top_builddir)/src/pq/libtalerpq.la \ + $(top_builddir)/src/util/libtalerutil.la \ + -lpq \ + -lgnunetpq \ + -lgnunetutil $(XLIB) + +lib_LTLIBRARIES = \ + libtalerauditordb.la + +libtalerauditordb_la_SOURCES = \ + auditordb_plugin.c + +libtalerauditordb_la_LIBADD = \ + $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetutil $(XLIB) + +libtalerauditordb_la_LDFLAGS = \ + $(POSTGRESQL_LDFLAGS) \ + -version-info 0:0:0 \ + -no-undefined + + +EXTRA_test_auditordb_postgres_DEPENDENCIES = \ + libtaler_plugin_auditordb_postgres.la diff --git a/src/auditordb/auditordb-postgres.conf b/src/auditordb/auditordb-postgres.conf new file mode 100644 index 000000000..4fe2064f1 --- /dev/null +++ b/src/auditordb/auditordb-postgres.conf @@ -0,0 +1,3 @@ +[auditordb-postgres] +# Argument for Postgres for how to connect to the database. +DB_CONN_STR = "postgres:///taler" diff --git a/src/auditordb/auditordb_plugin.c b/src/auditordb/auditordb_plugin.c new file mode 100644 index 000000000..6aa0274f8 --- /dev/null +++ b/src/auditordb/auditordb_plugin.c @@ -0,0 +1,88 @@ +/* + This file is part of TALER + Copyright (C) 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, see <http://www.gnu.org/licenses/> +*/ +/** + * @file auditordb/auditordb_plugin.c + * @brief Logic to load database plugin + * @author Christian Grothoff + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + */ +#include "platform.h" +#include "taler_auditordb_plugin.h" +#include <ltdl.h> + + +/** + * Initialize the plugin. + * + * @param cfg configuration to use + * @return #GNUNET_OK on success + */ +struct TALER_AUDITORDB_Plugin * +TALER_AUDITORDB_plugin_load (const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + char *plugin_name; + char *lib_name; + struct GNUNET_CONFIGURATION_Handle *cfg_dup; + struct TALER_AUDITORDB_Plugin *plugin; + + if (GNUNET_SYSERR == + GNUNET_CONFIGURATION_get_value_string (cfg, + "auditor", + "db", + &plugin_name)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "auditor", + "db"); + return NULL; + } + (void) GNUNET_asprintf (&lib_name, + "libtaler_plugin_auditordb_%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_AUDITORDB_plugin_unload (struct TALER_AUDITORDB_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 auditordb_plugin.c */ diff --git a/src/auditordb/plugin_auditordb_postgres.c b/src/auditordb/plugin_auditordb_postgres.c new file mode 100644 index 000000000..290af9dbc --- /dev/null +++ b/src/auditordb/plugin_auditordb_postgres.c @@ -0,0 +1,776 @@ +/* + This file is part of TALER + Copyright (C) 2014-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, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file plugin_auditordb_postgres.c + * @brief Low-level (statement-level) Postgres database access for the auditor + * @author Florian Dold + * @author Christian Grothoff + * @author Sree Harsha Totakura + */ +#include "platform.h" +#include "taler_pq_lib.h" +#include "taler_auditordb_plugin.h" +#include <pthread.h> +#include <libpq-fe.h> + +/** + * 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 (%s)\n", __FILE__, __LINE__, PQresultErrorMessage (result), PQresStatus (PQresultStatus (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 (%s)\n", PQresultErrorMessage (result), PQresStatus (PQresultStatus (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_AUDITORDB_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; +}; + + +/** + * 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); +} + + +/** + * Establish connection to the Postgres database + * and initialize callbacks for logging. + * + * @param pc configuration to use + * @return NULL on error + */ +static PGconn * +connect_to_postgres (struct PostgresClosure *pc) +{ + PGconn *conn; + + conn = PQconnectdb (pc->connection_cfg_str); + if (CONNECTION_OK != + PQstatus (conn)) + { + TALER_LOG_ERROR ("Database connection failed: %s\n", + PQerrorMessage (conn)); + GNUNET_break (0); + return NULL; + } + PQsetNoticeReceiver (conn, + &pq_notice_receiver_cb, + NULL); + PQsetNoticeProcessor (conn, + &pq_notice_processor_cb, + NULL); + return conn; +} + + +/** + * Drop all Taler tables. This should only be used by testcases. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ +static int +postgres_drop_tables (void *cls) +{ + struct PostgresClosure *pc = cls; + PGconn *conn; + + conn = connect_to_postgres (pc); + if (NULL == conn) + return GNUNET_SYSERR; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Dropping ALL tables\n"); + SQLEXEC_ (conn, + "DROP TABLE IF EXISTS test;"); + PQfinish (conn); + return GNUNET_OK; + SQLEXEC_fail: + PQfinish (conn); + return GNUNET_SYSERR; +} + + +/** + * Create the necessary tables if they are not present + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ +static int +postgres_create_tables (void *cls) +{ + struct PostgresClosure *pc = cls; + PGconn *conn; + + conn = connect_to_postgres (pc); + if (NULL == conn) + return GNUNET_SYSERR; +#define SQLEXEC(sql) SQLEXEC_(conn, sql); +#define SQLEXEC_INDEX(sql) SQLEXEC_IGNORE_ERROR_(conn, sql); + + /* Table with all of the denomination keys that the auditor + is aware of. */ + SQLEXEC ("CREATE TABLE IF NOT EXISTS auditor_denominations" + "(denom_pub BYTEA PRIMARY KEY" + ",master_pub BYTEA NOT NULL CHECK (LENGTH(master_pub)=32)" + ",valid_from INT8 NOT NULL" + ",expire_withdraw INT8 NOT NULL" + ",expire_deposit 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" + ",fee_refund_val INT8 NOT NULL" + ",fee_refund_frac INT4 NOT NULL" + ",fee_refund_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL" + ")"); + + /* Table with all of the customer reserves and their respective + balances that the auditor is aware of. + "last_reserve_out_serial_id" marks the last withdrawal from + "reserves_out" about this reserve that the auditor is aware of, + and "last_reserve_in_serial_id" is the last "reserve_in" + operation about this reserve that the auditor is aware of. */ + SQLEXEC ("CREATE TABLE IF NOT EXISTS auditor_reserves" + "(reserve_pub BYTEA PRIMARY KEY CHECK(LENGTH(reserve_pub)=32)" + ",master_pub BYTEA NOT NULL CHECK (LENGTH(master_pub)=32)" + ",reserve_balance_val INT8 NOT NULL" + ",reserve_balance_frac INT4 NOT NULL" + ",reserve_balance_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL" + ",withdraw_fee_balance_val INT8 NOT NULL" + ",withdraw_fee_balance_frac INT4 NOT NULL" + ",withdraw_fee_balance_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL" + ",expiration_date INT8 NOT NULL" + ",last_reserve_in_serial_id INT8 NOT NULL" + ",last_reserve_out_serial_id INT8 NOT NULL" + ")"); + + /* Table with the sum of the balances of all customer reserves + (by exchange's master public key) */ + SQLEXEC ("CREATE TABLE IF NOT EXISTS auditor_reserve_balance" + "(master_pub BYTEA NOT NULL CHECK (LENGTH(master_pub)=32)" + ",reserve_balance_val INT8 NOT NULL" + ",reserve_balance_frac INT4 NOT NULL" + ",reserve_balance_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL" + ",withdraw_fee_balance_val INT8 NOT NULL" + ",withdraw_fee_balance_frac INT4 NOT NULL" + ",withdraw_fee_balance_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL" + ")"); + + /* Table with all of the outstanding denomination coins that the + exchange is aware of. "last_deposit_serial_id" marks the + deposit_serial_id from "deposits" about this denomination key + that the auditor is aware of; "last_melt_serial_id" marks the + last melt from "refresh_sessions" that the auditor is aware + of; "refund_serial_id" tells us the last entry in "refunds" + for this denom_pub that the auditor is aware of. */ + SQLEXEC ("CREATE TABLE IF NOT EXISTS denomination_pending" + "(denom_pub BYTEA NOT NULL REFERENCES denominations (denom_pub) ON DELETE CASCADE" + ",denom_balance_val INT8 NOT NULL" + ",denom_balance_frac INT4 NOT NULL" + ",denom_balance_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL" + ",deposit_fee_balance_val INT8 NOT NULL" + ",deposit_fee_balance_frac INT4 NOT NULL" + ",deposit_fee_balance_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL" + ",melt_fee_balance_val INT8 NOT NULL" + ",melt_fee_balance_frac INT4 NOT NULL" + ",melt_fee_balance_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL" + ",refund_fee_balance_val INT8 NOT NULL" + ",refund_fee_balance_frac INT4 NOT NULL" + ",refund_fee_balance_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL" + ",last_deposit_serial_id INT8 NOT NULL" + ",last_melt_serial_id INT8 NOT NULL" + ",last_refund INT8 NOT NULL" + ")"); + + /* Table with the sum of the outstanding coins from + "denomination_pending" (denom_pubs must belong + to the respective's exchange's master public key); + it represents the total_liabilities of the exchange + at this point (modulo unexpected historic_loss-style + events where denomination keys are compromised) */ + SQLEXEC ("CREATE TABLE IF NOT EXISTS total_liabilities" + "(master_pub BYTEA NOT NULL CHECK (LENGTH(master_pub)=32)" + ",denom_balance_val INT8 NOT NULL" + ",denom_balance_frac INT4 NOT NULL" + ",denom_balance_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL" + ",deposit_fee_balance_val INT8 NOT NULL" + ",deposit_fee_balance_frac INT4 NOT NULL" + ",deposit_fee_balance_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL" + ",melt_fee_balance_val INT8 NOT NULL" + ",melt_fee_balance_frac INT4 NOT NULL" + ",melt_fee_balance_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL" + ")"); + + /* Table with the sum of the generated coins all + denomination keys. This represents the maximum + additional total financial risk of the exchange + in case that all denomination keys are compromised + (and all of the deposits so far were done by + the successful attacker). So this is strictly an + upper bound on the risk exposure of the exchange. + (Note that this risk is in addition to the known + total_liabilities) */ + SQLEXEC ("CREATE TABLE IF NOT EXISTS total_risk" + "(master_pub BYTEA NOT NULL CHECK (LENGTH(master_pub)=32)" + ",risk_val INT8 NOT NULL" + ",risk_frac INT4 NOT NULL" + ",risk_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL" + ")"); + + + /* Table with historic profits; basically, when a denom_pub + is expired and everything associated with it is garbage + collected, the final profits end up in here; note that + the "denom_pub" here is not a foreign key, we just keep + it as a reference point. "revenue_balance" is the sum + of all of the profits we made on the coin except for + withdraw fees (which are in historic_reserve_revenue); + the deposit and melt fees are given individually; the + delta to the revenue_balance is from coins that were withdrawn + but never deposited prior to expiration. */ + SQLEXEC ("CREATE TABLE IF NOT EXISTS historic_denomination_revenue" + "(master_pub BYTEA NOT NULL CHECK (LENGTH(master_pub)=32)" + ",denom_pub BYTEA NOT NULL" + ",revenue_timestamp INT8 NOT NULL" + ",revenue_balance_val INT8 NOT NULL" + ",revenue_balance_frac INT4 NOT NULL" + ",revenue_balance_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL" + ",deposit_fee_balance_val INT8 NOT NULL" + ",deposit_fee_balance_frac INT4 NOT NULL" + ",deposit_fee_balance_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL" + ",melt_fee_balance_val INT8 NOT NULL" + ",melt_fee_balance_frac INT4 NOT NULL" + ",melt_fee_balance_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL" ")"); + + /* Table with historic losses; basically, when we need to + invalidate a denom_pub because the denom_priv was + compromised, we incur a loss. These losses are totaled + up here. (NOTE: the 'bankrupcy' protocol is not yet + implemented, so right now this table is not used.) */ + SQLEXEC ("CREATE TABLE IF NOT EXISTS historic_losses" + "(master_pub BYTEA NOT NULL CHECK (LENGTH(master_pub)=32)" + ",denom_pub BYTEA NOT NULL" + ",loss_timestamp INT8 NOT NULL" + ",loss_balance_val INT8 NOT NULL" + ",loss_balance_frac INT4 NOT NULL" + ",loss_balance_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL" + ")"); + + /* Table with historic profits by reserve; basically, when a + reserve expires, we transmit the balance back to the user, but + rounding gains and withdraw fees are listed here. */ + SQLEXEC ("CREATE TABLE IF NOT EXISTS historic_reserve_revenue" + "(master_pub BYTEA NOT NULL CHECK (LENGTH(master_pub)=32)" + ",reserve_pub BYTEA NOT NULL CHECK (LENGTH(reserve_pub)=32)" + ",expiration_date INT8 NOT NULL" + ",reserve_profit_val INT8 NOT NULL" + ",reserve_profit_frac INT4 NOT NULL" + ",reserve_profit_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL" + ")"); + + /* Table with historic profits from reserves; we eventually + GC "historic_reserve_revenue", and then store the totals + in here (by time intervals). */ + SQLEXEC ("CREATE TABLE IF NOT EXISTS historic_reserve_summary" + "(master_pub BYTEA NOT NULL CHECK (LENGTH(master_pub)=32)" + ",start_date INT8 NOT NULL" + ",end_date INT8 NOT NULL" + ",reserve_profits_val INT8 NOT NULL" + ",reserve_profits_frac INT4 NOT NULL" + ",reserve_profits_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL" + ")"); + + /* Table with historic business ledger; basically, when the exchange + operator decides to use operating costs for anything but wire + transfers to merchants, it goes in here. This happens when the + operator users transaction fees for business expenses. "purpose" + is free-form but should be a human-readable wire transfer + identifier. */ + SQLEXEC ("CREATE TABLE IF NOT EXISTS historic_ledger" + "(master_pub BYTEA NOT NULL CHECK (LENGTH(master_pub)=32)" + ",purpose VARCHAR NOT NULL" + ",timestamp INT8 NOT NULL" + ",balance_val INT8 NOT NULL" + ",balance_frac INT4 NOT NULL" + ",balance_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL" + ")"); + + /* Table with the sum of the ledger, historic_revenue, + historic_losses and the auditor_reserve_balance. + This is the final amount that the exchange should have + in its bank account right now. */ + SQLEXEC ("CREATE TABLE IF NOT EXISTS predicted_result" + "(master_pub BYTEA NOT NULL CHECK (LENGTH(master_pub)=32)" + ",balance_val INT8 NOT NULL" + ",balance_frac INT4 NOT NULL" + ",balance_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL" + ")"); + + + SQLEXEC_INDEX("CREATE INDEX testx " + "ON test(test_pub)"); +#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_XXX() */ + PREPARE ("test_insert", + "INSERT INTO test " + "(test_pub" + ") VALUES " + "($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_AUDITORDB_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 + * @return the database connection, or NULL on error + */ +static struct TALER_AUDITORDB_Session * +postgres_get_session (void *cls) +{ + struct PostgresClosure *pc = cls; + PGconn *db_conn; + struct TALER_AUDITORDB_Session *session; + + if (NULL != (session = pthread_getspecific (pc->db_conn_threadlocal))) + return session; + db_conn = connect_to_postgres (pc); + if (NULL == db_conn) + return NULL; + if (GNUNET_OK != + postgres_prepare (db_conn)) + { + GNUNET_break (0); + PQfinish (db_conn); + return NULL; + } + session = GNUNET_new (struct TALER_AUDITORDB_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_AUDITORDB_Session *session) +{ + PGresult *result; + + result = PQexec (session->conn, + "START TRANSACTION ISOLATION LEVEL SERIALIZABLE"); + 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_AUDITORDB_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_AUDITORDB_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; +} + + +/** + * Function called to perform "garbage collection" on the + * database, expiring records we no longer require. + * + * @param cls closure + * @return #GNUNET_OK on success, + * #GNUNET_SYSERR on DB errors + */ +static int +postgres_gc (void *cls) +{ + struct PostgresClosure *pc = cls; + struct GNUNET_TIME_Absolute now; + struct GNUNET_PQ_QueryParam params_time[] = { + GNUNET_PQ_query_param_absolute_time (&now), + GNUNET_PQ_query_param_end + }; + PGconn *conn; + PGresult *result; + + now = GNUNET_TIME_absolute_get (); + conn = connect_to_postgres (pc); + if (NULL == conn) + return GNUNET_SYSERR; + if (GNUNET_OK != + postgres_prepare (conn)) + { + PQfinish (conn); + return GNUNET_SYSERR; + } + result = GNUNET_PQ_exec_prepared (conn, + "gc_auditor", + params_time); + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + PQfinish (conn); + return GNUNET_SYSERR; + } + PQclear (result); + PQfinish (conn); + return GNUNET_OK; +} + + +/** + * Initialize Postgres database subsystem. + * + * @param cls a configuration instance + * @return NULL on error, otherwise a `struct TALER_AUDITORDB_Plugin` + */ +void * +libtaler_plugin_auditordb_postgres_init (void *cls) +{ + struct GNUNET_CONFIGURATION_Handle *cfg = cls; + struct PostgresClosure *pg; + struct TALER_AUDITORDB_Plugin *plugin; + const char *ec; + + 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; + } + ec = getenv ("TALER_AUDITORDB_POSTGRES_CONFIG"); + if (NULL != ec) + { + pg->connection_cfg_str = GNUNET_strdup (ec); + } + else + { + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "auditordb-postgres", + "db_conn_str", + &pg->connection_cfg_str)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "auditordb-postgres", + "db_conn_str"); + GNUNET_free (pg); + return NULL; + } + } + plugin = GNUNET_new (struct TALER_AUDITORDB_Plugin); + plugin->cls = pg; + plugin->get_session = &postgres_get_session; + plugin->drop_tables = &postgres_drop_tables; + plugin->create_tables = &postgres_create_tables; + plugin->start = &postgres_start; + plugin->commit = &postgres_commit; + plugin->rollback = &postgres_rollback; + plugin->gc = &postgres_gc; + return plugin; +} + + +/** + * Shutdown Postgres database subsystem. + * + * @param cls a `struct TALER_AUDITORDB_Plugin` + * @return NULL (always) + */ +void * +libtaler_plugin_auditordb_postgres_done (void *cls) +{ + struct TALER_AUDITORDB_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_auditordb_postgres.c */ diff --git a/src/exchange-tools/Makefile.am b/src/exchange-tools/Makefile.am index a1e4f67a2..2fd4177ed 100644 --- a/src/exchange-tools/Makefile.am +++ b/src/exchange-tools/Makefile.am @@ -4,7 +4,6 @@ AM_CPPFLAGS = -I$(top_srcdir)/src/include pkgcfgdir = $(prefix)/share/taler/config.d/ pkgcfg_DATA = \ - auditor.conf \ exchange-signkeys.conf \ coins.conf @@ -14,7 +13,6 @@ if USE_COVERAGE endif bin_PROGRAMS = \ - taler-auditor-sign \ taler-exchange-keyup \ taler-exchange-keycheck \ taler-exchange-reservemod \ @@ -31,14 +29,6 @@ taler_exchange_keyup_LDADD = \ -lgnunetutil $(XLIB) taler_exchange_keyup_LDFLAGS = $(POSTGRESQL_LDFLAGS) -taler_auditor_sign_SOURCES = \ - taler-auditor-sign.c -taler_auditor_sign_LDADD = \ - $(LIBGCRYPT_LIBS) \ - $(top_builddir)/src/util/libtalerutil.la \ - $(top_builddir)/src/exchangedb/libtalerexchangedb.la \ - -lgnunetutil $(XLIB) - taler_exchange_wire_SOURCES = \ taler-exchange-wire.c diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index a90ff849d..9229b10de 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -311,7 +311,8 @@ postgres_create_tables (void *cls) into the reserve. The rows of this table correspond to each incoming transaction. */ SQLEXEC("CREATE TABLE IF NOT EXISTS reserves_in" - "(reserve_pub BYTEA NOT NULL REFERENCES reserves (reserve_pub) ON DELETE CASCADE" + "(reserve_in_serial_id BIGSERIAL" + ",reserve_pub BYTEA NOT NULL REFERENCES reserves (reserve_pub) ON DELETE CASCADE" ",credit_val INT8 NOT NULL" ",credit_frac INT4 NOT NULL" ",credit_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL" @@ -329,7 +330,8 @@ postgres_create_tables (void *cls) 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" + "(reserve_out_serial_id BIGSERIAL" + ",h_blind_ev BYTEA PRIMARY KEY" ",denom_pub BYTEA NOT NULL REFERENCES denominations (denom_pub) ON DELETE CASCADE" ",denom_sig BYTEA NOT NULL" ",reserve_pub BYTEA NOT NULL REFERENCES reserves (reserve_pub) ON DELETE CASCADE" @@ -360,7 +362,8 @@ postgres_create_tables (void *cls) * 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)" + "(melt_serial_id BIGSERIAL" + ",session_hash BYTEA PRIMARY KEY CHECK (LENGTH(session_hash)=64)" ",old_coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) ON DELETE CASCADE" ",old_coin_sig BYTEA NOT NULL CHECK(LENGTH(old_coin_sig)=64)" ",amount_with_fee_val INT8 NOT NULL" @@ -446,7 +449,8 @@ postgres_create_tables (void *cls) /* Table with information about coins that have been refunded. (Technically one of the deposit operations that a coin was involved with is refunded.)*/ SQLEXEC("CREATE TABLE IF NOT EXISTS refunds " - "(coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) ON DELETE CASCADE" + "(refund_serial_id BIGSERIAL" + ",coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) ON DELETE CASCADE" ",merchant_pub BYTEA NOT NULL CHECK(LENGTH(merchant_pub)=32)" ",merchant_sig BYTEA NOT NULL CHECK(LENGTH(merchant_sig)=64)" ",h_contract BYTEA NOT NULL CHECK(LENGTH(h_contract)=64)" diff --git a/src/include/Makefile.am b/src/include/Makefile.am index dfecf4694..7a8f6e071 100644 --- a/src/include/Makefile.am +++ b/src/include/Makefile.am @@ -13,6 +13,8 @@ else talerinclude_HEADERS = \ platform.h \ taler_amount_lib.h \ + taler_auditordb_lib.h \ + taler_auditordb_plugin.h \ taler_bank_service.h \ taler_crypto_lib.h \ taler_error_codes.h \ diff --git a/src/include/taler_auditordb_lib.h b/src/include/taler_auditordb_lib.h new file mode 100644 index 000000000..0953cf110 --- /dev/null +++ b/src/include/taler_auditordb_lib.h @@ -0,0 +1,47 @@ +/* + This file is part of TALER + Copyright (C) 2016 Inria & GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file include/taler_auditordb_lib.h + * @brief high-level interface for the auditor's database + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#ifndef TALER_AUDITORDB_LIB_H +#define TALER_AUDITORDB_LIB_H + +#include "taler_auditordb_plugin.h" + +/** + * Initialize the plugin. + * + * @param cfg configuration to use + * @return NULL on failure + */ +struct TALER_AUDITORDB_Plugin * +TALER_AUDITORDB_plugin_load (const struct GNUNET_CONFIGURATION_Handle *cfg); + + +/** + * Shutdown the plugin. + * + * @param plugin plugin to unload + */ +void +TALER_AUDITORDB_plugin_unload (struct TALER_AUDITORDB_Plugin *plugin); + + +#endif diff --git a/src/include/taler_auditordb_plugin.h b/src/include/taler_auditordb_plugin.h new file mode 100644 index 000000000..ec489c62f --- /dev/null +++ b/src/include/taler_auditordb_plugin.h @@ -0,0 +1,135 @@ +/* + This file is part of TALER + Copyright (C) 2014-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, see <http://www.gnu.org/licenses/> +*/ +/** + * @file include/taler_auditordb_plugin.h + * @brief Low-level (statement-level) database access for the auditor + * @author Florian Dold + * @author Christian Grothoff + */ +#ifndef TALER_AUDITORDB_PLUGIN_H +#define TALER_AUDITORDB_PLUGIN_H + +#include <jansson.h> +#include <gnunet/gnunet_util_lib.h> +#include "taler_auditordb_lib.h" + + +/** + * Handle for one session with the database. + */ +struct TALER_AUDITORDB_Session; + + +/** + * @brief The plugin API, returned from the plugin's "init" function. + * The argument given to "init" is simply a configuration handle. + */ +struct TALER_AUDITORDB_Plugin +{ + + /** + * Closure for all callbacks. + */ + void *cls; + + /** + * Name of the library which generated this plugin. Set by the + * plugin loader. + */ + char *library_name; + + /** + * Get the thread-local database-handle. + * Connect to the db if the connection does not exist yet. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param the database connection, or NULL on error + */ + struct TALER_AUDITORDB_Session * + (*get_session) (void *cls); + + + /** + * Drop the Taler tables. This should only be used in testcases. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ + int + (*drop_tables) (void *cls); + + + /** + * Create the necessary tables if they are not present + * + * @param cls the @e cls of this struct with the plugin-specific state + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ + int + (*create_tables) (void *cls); + + + /** + * Start a transaction. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param session connection to use + * @return #GNUNET_OK on success + */ + int + (*start) (void *cls, + struct TALER_AUDITORDB_Session *session); + + + /** + * Commit a transaction. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param session connection to use + * @return #GNUNET_OK on success, #GNUNET_NO if the transaction + * can be retried, #GNUNET_SYSERR on hard failures + */ + int + (*commit) (void *cls, + struct TALER_AUDITORDB_Session *session); + + + /** + * Abort/rollback a transaction. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param session connection to use + */ + void + (*rollback) (void *cls, + struct TALER_AUDITORDB_Session *session); + + + /** + * Function called to perform "garbage collection" on the + * database, expiring records we no longer require. + * + * @param cls closure + * @return #GNUNET_OK on success, + * #GNUNET_SYSERR on DB errors + */ + int + (*gc) (void *cls); + +}; + + +#endif /* _TALER_AUDITOR_DB_H */ |