From e78e0f6c4e28e1f90fadd5d9840f5428f6ba1ea0 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Mon, 25 Sep 2017 23:26:48 +0200 Subject: starting point for #4948 --- src/auditor/Makefile.am | 13 ++ src/auditor/taler-wire-auditor.c | 485 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 498 insertions(+) create mode 100644 src/auditor/taler-wire-auditor.c (limited to 'src') diff --git a/src/auditor/Makefile.am b/src/auditor/Makefile.am index 62dd19106..5beab8b6a 100644 --- a/src/auditor/Makefile.am +++ b/src/auditor/Makefile.am @@ -13,6 +13,7 @@ pkgcfg_DATA = \ bin_PROGRAMS = \ taler-auditor \ + taler-wire-auditor \ taler-auditor-sign taler_auditor_SOURCES = \ @@ -27,6 +28,18 @@ taler_auditor_LDADD = \ -ljansson \ -lgnunetutil +taler_wire_auditor_SOURCES = \ + taler-wire-auditor.c +taler_wire_auditor_LDADD = \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/util/libtalerutil.la \ + $(top_builddir)/src/json/libtalerjson.la \ + $(top_builddir)/src/wire/libtalerwire.la \ + $(top_builddir)/src/exchangedb/libtalerexchangedb.la \ + $(top_builddir)/src/auditordb/libtalerauditordb.la \ + -ljansson \ + -lgnunetutil + taler_auditor_sign_SOURCES = \ taler-auditor-sign.c taler_auditor_sign_LDADD = \ 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 +*/ +/** + * @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 +#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", + ¤cy)) + { + 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 */ -- cgit v1.2.3