diff options
| author | Christian Grothoff <christian@grothoff.org> | 2017-09-25 23:26:48 +0200 | 
|---|---|---|
| committer | Christian Grothoff <christian@grothoff.org> | 2017-09-25 23:27:16 +0200 | 
| commit | e78e0f6c4e28e1f90fadd5d9840f5428f6ba1ea0 (patch) | |
| tree | a54e8bb3be2550bad41a9e8319329ddb142630f0 /src/auditor/taler-wire-auditor.c | |
| parent | e5a9b3ffa7a6104d730b450082362b9cf06ada22 (diff) | |
starting point for #4948
Diffstat (limited to 'src/auditor/taler-wire-auditor.c')
| -rw-r--r-- | src/auditor/taler-wire-auditor.c | 485 | 
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 00000000..d6a98a44 --- /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", +                                             ¤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 */  | 
