/*
  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
 * - Finally, we check that all wire transfers that should have been made,
 *   were actually made
 */
#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"
/**
 * How much time do we allow the aggregator to lag behind?  If
 * wire transfers should have been made more than #GRACE_PERIOD
 * before, we issue warnings.
 */
#define GRACE_PERIOD GNUNET_TIME_UNIT_HOURS
/**
 * Return value from main().
 */
static int global_ret;
/**
 * Command-line option "-r": restart audit from scratch
 */
static int restart;
/**
 * Name of the wire plugin to load to access the exchange's bank account.
 */
static char *wire_plugin;
/**
 * 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;
/**
 * Map with information about incoming wire transfers.
 * Maps hashes of the wire offsets to `struct ReserveInInfo`s.
 */
static struct GNUNET_CONTAINER_MultiHashMap *in_map;
/**
 * Map with information about outgoing wire transfers.
 * Maps hashes of the wire subjects (in binary encoding)
 * to `struct ReserveOutInfo`s.
 */
static struct GNUNET_CONTAINER_MultiHashMap *out_map;
/**
 * 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;
/**
 * Handle to the wire plugin for wire operations.
 */
static struct TALER_WIRE_Plugin *wp;
/**
 * Active wire request for the transaction history.
 */
static struct TALER_WIRE_HistoryHandle *hh;
/**
 * Query status for the incremental processing status in the auditordb.
 */
static enum GNUNET_DB_QueryStatus qsx;
/**
 * Last reserve_in / wire_out serial IDs seen.
 */
static struct TALER_AUDITORDB_WireProgressPoint pp;
/**
 * Where we are in the inbound (CREDIT) transaction history.
 */
static void *in_wire_off;
/**
 * Where we are in the inbound (DEBIT) transaction history.
 */
static void *out_wire_off;
/**
 * Number of bytes in #in_wire_off and #out_wire_off.
 */
static size_t wire_off_size;
/**
 * Array of reports about row inconsitencies in wire_out table.
 */
static json_t *report_wire_out_inconsistencies;
/**
 * Array of reports about row inconsitencies in reserves_in table.
 */
static json_t *report_reserve_in_inconsistencies;
/**
 * Array of reports about wrong bank account being recorded for
 * incoming wire transfers.
 */
static json_t *report_missattribution_in_inconsistencies;
/**
 * Array of reports about row inconcistencies.
 */
static json_t *report_row_inconsistencies;
/**
 * Array of reports about inconcistencies in the database about
 * the incoming wire transfers (exchange is not exactly to blame).
 */
static json_t *report_wire_format_inconsistencies;
/**
 * Array of reports about minor row inconcistencies.
 */
static json_t *report_row_minor_inconsistencies;
/**
 * Array of reports about lagging transactions.
 */
static json_t *report_lags;
/**
 * Total amount that was transferred too much from the exchange.
 */
static struct TALER_Amount total_bad_amount_out_plus;
/**
 * Total amount that was transferred too little from the exchange.
 */
static struct TALER_Amount total_bad_amount_out_minus;
/**
 * Total amount that was transferred too much to the exchange.
 */
static struct TALER_Amount total_bad_amount_in_plus;
/**
 * Total amount that was transferred too little to the exchange.
 */
static struct TALER_Amount total_bad_amount_in_minus;
/**
 * Total amount where the exchange has the wrong sender account
 * for incoming funds and may thus wire funds to the wrong
 * destination when closing the reserve.
 */
static struct TALER_Amount total_missattribution_in;
/**
 * Total amount which the exchange did not transfer in time.
 */
static struct TALER_Amount total_amount_lag;
/**
 * Total amount affected by wire format trouble.s
 */
static struct TALER_Amount total_wire_format_amount;
/**
 * Amount of zero in our currency.
 */
static struct TALER_Amount zero;
/* *****************************   Shutdown   **************************** */
/**
 * Entry in map with wire information we expect to obtain from the
 * bank later.
 */
struct ReserveInInfo
{
  /**
   * Hash of expected row offset.
   */
  struct GNUNET_HashCode row_off_hash;
  /**
   * Number of bytes in @e row_off.
   */
  size_t row_off_size;
  /**
   * Expected details about the wire transfer.
   */
  struct TALER_WIRE_TransferDetails details;
  /**
   * RowID in reserves_in table.
   */
  uint64_t rowid;
};
/**
 * Entry in map with wire information we expect to obtain from the
 * #edb later.
 */
struct ReserveOutInfo
{
  /**
   * Hash of the wire transfer subject.
   */
  struct GNUNET_HashCode subject_hash;
  /**
   * Expected details about the wire transfer.
   */
  struct TALER_WIRE_TransferDetails details;
};
/**
 * Free entry in #in_map.
 *
 * @param cls NULL
 * @param key unused key
 * @param value the `struct ReserveInInfo` to free
 * @return #GNUNET_OK
 */
static int
free_rii (void *cls,
	  const struct GNUNET_HashCode *key,
	  void *value)
{
  struct ReserveInInfo *rii = value;
  GNUNET_assert (GNUNET_YES ==
		 GNUNET_CONTAINER_multihashmap_remove (in_map,
						       key,
						       rii));
  json_decref (rii->details.account_details);
  GNUNET_free (rii);
  return GNUNET_OK;
}
/**
 * Free entry in #out_map.
 *
 * @param cls NULL
 * @param key unused key
 * @param value the `struct ReserveOutInfo` to free
 * @return #GNUNET_OK
 */
static int
free_roi (void *cls,
	  const struct GNUNET_HashCode *key,
	  void *value)
{
  struct ReserveOutInfo *roi = value;
  GNUNET_assert (GNUNET_YES ==
		 GNUNET_CONTAINER_multihashmap_remove (out_map,
						       key,
						       roi));
  json_decref (roi->details.account_details);
  GNUNET_free (roi);
  return GNUNET_OK;
}
/**
 * Task run on shutdown.
 *
 * @param cls NULL
 */
static void
do_shutdown (void *cls)
{
  if (NULL != report_row_inconsistencies)
  {
    json_t *report;
    GNUNET_assert (NULL != report_row_minor_inconsistencies);
    report = json_pack ("{s:o, s:o, s:o, s:o, s:o,"
                        " s:o, s:o, s:o, s:o, s:o,"
                        " s:o, s:o, s:o, s:o }",
                        /* blocks of 5 */
			"wire_out_amount_inconsistencies",
                        report_wire_out_inconsistencies,
                        "total_wire_out_delta_plus",
                        TALER_JSON_from_amount (&total_bad_amount_out_plus),
                        "total_wire_out_delta_minus",
                        TALER_JSON_from_amount (&total_bad_amount_out_minus),
			"reserve_in_amount_inconsistencies",
                        report_reserve_in_inconsistencies,
                        "total_wire_in_delta_plus",
                        TALER_JSON_from_amount (&total_bad_amount_in_plus),
                        /* block */
                        "total_wire_in_delta_minus",
                        TALER_JSON_from_amount (&total_bad_amount_in_minus),
                        "missattribution_in_inconsistencies",
                        report_missattribution_in_inconsistencies,
                        "total_missattribution_in",
                        TALER_JSON_from_amount (&total_missattribution_in),
			"row_inconsistencies",
                        report_row_inconsistencies,
			"row_minor_inconsistencies",
                        report_row_minor_inconsistencies,
                        /* block */
                        "total_wire_format_amount",
                        TALER_JSON_from_amount (&total_wire_format_amount),
                        "wire_format_inconsistencies",
                        report_wire_format_inconsistencies,
                        "total_amount_lag",
                        TALER_JSON_from_amount (&total_bad_amount_in_minus),
                        "lag_details",
                        report_lags);
    GNUNET_break (NULL != report);
    json_dumpf (report,
		stdout,
		JSON_INDENT (2));
    json_decref (report);
    report_wire_out_inconsistencies = NULL;
    report_reserve_in_inconsistencies = NULL;
    report_row_inconsistencies = NULL;
    report_row_minor_inconsistencies = NULL;
    report_missattribution_in_inconsistencies = NULL;
    report_lags = NULL;
    report_wire_format_inconsistencies = NULL;
  }
  if (NULL != hh)
  {
    wp->get_history_cancel (wp->cls,
                            hh);
    hh = NULL;
  }
  if (NULL != in_map)
  {
    GNUNET_CONTAINER_multihashmap_iterate (in_map,
					   &free_rii,
					   NULL);
    GNUNET_CONTAINER_multihashmap_destroy (in_map);
    in_map = NULL;
  }
  if (NULL != out_map)
  {
    GNUNET_CONTAINER_multihashmap_iterate (out_map,
					   &free_roi,
					   NULL);
    GNUNET_CONTAINER_multihashmap_destroy (out_map);
    out_map = NULL;
  }
  if (NULL != wp)
  {
    TALER_WIRE_plugin_unload (wp);
    wp = NULL;
  }
  if (NULL != adb)
  {
    TALER_AUDITORDB_plugin_unload (adb);
    adb = NULL;
  }
  if (NULL != edb)
  {
    TALER_EXCHANGEDB_plugin_unload (edb);
    edb = NULL;
  }
}
/* ***************************** Report logic **************************** */
/**
 * Add @a object to the report @a array.  Fail hard if this fails.
 *
 * @param array report array to append @a object to
 * @param object object to append, should be check that it is not NULL
 */
static void
report (json_t *array,
	json_t *object)
{
  GNUNET_assert (NULL != object);
  GNUNET_assert (0 ==
		 json_array_append_new (array,
					object));
}
/* *************************** General transaction logic ****************** */
/**
 * Commit the transaction, checkpointing our progress in the auditor
 * DB.
 *
 * @param qs transaction status so far
 * @return transaction status code
 */
static enum GNUNET_DB_QueryStatus
commit (enum GNUNET_DB_QueryStatus qs)
{
  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 error, not recording progress\n");
    adb->rollback (adb->cls,
                   asession);
    edb->rollback (edb->cls,
                   esession);
    return qs;
  }
  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qsx)
    qs = adb->update_wire_auditor_progress (adb->cls,
                                            asession,
                                            &master_pub,
                                            &pp,
                                            in_wire_off,
                                            out_wire_off,
                                            wire_off_size);
  else
    qs = adb->insert_wire_auditor_progress (adb->cls,
                                            asession,
                                            &master_pub,
                                            &pp,
                                            in_wire_off,
                                            out_wire_off,
                                            wire_off_size);
  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\n"),
              (unsigned long long) pp.last_reserve_in_serial_id,
              (unsigned long long) pp.last_wire_out_serial_id);
  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;
}
/* ***************************** Analyze reserves_out ************************ */
/**
 * Function called with details about outgoing wire transfers
 * as claimed by the exchange DB.
 *
 * @param cls NULL
 * @param rowid unique serial ID for the refresh session in our DB
 * @param date timestamp of the wire transfer (roughly)
 * @param wtid wire transfer subject
 * @param wire wire transfer details of the receiver
 * @param amount amount that was wired
 * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
 */
static int
wire_out_cb (void *cls,
             uint64_t rowid,
             struct GNUNET_TIME_Absolute date,
             const struct TALER_WireTransferIdentifierRawP *wtid,
             const json_t *wire,
             const struct TALER_Amount *amount)
{
  struct GNUNET_HashCode key;
  struct ReserveOutInfo *roi;
  GNUNET_CRYPTO_hash (wtid,
		      sizeof (struct TALER_WireTransferIdentifierRawP),
		      &key);
  roi = GNUNET_CONTAINER_multihashmap_get (in_map,
					   &key);
  if (NULL == roi)
  {
    /* Wire transfer was not made (yet) at all (but would have been
       justified), so the entire amount is missing / still to be done.
       This is moderately harmless, it might just be that the aggreator
       has not yet fully caught up with the transfers it should do. */
    report (report_wire_out_inconsistencies,
            json_pack ("{s:I, s:o, s:o, s:o, s:s, s:s}",
                       "row", (json_int_t) rowid,
                       "amount_wired", TALER_JSON_from_amount (&zero),
                       "amount_justified", TALER_JSON_from_amount (amount),
                       "wtid", GNUNET_JSON_from_data_auto (wtid),
                       "timestamp", GNUNET_STRINGS_absolute_time_to_string (date),
                       "diagnostic", "wire transfer not made (yet?)"));
    GNUNET_break (GNUNET_OK ==
                  TALER_amount_add (&total_bad_amount_out_minus,
                                    &total_bad_amount_out_minus,
                                    amount));
    return GNUNET_OK;
  }
  if (! json_equal ((json_t *) wire,
		    roi->details.account_details))
  {
    /* Destination bank account is wrong in actual wire transfer, so
       we should count the wire transfer as entirely spurious, and
       additionally consider the justified wire transfer as missing. */
    report (report_wire_out_inconsistencies,
            json_pack ("{s:I, s:o, s:o, s:o, s:s, s:s}",
                       "row", (json_int_t) rowid,
                       "amount_wired", TALER_JSON_from_amount (&roi->details.amount),
                       "amount_justified", TALER_JSON_from_amount (&zero),
                       "wtid", GNUNET_JSON_from_data_auto (wtid),
                       "timestamp", GNUNET_STRINGS_absolute_time_to_string (date),
                       "diagnostic", "recevier account missmatch"));
    GNUNET_break (GNUNET_OK ==
                  TALER_amount_add (&total_bad_amount_out_plus,
                                    &total_bad_amount_out_plus,
                                    &roi->details.amount));
    report (report_wire_out_inconsistencies,
            json_pack ("{s:I, s:o, s:o, s:o, s:s, s:s}",
                       "row", (json_int_t) rowid,
                       "amount_wired", TALER_JSON_from_amount (&zero),
                       "amount_justified", TALER_JSON_from_amount (amount),
                       "wtid", GNUNET_JSON_from_data_auto (wtid),
                       "timestamp", GNUNET_STRINGS_absolute_time_to_string (date),
                       "diagnostic", "receiver account missmatch"));
    GNUNET_break (GNUNET_OK ==
                  TALER_amount_add (&total_bad_amount_out_minus,
                                    &total_bad_amount_out_minus,
                                    amount));
    goto cleanup;
  }
  if (0 != TALER_amount_cmp (&roi->details.amount,
			     amount))
  {
    report (report_wire_out_inconsistencies,
            json_pack ("{s:I, s:o, s:o, s:o, s:s, s:s}",
                       "row", (json_int_t) rowid,
                       "amount_justified", TALER_JSON_from_amount (amount),
                       "amount_wired", TALER_JSON_from_amount (&roi->details.amount),
                       "wtid", GNUNET_JSON_from_data_auto (wtid),
                       "timestamp", GNUNET_STRINGS_absolute_time_to_string (date),
                       "diagnostic", "wire amount does not match"));
    if (0 < TALER_amount_cmp (amount,
                              &roi->details.amount))
    {
      /* amount > roi->details.amount: wire transfer was smaller than it should have been */
      struct TALER_Amount delta;
      GNUNET_break (GNUNET_OK ==
                    TALER_amount_subtract (&delta,
                                           amount,
                                           &roi->details.amount));
      GNUNET_break (GNUNET_OK ==
                    TALER_amount_add (&total_bad_amount_out_minus,
                                      &total_bad_amount_out_minus,
                                      &delta));
    }
    else
    {
      /* roi->details.amount < amount: wire transfer was larger than it should have been */
      struct TALER_Amount delta;
      GNUNET_break (GNUNET_OK ==
                    TALER_amount_subtract (&delta,
                                           &roi->details.amount,
                                           amount));
      GNUNET_break (GNUNET_OK ==
                    TALER_amount_add (&total_bad_amount_out_plus,
                                      &total_bad_amount_out_plus,
                                      &delta));
    }
    goto cleanup;
  }
  if (roi->details.execution_date.abs_value_us !=
      date.abs_value_us)
  {
    report (report_row_minor_inconsistencies,
            json_pack ("{s:s, s:I, s:s}",
                       "table", "wire_out",
                       "row", (json_int_t) rowid,
                       "diagnostic", "execution date missmatch"));
  }
cleanup:
  GNUNET_assert (GNUNET_OK ==
		 GNUNET_CONTAINER_multihashmap_remove (out_map,
						       &key,
						       roi));
  GNUNET_assert (GNUNET_OK ==
		 free_roi (NULL,
			   &key,
			   roi));
  return GNUNET_OK;
}
/**
 * Complain that we failed to match an entry from #out_map.  This
 * means a wire transfer was made without proper justification.
 *
 * @param cls NULL
 * @param key unused key
 * @param value the `struct ReserveOutInfo` to report
 * @return #GNUNET_OK
 */
static int
complain_out_not_found (void *cls,
			const struct GNUNET_HashCode *key,
			void *value)
{
  struct ReserveOutInfo *roi = value;
  report (report_wire_out_inconsistencies,
          json_pack ("{s:I, s:o, s:o, s:o, s:s, s:s}",
                     "row", (json_int_t) 0,
                     "amount_wired", TALER_JSON_from_amount (&roi->details.amount),
                     "amount_justified", TALER_JSON_from_amount (&zero),
                     "wtid", (NULL == roi->details.wtid_s)
                     ? GNUNET_JSON_from_data_auto (&roi->details.wtid)
                     : json_string (roi->details.wtid_s),
                     "timestamp", GNUNET_STRINGS_absolute_time_to_string (roi->details.execution_date),
                     "diagnostic", "justification for wire transfer not found"));
  GNUNET_break (GNUNET_OK ==
                TALER_amount_add (&total_bad_amount_out_plus,
                                  &total_bad_amount_out_plus,
                                  &roi->details.amount));
  return GNUNET_OK;
}
/**
 * Function called on deposits that are past their due date
 * and have not yet seen a wire transfer.
 *
 * @param cls closure
 * @param rowid deposit table row of the coin's deposit
 * @param coin_pub public key of the coin
 * @param amount value of the deposit, including fee
 * @param wire where should the funds be wired
 * @param deadline what was the requested wire transfer deadline
 * @param tiny did the exchange defer this transfer because it is too small?
 * @param done did the exchange claim that it made a transfer?
 */
static void
wire_missing_cb (void *cls,
                 uint64_t rowid,
                 const struct TALER_CoinSpendPublicKeyP *coin_pub,
                 const struct TALER_Amount *amount,
                 const json_t *wire,
                 struct GNUNET_TIME_Absolute deadline,
                 /* bool? */ int tiny,
                 /* bool? */ int done)
{
  GNUNET_break (GNUNET_OK ==
                TALER_amount_add (&total_amount_lag,
                                  &total_amount_lag,
                                  amount));
  if (GNUNET_YES == tiny)
  {
    struct TALER_Amount rounded;
    rounded = *amount;
    GNUNET_break (GNUNET_SYSERR !=
                  wp->amount_round (wp->cls,
                                    &rounded));
    if (0 == TALER_amount_cmp (&rounded,
                               &zero))
      return; /* acceptable, amount was tiny */
  }
  report (report_lags,
          json_pack ("{s:I, s:o, s:s, s:s, s:o, s:O}",
                     "row", (json_int_t) rowid,
                     "amount", TALER_JSON_from_amount (amount),
                     "deadline", GNUNET_STRINGS_absolute_time_to_string (deadline),
                     "claimed_done", (done) ? "yes" : "no",
                     "coin_pub", GNUNET_JSON_from_data_auto (coin_pub),
                     "account", wire));
}
/**
 * Go over the "wire_out" table of the exchange and
 * verify that all wire outs are in that table.
 */
static void
check_exchange_wire_out ()
{
  enum GNUNET_DB_QueryStatus qs;
  struct GNUNET_TIME_Absolute next_timestamp;
  qs = edb->select_wire_out_above_serial_id (edb->cls,
					     esession,
					     pp.last_wire_out_serial_id,
					     &wire_out_cb,
					     NULL);
  if (0 > qs)
  {
    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    global_ret = 1;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  GNUNET_CONTAINER_multihashmap_iterate (out_map,
					 &complain_out_not_found,
					 NULL);
  /* clean up */
  GNUNET_CONTAINER_multihashmap_iterate (out_map,
					 &free_roi,
					 NULL);
  GNUNET_CONTAINER_multihashmap_destroy (out_map);
  out_map = NULL;
  /* now check that all wire transfers that should have happened,
     have indeed happened */
  next_timestamp = GNUNET_TIME_absolute_get ();
  /* Subtract #GRACE_PERIOD, so we can be a bit behind in processing
     without immediately raising undue concern */
  next_timestamp = GNUNET_TIME_absolute_subtract (next_timestamp,
                                                  GRACE_PERIOD);
  qs = edb->select_deposits_missing_wire (edb->cls,
                                          esession,
                                          pp.last_timestamp,
                                          next_timestamp,
                                          &wire_missing_cb,
                                          &next_timestamp);
  if (0 > qs)
  {
    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    global_ret = 1;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  pp.last_timestamp = next_timestamp;
  /* conclude with: */
  commit (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
  GNUNET_SCHEDULER_shutdown ();
}
/**
 * This function is called for all transactions that
 * are credited to the exchange's account (incoming
 * transactions).
 *
 * @param cls closure
 * @param ec error code in case something went wrong
 * @param dir direction of the transfer
 * @param row_off identification of the position at which we are querying
 * @param row_off_size number of bytes in @a row_off
 * @param details details about the wire transfer
 * @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration
 */
static int
history_debit_cb (void *cls,
                  enum TALER_ErrorCode ec,
		  enum TALER_BANK_Direction dir,
		  const void *row_off,
		  size_t row_off_size,
		  const struct TALER_WIRE_TransferDetails *details)
{
  struct ReserveOutInfo *roi;
  struct GNUNET_HashCode rowh;
  if (TALER_BANK_DIRECTION_NONE == dir)
  {
    if (TALER_EC_NONE != ec)
    {
      /* FIXME: log properly to audit report! */
      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "Error fetching history: %u!\n",
                  (unsigned int) ec);
    }
    /* end of iteration, now check wire_out to see
       if it matches #out_map */
    hh = NULL;
    check_exchange_wire_out ();
    return GNUNET_OK;
  }
  if (NULL != details->wtid_s)
  {
    char *diagnostic;
    GNUNET_CRYPTO_hash (row_off,
                        row_off_size,
                        &rowh);
    GNUNET_asprintf (&diagnostic,
                     "malformed subject `%8s...'",
                     details->wtid_s);
    GNUNET_break (GNUNET_OK ==
                  TALER_amount_add (&total_wire_format_amount,
                                    &total_wire_format_amount,
                                    &details->amount));
    report (report_wire_format_inconsistencies,
            json_pack ("{s:o, s:o, s:s}",
                       "amount", TALER_JSON_from_amount (&details->amount),
                       "wire_offset_hash", GNUNET_JSON_from_data_auto (&rowh),
                       "diagnostic", diagnostic));
    GNUNET_free (diagnostic);
    return GNUNET_OK;
  }
  roi = GNUNET_new (struct ReserveOutInfo);
  GNUNET_CRYPTO_hash (&details->wtid,
		      sizeof (details->wtid),
		      &roi->subject_hash);
  roi->details.amount = details->amount;
  roi->details.execution_date = details->execution_date;
  roi->details.wtid = details->wtid;
  roi->details.account_details = json_incref ((json_t *) details->account_details);
  if (GNUNET_OK !=
      GNUNET_CONTAINER_multihashmap_put (out_map,
					 &roi->subject_hash,
					 roi,
					 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
  {
    char *diagnostic;
    GNUNET_CRYPTO_hash (row_off,
                        row_off_size,
                        &rowh);
    GNUNET_asprintf (&diagnostic,
                     "duplicate subject hash `%8s...'",
                     TALER_B2S (&roi->subject_hash));
    GNUNET_break (GNUNET_OK ==
                  TALER_amount_add (&total_wire_format_amount,
                                    &total_wire_format_amount,
                                    &details->amount));
    report (report_wire_format_inconsistencies,
            json_pack ("{s:o, s:o, s:s}",
                       "amount", TALER_JSON_from_amount (&details->amount),
                       "wire_offset_hash", GNUNET_JSON_from_data_auto (&rowh),
                       "diagnostic", diagnostic));
    GNUNET_free (diagnostic);
    return GNUNET_OK;
  }
  return GNUNET_OK;
}
/**
 * Main function for processing 'reserves_out' data.
 * We start by going over the DEBIT transactions this
 * time, and then verify that all of them are justified
 * by 'reserves_out'.
 */
static void
process_debits ()
{
  GNUNET_assert (NULL == hh);
  out_map = GNUNET_CONTAINER_multihashmap_create (1024,
						  GNUNET_YES);
  hh = wp->get_history (wp->cls,
                        TALER_BANK_DIRECTION_DEBIT,
                        out_wire_off,
                        wire_off_size,
                        INT64_MAX,
                        &history_debit_cb,
                        NULL);
  if (NULL == hh)
  {
    fprintf (stderr,
             "Failed to obtain bank transaction history\n");
    commit (GNUNET_DB_STATUS_HARD_ERROR);
    global_ret = 1;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
}
/* ***************************** Analyze reserves_in ************************ */
/**
 * Function called with details about incoming wire transfers
 * as claimed by the exchange DB.
 *
 * @param cls NULL
 * @param rowid unique serial ID for the refresh session in our DB
 * @param reserve_pub public key of the reserve (also the WTID)
 * @param credit amount that was received
 * @param sender_account_details information about the sender's bank account
 * @param wire_reference unique identifier for the wire transfer (plugin-specific format)
 * @param wire_reference_size number of bytes in @a wire_reference
 * @param execution_date when did we receive the funds
 * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
 */
static int
reserve_in_cb (void *cls,
	       uint64_t rowid,
	       const struct TALER_ReservePublicKeyP *reserve_pub,
	       const struct TALER_Amount *credit,
	       const json_t *sender_account_details,
	       const void *wire_reference,
	       size_t wire_reference_size,
	       struct GNUNET_TIME_Absolute execution_date)
{
  struct ReserveInInfo *rii;
  rii = GNUNET_new (struct ReserveInInfo);
  GNUNET_CRYPTO_hash (wire_reference,
		      wire_reference_size,
		      &rii->row_off_hash);
  rii->row_off_size = wire_reference_size;
  rii->details.amount = *credit;
  rii->details.execution_date = execution_date;
  /* reserve public key should be the WTID */
  GNUNET_assert (sizeof (rii->details.wtid) ==
                 sizeof (*reserve_pub));
  memcpy (&rii->details.wtid,
          reserve_pub,
          sizeof (*reserve_pub));
  rii->details.account_details = json_incref ((json_t *) sender_account_details);
  rii->rowid = rowid;
  if (GNUNET_OK !=
      GNUNET_CONTAINER_multihashmap_put (in_map,
					 &rii->row_off_hash,
					 rii,
					 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
  {
    report (report_row_inconsistencies,
            json_pack ("{s:s, s:I, s:o, s:s}",
                       "table", "reserves_in",
                       "row", (json_int_t) rowid,
                       "wire_offset_hash", GNUNET_JSON_from_data_auto (&rii->row_off_hash),
                       "diagnostic", "duplicate wire offset"));
    json_decref (rii->details.account_details);
    GNUNET_free (rii);
    return GNUNET_OK;
  }
  pp.last_reserve_in_serial_id = rowid + 1;
  return GNUNET_OK;
}
/**
 * Complain that we failed to match an entry from #in_map.
 *
 * @param cls NULL
 * @param key unused key
 * @param value the `struct ReserveInInfo` to free
 * @return #GNUNET_OK
 */
static int
complain_in_not_found (void *cls,
		       const struct GNUNET_HashCode *key,
		       void *value)
{
  struct ReserveInInfo *rii = value;
  report (report_reserve_in_inconsistencies,
          json_pack ("{s:I, s:o, s:o, s:o, s:s, s:s}",
                     "row", (json_int_t) rii->rowid,
                     "amount_expected", TALER_JSON_from_amount (&rii->details.amount),
                     "amount_wired", TALER_JSON_from_amount (&zero),
                     "wtid", GNUNET_JSON_from_data_auto (&rii->details.wtid),
                     "timestamp", GNUNET_STRINGS_absolute_time_to_string (rii->details.execution_date),
                     "diagnostic", "incoming wire transfer claimed by exchange not found"));
  GNUNET_break (GNUNET_OK ==
                TALER_amount_add (&total_bad_amount_in_minus,
                                  &total_bad_amount_in_minus,
                                  &rii->details.amount));
  return GNUNET_OK;
}
/**
 * Conclude the credit history check by logging entries that
 * were not found and freeing resources. Then move on to
 * processing debits.
 */
static void
conclude_credit_history ()
{
  GNUNET_CONTAINER_multihashmap_iterate (in_map,
                                         &complain_in_not_found,
                                         NULL);
  /* clean up before 2nd phase */
  GNUNET_CONTAINER_multihashmap_iterate (in_map,
                                         &free_rii,
                                         NULL);
  GNUNET_CONTAINER_multihashmap_destroy (in_map);
  in_map = NULL;
  process_debits ();
}
/**
 * This function is called for all transactions that
 * are credited to the exchange's account (incoming
 * transactions).
 *
 * @param cls closure
 * @param ec error code in case something went wrong
 * @param dir direction of the transfer
 * @param row_off identification of the position at which we are querying
 * @param row_off_size number of bytes in @a row_off
 * @param details details about the wire transfer
 * @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration
 */
static int
history_credit_cb (void *cls,
                   enum TALER_ErrorCode ec,
                   enum TALER_BANK_Direction dir,
                   const void *row_off,
                   size_t row_off_size,
                   const struct TALER_WIRE_TransferDetails *details)
{
  struct ReserveInInfo *rii;
  struct GNUNET_HashCode key;
  if (TALER_BANK_DIRECTION_NONE == dir)
  {
    if (TALER_EC_NONE != ec)
    {
      /* FIXME: log properly to audit report! */
      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "Error fetching history: %u!\n",
                  (unsigned int) ec);
    }
    /* end of operation */
    hh = NULL;
    conclude_credit_history ();
    return GNUNET_OK;
  }
  GNUNET_CRYPTO_hash (row_off,
		      row_off_size,
		      &key);
  rii = GNUNET_CONTAINER_multihashmap_get (in_map,
					   &key);
  if (NULL == rii)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
		"Failed to find wire transfer at `%s' in exchange database. Audit ends at this point in time.\n",
		GNUNET_STRINGS_absolute_time_to_string (details->execution_date));
    hh = NULL;
    conclude_credit_history ();
    return GNUNET_SYSERR; /* not an error, just end of processing */
  }
  /* Update offset */
  if (NULL == in_wire_off)
  {
    wire_off_size = row_off_size;
    in_wire_off = GNUNET_malloc (row_off_size);
  }
  if (wire_off_size != row_off_size)
  {
    GNUNET_break (0);
    commit (GNUNET_DB_STATUS_HARD_ERROR);
    GNUNET_SCHEDULER_shutdown ();
    hh = NULL;
    return GNUNET_SYSERR;
  }
  memcpy (in_wire_off,
	  row_off,
	  row_off_size);
  /* compare records with expected data */
  if (row_off_size != rii->row_off_size)
  {
    GNUNET_break (0);
    report (report_row_inconsistencies,
            json_pack ("{s:s, s:o, s:o, s:s}",
                       "table", "reserves_in",
                       "row", GNUNET_JSON_from_data (row_off, row_off_size),
                       "wire_offset_hash", GNUNET_JSON_from_data_auto (&key),
                       "diagnostic", "wire reference size missmatch"));
    return GNUNET_OK;
  }
  if (0 != memcmp (&details->wtid,
		   &rii->details.wtid,
		   sizeof (struct TALER_WireTransferIdentifierRawP)))
  {
    report (report_reserve_in_inconsistencies,
            json_pack ("{s:I, s:o, s:o, s:o, s:s, s:s}",
                       "row", GNUNET_JSON_from_data (row_off, row_off_size),
                       "amount_exchange_expected", TALER_JSON_from_amount (&rii->details.amount),
                       "amount_wired", TALER_JSON_from_amount (&zero),
                       "wtid", GNUNET_JSON_from_data_auto (&rii->details.wtid),
                       "timestamp", GNUNET_STRINGS_absolute_time_to_string (rii->details.execution_date),
                       "diagnostic", "wire subject does not match"));
    GNUNET_break (GNUNET_OK ==
                  TALER_amount_add (&total_bad_amount_in_minus,
                                    &total_bad_amount_in_minus,
                                    &rii->details.amount));
    report (report_reserve_in_inconsistencies,
            json_pack ("{s:I, s:o, s:o, s:o, s:s, s:s}",
                       "row", GNUNET_JSON_from_data (row_off, row_off_size),
                       "amount_exchange_expected", TALER_JSON_from_amount (&zero),
                       "amount_wired", TALER_JSON_from_amount (&details->amount),
                       "wtid", GNUNET_JSON_from_data_auto (&details->wtid),
                       "timestamp", GNUNET_STRINGS_absolute_time_to_string (details->execution_date),
                       "diagnostic", "wire subject does not match"));
    GNUNET_break (GNUNET_OK ==
                  TALER_amount_add (&total_bad_amount_in_plus,
                                    &total_bad_amount_in_plus,
                                    &details->amount));
    goto cleanup;
  }
  if (0 != TALER_amount_cmp (&rii->details.amount,
			     &details->amount))
  {
    report (report_reserve_in_inconsistencies,
            json_pack ("{s:I, s:o, s:o, s:o, s:s, s:s}",
                       "row", GNUNET_JSON_from_data (row_off, row_off_size),
                       "amount_exchange_expected", TALER_JSON_from_amount (&rii->details.amount),
                       "amount_wired", TALER_JSON_from_amount (&details->amount),
                       "wtid", GNUNET_JSON_from_data_auto (&details->wtid),
                       "timestamp", GNUNET_STRINGS_absolute_time_to_string (details->execution_date),
                       "diagnostic", "wire amount does not match"));
    if (0 < TALER_amount_cmp (&details->amount,
                              &rii->details.amount))
    {
      /* details->amount > rii->details.amount: wire transfer was larger than it should have been */
      struct TALER_Amount delta;
      GNUNET_break (GNUNET_OK ==
                    TALER_amount_subtract (&delta,
                                           &details->amount,
                                           &rii->details.amount));
      GNUNET_break (GNUNET_OK ==
                    TALER_amount_add (&total_bad_amount_in_plus,
                                      &total_bad_amount_in_plus,
                                      &delta));
    }
    else
    {
      /* rii->details.amount < details->amount: wire transfer was smaller than it should have been */
      struct TALER_Amount delta;
      GNUNET_break (GNUNET_OK ==
                    TALER_amount_subtract (&delta,
                                           &rii->details.amount,
                                           &details->amount));
      GNUNET_break (GNUNET_OK ==
                    TALER_amount_add (&total_bad_amount_in_minus,
                                      &total_bad_amount_in_minus,
                                      &delta));
    }
    goto cleanup;
  }
  if (! json_equal (details->account_details,
		    rii->details.account_details))
  {
    report (report_missattribution_in_inconsistencies,
            json_pack ("{s:s, s:o, s:o}",
                       "amount", TALER_JSON_from_amount (&rii->details.amount),
                       "row", GNUNET_JSON_from_data (row_off, row_off_size),
                       "wtid", GNUNET_JSON_from_data_auto (&rii->details.wtid)));
    GNUNET_break (GNUNET_OK ==
                  TALER_amount_add (&total_missattribution_in,
                                    &total_missattribution_in,
                                    &rii->details.amount));
  }
  if (details->execution_date.abs_value_us !=
      rii->details.execution_date.abs_value_us)
  {
    report (report_row_minor_inconsistencies,
            json_pack ("{s:s, s:o, s:s}",
                       "table", "reserves_in",
                       "row", GNUNET_JSON_from_data (row_off, row_off_size),
                       "diagnostic", "execution date missmatch"));
  }
 cleanup:
  GNUNET_assert (GNUNET_OK ==
		 GNUNET_CONTAINER_multihashmap_remove (in_map,
						       &key,
						       rii));
  GNUNET_assert (GNUNET_OK ==
		 free_rii (NULL,
			   &key,
			   rii));
  return GNUNET_OK;
}
/* ***************************** Setup logic ************************ */
/**
 * 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)
{
  static const struct TALER_MasterPublicKeyP zeromp;
  enum GNUNET_DB_QueryStatus qs;
  int ret;
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Launching auditor\n");
  cfg = c;
  if (0 == memcmp (&zeromp,
                   &master_pub,
                   sizeof (struct TALER_MasterPublicKeyP)))
  {
    /* -m option not given, try configuration */
    char *master_public_key_str;
    if (GNUNET_OK !=
        GNUNET_CONFIGURATION_get_value_string (cfg,
                                               "exchange",
                                               "MASTER_PUBLIC_KEY",
                                               &master_public_key_str))
    {
      fprintf (stderr,
               "Pass option -m or set it in the configuration!\n");
      GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                                 "exchange",
                                 "MASTER_PUBLIC_KEY");
      global_ret = 1;
      return;
    }
    if (GNUNET_OK !=
        GNUNET_CRYPTO_eddsa_public_key_from_string (master_public_key_str,
                                                    strlen (master_public_key_str),
                                                    &master_pub.eddsa_pub))
    {
      fprintf (stderr,
               "Invalid master public key given in configuration file.");
      GNUNET_free (master_public_key_str);
      global_ret = 1;
      return;
    }
    GNUNET_free (master_public_key_str);
  } /* end of -m not given */
  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_SCHEDULER_add_shutdown (&do_shutdown,
                                 NULL);
  esession = edb->get_session (edb->cls);
  if (NULL == esession)
  {
    fprintf (stderr,
             "Failed to initialize exchange session.\n");
    global_ret = 1;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  asession = adb->get_session (adb->cls);
  if (NULL == asession)
  {
    fprintf (stderr,
             "Failed to initialize auditor session.\n");
    global_ret = 1;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  wp = TALER_WIRE_plugin_load (cfg,
                               wire_plugin);
  if (NULL == wp)
  {
    fprintf (stderr,
             "Failed to load wire plugin `%s'\n",
             wire_plugin);
    global_ret = 1;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Starting audit\n");
  ret = adb->start (adb->cls,
                    asession);
  if (GNUNET_OK != ret)
  {
    GNUNET_break (0);
    global_ret = 1;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  ret = edb->start (edb->cls,
                    esession);
  if (GNUNET_OK != ret)
  {
    GNUNET_break (0);
    global_ret = 1;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  GNUNET_assert (NULL !=
		 (report_wire_out_inconsistencies = json_array ()));
  GNUNET_assert (NULL !=
		 (report_reserve_in_inconsistencies = json_array ()));
  GNUNET_assert (NULL !=
		 (report_row_minor_inconsistencies = json_array ()));
  GNUNET_assert (NULL !=
		 (report_wire_format_inconsistencies = json_array ()));
  GNUNET_assert (NULL !=
		 (report_row_inconsistencies = json_array ()));
  GNUNET_assert (NULL !=
		 (report_missattribution_in_inconsistencies = json_array ()));
  GNUNET_assert (NULL !=
		 (report_lags = json_array ()));
  GNUNET_assert (GNUNET_OK ==
                 TALER_amount_get_zero (currency,
                                        &total_bad_amount_out_plus));
  GNUNET_assert (GNUNET_OK ==
                 TALER_amount_get_zero (currency,
                                        &total_bad_amount_out_minus));
  GNUNET_assert (GNUNET_OK ==
                 TALER_amount_get_zero (currency,
                                        &total_bad_amount_in_plus));
  GNUNET_assert (GNUNET_OK ==
                 TALER_amount_get_zero (currency,
                                        &total_bad_amount_in_minus));
  GNUNET_assert (GNUNET_OK ==
                 TALER_amount_get_zero (currency,
                                        &total_missattribution_in));
  GNUNET_assert (GNUNET_OK ==
                 TALER_amount_get_zero (currency,
                                        &total_amount_lag));
  GNUNET_assert (GNUNET_OK ==
                 TALER_amount_get_zero (currency,
                                        &total_wire_format_amount));
  GNUNET_assert (GNUNET_OK ==
                 TALER_amount_get_zero (currency,
                                        &zero));
  qsx = adb->get_wire_auditor_progress (adb->cls,
                                        asession,
                                        &master_pub,
                                        &pp,
                                        &in_wire_off,
                                        &out_wire_off,
                                        &wire_off_size);
  if (0 > qsx)
  {
    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qsx);
    global_ret = 1;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  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\n"),
                (unsigned long long) pp.last_reserve_in_serial_id,
                (unsigned long long) pp.last_wire_out_serial_id);
  }
  in_map = GNUNET_CONTAINER_multihashmap_create (1024,
						 GNUNET_YES);
  qs = edb->select_reserves_in_above_serial_id (edb->cls,
						esession,
						pp.last_reserve_in_serial_id,
						&reserve_in_cb,
						NULL);
  if (0 > qs)
  {
    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    global_ret = 1;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
                "No new incoming transactions available, skipping CREDIT phase\n");
    process_debits ();
    return;
  }
  hh = wp->get_history (wp->cls,
                        TALER_BANK_DIRECTION_CREDIT,
                        in_wire_off,
                        wire_off_size,
                        INT64_MAX,
                        &history_credit_cb,
                        NULL);
  if (NULL == hh)
  {
    fprintf (stderr,
             "Failed to obtain bank transaction history\n");
    commit (GNUNET_DB_STATUS_HARD_ERROR);
    global_ret = 1;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
}
/**
 * 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_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_mandatory
    (GNUNET_GETOPT_option_string ('w',
				  "wire",
				  "PLUGINNAME",
				  "name of the wire plugin to use",
				  &wire_plugin)),
    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 */