aboutsummaryrefslogtreecommitdiff
path: root/src/auditor/taler-wire-auditor.c
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2017-09-25 23:26:48 +0200
committerChristian Grothoff <christian@grothoff.org>2017-09-25 23:27:16 +0200
commite78e0f6c4e28e1f90fadd5d9840f5428f6ba1ea0 (patch)
treea54e8bb3be2550bad41a9e8319329ddb142630f0 /src/auditor/taler-wire-auditor.c
parente5a9b3ffa7a6104d730b450082362b9cf06ada22 (diff)
starting point for #4948
Diffstat (limited to 'src/auditor/taler-wire-auditor.c')
-rw-r--r--src/auditor/taler-wire-auditor.c485
1 files changed, 485 insertions, 0 deletions
diff --git a/src/auditor/taler-wire-auditor.c b/src/auditor/taler-wire-auditor.c
new file mode 100644
index 000000000..d6a98a445
--- /dev/null
+++ b/src/auditor/taler-wire-auditor.c
@@ -0,0 +1,485 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2017 Taler Systems SA
+
+ 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-wire-auditor.c
+ * @brief audits that wire transfers match those from an exchange database.
+ * @author Christian Grothoff
+ *
+ * - First, this auditor verifies that 'reserves_in' actually matches
+ * the incoming wire transfers from the bank.
+ * - Second, we check that the outgoing wire transfers match those
+ * given in the 'wire_out' table.
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_auditordb_plugin.h"
+#include "taler_exchangedb_plugin.h"
+#include "taler_json_lib.h"
+#include "taler_wire_lib.h"
+#include "taler_signatures.h"
+
+
+/**
+ * Return value from main().
+ */
+static int global_ret;
+
+/**
+ * Command-line option "-r": restart audit from scratch
+ */
+static int restart;
+
+/**
+ * Handle to access the exchange's database.
+ */
+static struct TALER_EXCHANGEDB_Plugin *edb;
+
+/**
+ * Which currency are we doing the audit for?
+ */
+static char *currency;
+
+/**
+ * Our configuration.
+ */
+static const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+/**
+ * Our session with the #edb.
+ */
+static struct TALER_EXCHANGEDB_Session *esession;
+
+/**
+ * Handle to access the auditor's database.
+ */
+static struct TALER_AUDITORDB_Plugin *adb;
+
+/**
+ * Our session with the #adb.
+ */
+static struct TALER_AUDITORDB_Session *asession;
+
+/**
+ * Master public key of the exchange to audit.
+ */
+static struct TALER_MasterPublicKeyP master_pub;
+
+/**
+ * Last reserve_in serial ID seen.
+ */
+static struct TALER_AUDITORDB_ProgressPoint pp;
+
+
+/* ***************************** Report logic **************************** */
+
+#if 0
+/**
+ * Report a (serious) inconsistency in the exchange's database.
+ *
+ * @param table affected table
+ * @param rowid affected row, UINT64_MAX if row is missing
+ * @param diagnostic message explaining the problem
+ */
+static void
+report_row_inconsistency (const char *table,
+ uint64_t rowid,
+ const char *diagnostic)
+{
+ // TODO: implement proper reporting logic writing to file.
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Database inconsistency detected in table %s at row %llu: %s\n",
+ table,
+ (unsigned long long) rowid,
+ diagnostic);
+}
+
+
+/**
+ * Report a minor inconsistency in the exchange's database (i.e. something
+ * relating to timestamps that should have no financial implications).
+ *
+ * @param table affected table
+ * @param rowid affected row, UINT64_MAX if row is missing
+ * @param diagnostic message explaining the problem
+ */
+static void
+report_row_minor_inconsistency (const char *table,
+ uint64_t rowid,
+ const char *diagnostic)
+{
+ // TODO: implement proper reporting logic writing to file.
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Minor inconsistency detected in table %s at row %llu: %s\n",
+ table,
+ (unsigned long long) rowid,
+ diagnostic);
+}
+#endif
+
+
+/* ***************************** Analyze reserves_in ************************ */
+/* This logic checks the reserves_in table */
+
+/**
+ * Analyze reserves for being well-formed.
+ *
+ * @param cls NULL
+ * @return transaction status code
+ */
+static enum GNUNET_DB_QueryStatus
+analyze_reserves_in (void *cls)
+{
+ /* FIXME: #4958 */
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+}
+
+
+/* ***************************** Analyze reserves_out ************************ */
+/* This logic checks the reserves_out table */
+
+/**
+ * Analyze reserves for being well-formed.
+ *
+ * @param cls NULL
+ * @return transaction status code
+ */
+static enum GNUNET_DB_QueryStatus
+analyze_reserves_out (void *cls)
+{
+ /* FIXME: #4958 */
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+}
+
+
+/* *************************** General transaction logic ****************** */
+
+/**
+ * Type of an analysis function. Each analysis function runs in
+ * its own transaction scope and must thus be internally consistent.
+ *
+ * @param cls closure
+ * @return transaction status code
+ */
+typedef enum GNUNET_DB_QueryStatus
+(*Analysis)(void *cls);
+
+
+/**
+ * Perform the given @a analysis incrementally, checkpointing our
+ * progress in the auditor DB.
+ *
+ * @param analysis analysis to run
+ * @param analysis_cls closure for @a analysis
+ * @return transaction status code
+ */
+static enum GNUNET_DB_QueryStatus
+incremental_processing (Analysis analysis,
+ void *analysis_cls)
+{
+ enum GNUNET_DB_QueryStatus qs;
+ enum GNUNET_DB_QueryStatus qsx;
+
+ qsx = adb->get_auditor_progress (adb->cls,
+ asession,
+ &master_pub,
+ &pp);
+ if (0 > qsx)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qsx);
+ return qsx;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qsx)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ _("First analysis using this auditor, starting audit from scratch\n"));
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ _("Resuming audit at %llu/%llu/%llu/%llu/%llu/%llu/%llu\n"),
+ (unsigned long long) pp.last_reserve_in_serial_id,
+ (unsigned long long) pp.last_reserve_out_serial_id,
+ (unsigned long long) pp.last_withdraw_serial_id,
+ (unsigned long long) pp.last_deposit_serial_id,
+ (unsigned long long) pp.last_melt_serial_id,
+ (unsigned long long) pp.last_refund_serial_id,
+ (unsigned long long) pp.last_wire_out_serial_id);
+ }
+ qs = analysis (analysis_cls);
+ if (0 > qs)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Serialization issue, not recording progress\n");
+ else
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Hard database error, not recording progress\n");
+ return qs;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qsx)
+ qs = adb->update_auditor_progress (adb->cls,
+ asession,
+ &master_pub,
+ &pp);
+ else
+ qs = adb->insert_auditor_progress (adb->cls,
+ asession,
+ &master_pub,
+ &pp);
+ if (0 >= qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Failed to update auditor DB, not recording progress\n");
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ _("Concluded audit step at %llu/%llu/%llu/%llu/%llu/%llu/%llu\n\n"),
+ (unsigned long long) pp.last_reserve_in_serial_id,
+ (unsigned long long) pp.last_reserve_out_serial_id,
+ (unsigned long long) pp.last_withdraw_serial_id,
+ (unsigned long long) pp.last_deposit_serial_id,
+ (unsigned long long) pp.last_melt_serial_id,
+ (unsigned long long) pp.last_refund_serial_id,
+ (unsigned long long) pp.last_wire_out_serial_id);
+ return qs;
+}
+
+
+/**
+ * Perform the given @a analysis within a transaction scope.
+ * Commit on success.
+ *
+ * @param analysis analysis to run
+ * @param analysis_cls closure for @a analysis
+ * @return #GNUNET_OK if @a analysis succeessfully committed,
+ * #GNUNET_NO if we had an error on commit (retry may help)
+ * #GNUNET_SYSERR on hard errors
+ */
+static int
+transact (Analysis analysis,
+ void *analysis_cls)
+{
+ int ret;
+ enum GNUNET_DB_QueryStatus qs;
+
+ ret = adb->start (adb->cls,
+ asession);
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ ret = edb->start (edb->cls,
+ esession);
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ qs = incremental_processing (analysis,
+ analysis_cls);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ qs = edb->commit (edb->cls,
+ esession);
+ if (0 > qs)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Exchange DB commit failed, rolling back transaction\n");
+ adb->rollback (adb->cls,
+ asession);
+ }
+ else
+ {
+ qs = adb->commit (adb->cls,
+ asession);
+ if (0 > qs)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Auditor DB commit failed!\n");
+ }
+ }
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Processing failed, rolling back transaction\n");
+ adb->rollback (adb->cls,
+ asession);
+ edb->rollback (edb->cls,
+ esession);
+ }
+ return qs;
+}
+
+
+/**
+ * Initialize DB sessions and run the analysis.
+ */
+static void
+setup_sessions_and_run ()
+{
+ esession = edb->get_session (edb->cls);
+ if (NULL == esession)
+ {
+ fprintf (stderr,
+ "Failed to initialize exchange session.\n");
+ global_ret = 1;
+ return;
+ }
+ asession = adb->get_session (adb->cls);
+ if (NULL == asession)
+ {
+ fprintf (stderr,
+ "Failed to initialize auditor session.\n");
+ global_ret = 1;
+ return;
+ }
+
+ transact (&analyze_reserves_in,
+ NULL);
+ transact (&analyze_reserves_out,
+ NULL);
+}
+
+
+/**
+ * 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 c configuration
+ */
+static void
+run (void *cls,
+ char *const *args,
+ const char *cfgfile,
+ const struct GNUNET_CONFIGURATION_Handle *c)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Launching auditor\n");
+ cfg = c;
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "taler",
+ "CURRENCY",
+ &currency))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "taler",
+ "CURRENCY");
+ global_ret = 1;
+ return;
+ }
+ 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;
+ }
+ if (restart)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Full audit restart requested, dropping old audit data.\n");
+ GNUNET_break (GNUNET_OK ==
+ adb->drop_tables (adb->cls));
+ TALER_AUDITORDB_plugin_unload (adb);
+ if (NULL ==
+ (adb = TALER_AUDITORDB_plugin_load (cfg)))
+ {
+ fprintf (stderr,
+ "Failed to initialize auditor database plugin after drop.\n");
+ global_ret = 1;
+ TALER_EXCHANGEDB_plugin_unload (edb);
+ return;
+ }
+ GNUNET_break (GNUNET_OK ==
+ adb->create_tables (adb->cls));
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Starting audit\n");
+ setup_sessions_and_run ();
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Audit complete\n");
+ 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_mandatory
+ (GNUNET_GETOPT_option_base32_auto ('m',
+ "exchange-key",
+ "KEY",
+ "public key of the exchange (Crockford base32 encoded)",
+ &master_pub)),
+ GNUNET_GETOPT_option_flag ('r',
+ "restart",
+ "restart audit from the beginning (required on first run)",
+ &restart),
+ 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-wire-auditor",
+ "MESSAGE",
+ NULL));
+ if (GNUNET_OK !=
+ GNUNET_PROGRAM_run (argc,
+ argv,
+ "taler-wire-auditor",
+ "Audit exchange database for consistency with the bank's wire transfers",
+ options,
+ &run,
+ NULL))
+ return 1;
+ return global_ret;
+}
+
+
+/* end of taler-wire-auditor.c */