/*
  This file is part of TALER
  Copyright (C) 2016-2020 Taler Systems SA
  TALER is free software; you can redistribute it and/or modify it under the
  terms of the GNU Affero 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 Affero Public License for more details.
  You should have received a copy of the GNU Affero Public License along with
  TALER; see the file COPYING.  If not, see 
*/
/**
 * @file auditor/taler-helper-auditor-reserves.c
 * @brief audits the reserves of an exchange database
 * @author Christian Grothoff
 */
#include "platform.h"
#include 
#include "taler_auditordb_plugin.h"
#include "taler_exchangedb_lib.h"
#include "taler_json_lib.h"
#include "taler_bank_service.h"
#include "taler_signatures.h"
#include "report-lib.h"
/**
 * Use a 1 day grace period to deal with clocks not being perfectly synchronized.
 */
#define CLOSING_GRACE_PERIOD GNUNET_TIME_UNIT_DAYS
/**
 * Return value from main().
 */
static int global_ret;
/**
 * After how long should idle reserves be closed?
 */
static struct GNUNET_TIME_Relative idle_reserve_expiration_time;
/**
 * Checkpointing our progress for reserves.
 */
static struct TALER_AUDITORDB_ProgressPointReserve ppr;
/**
 * Checkpointing our progress for reserves.
 */
static struct TALER_AUDITORDB_ProgressPointReserve ppr_start;
/**
 * Array of reports about row inconsitencies.
 */
static json_t *report_row_inconsistencies;
/**
 * Array of reports about the denomination key not being
 * valid at the time of withdrawal.
 */
static json_t *denomination_key_validity_withdraw_inconsistencies;
/**
 * Array of reports about reserve balance insufficient inconsitencies.
 */
static json_t *report_reserve_balance_insufficient_inconsistencies;
/**
 * Total amount reserves were charged beyond their balance.
 */
static struct TALER_Amount total_balance_insufficient_loss;
/**
 * Array of reports about reserve balance summary wrong in database.
 */
static json_t *report_reserve_balance_summary_wrong_inconsistencies;
/**
 * Total delta between expected and stored reserve balance summaries,
 * for positive deltas.
 */
static struct TALER_Amount total_balance_summary_delta_plus;
/**
 * Total delta between expected and stored reserve balance summaries,
 * for negative deltas.
 */
static struct TALER_Amount total_balance_summary_delta_minus;
/**
 * Array of reports about reserve's not being closed inconsitencies.
 */
static json_t *report_reserve_not_closed_inconsistencies;
/**
 * Total amount affected by reserves not having been closed on time.
 */
static struct TALER_Amount total_balance_reserve_not_closed;
/**
 * Report about amount calculation differences (causing profit
 * or loss at the exchange).
 */
static json_t *report_amount_arithmetic_inconsistencies;
/**
 * Profits the exchange made by bad amount calculations.
 */
static struct TALER_Amount total_arithmetic_delta_plus;
/**
 * Losses the exchange made by bad amount calculations.
 */
static struct TALER_Amount total_arithmetic_delta_minus;
/**
 * Expected balance in the escrow account.
 */
static struct TALER_Amount total_escrow_balance;
/**
 * Recoups we made on denominations that were not revoked (!?).
 */
static struct TALER_Amount total_irregular_recoups;
/**
 * Total withdraw fees earned.
 */
static struct TALER_Amount total_withdraw_fee_income;
/**
 * Array of reports about coin operations with bad signatures.
 */
static json_t *report_bad_sig_losses;
/**
 * Total amount lost by operations for which signatures were invalid.
 */
static struct TALER_Amount total_bad_sig_loss;
/* ***************************** Report logic **************************** */
/**
 * Report a (serious) inconsistency in the exchange's database with
 * respect to calculations involving amounts.
 *
 * @param operation what operation had the inconsistency
 * @param rowid affected row, UINT64_MAX if row is missing
 * @param exchange amount calculated by exchange
 * @param auditor amount calculated by auditor
 * @param profitable 1 if @a exchange being larger than @a auditor is
 *           profitable for the exchange for this operation,
 *           -1 if @a exchange being smaller than @a auditor is
 *           profitable for the exchange, and 0 if it is unclear
 */
static void
report_amount_arithmetic_inconsistency (const char *operation,
                                        uint64_t rowid,
                                        const struct
                                        TALER_Amount *exchange,
                                        const struct
                                        TALER_Amount *auditor,
                                        int profitable)
{
  struct TALER_Amount delta;
  struct TALER_Amount *target;
  if (0 < TALER_amount_cmp (exchange,
                            auditor))
  {
    /* exchange > auditor */
    GNUNET_break (GNUNET_OK ==
                  TALER_amount_subtract (&delta,
                                         exchange,
                                         auditor));
  }
  else
  {
    /* auditor < exchange */
    profitable = -profitable;
    GNUNET_break (GNUNET_OK ==
                  TALER_amount_subtract (&delta,
                                         auditor,
                                         exchange));
  }
  TALER_ARL_report (report_amount_arithmetic_inconsistencies,
                    json_pack ("{s:s, s:I, s:o, s:o, s:I}",
                               "operation", operation,
                               "rowid", (json_int_t) rowid,
                               "exchange", TALER_JSON_from_amount (exchange),
                               "auditor", TALER_JSON_from_amount (auditor),
                               "profitable", (json_int_t) profitable));
  if (0 != profitable)
  {
    target = (1 == profitable)
             ? &total_arithmetic_delta_plus
             : &total_arithmetic_delta_minus;
    GNUNET_break (GNUNET_OK ==
                  TALER_amount_add (target,
                                    target,
                                    &delta));
  }
}
/**
 * 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)
{
  TALER_ARL_report (report_row_inconsistencies,
                    json_pack ("{s:s, s:I, s:s}",
                               "table", table,
                               "row", (json_int_t) rowid,
                               "diagnostic", diagnostic));
}
/* ***************************** Analyze reserves ************************ */
/* This logic checks the reserves_in, reserves_out and reserves-tables */
/**
 * Summary data we keep per reserve.
 */
struct ReserveSummary
{
  /**
   * Public key of the reserve.
   * Always set when the struct is first initialized.
   */
  struct TALER_ReservePublicKeyP reserve_pub;
  /**
   * Sum of all incoming transfers during this transaction.
   * Updated only in #handle_reserve_in().
   */
  struct TALER_Amount total_in;
  /**
   * Sum of all outgoing transfers during this transaction (includes fees).
   * Updated only in #handle_reserve_out().
   */
  struct TALER_Amount total_out;
  /**
   * Sum of withdraw fees encountered during this transaction.
   */
  struct TALER_Amount total_fee;
  /**
   * Previous balance of the reserve as remembered by the auditor.
   * (updated based on @e total_in and @e total_out at the end).
   */
  struct TALER_Amount a_balance;
  /**
   * Previous withdraw fee balance of the reserve, as remembered by the auditor.
   * (updated based on @e total_fee at the end).
   */
  struct TALER_Amount a_withdraw_fee_balance;
  /**
   * Previous reserve expiration data, as remembered by the auditor.
   * (updated on-the-fly in #handle_reserve_in()).
   */
  struct GNUNET_TIME_Absolute a_expiration_date;
  /**
   * Which account did originally put money into the reserve?
   */
  char *sender_account;
  /**
   * Did we have a previous reserve info?  Used to decide between
   * UPDATE and INSERT later.  Initialized in
   * #load_auditor_reserve_summary() together with the a-* values
   * (if available).
   */
  int had_ri;
};
/**
 * Load the auditor's remembered state about the reserve into @a rs.
 * The "total_in" and "total_out" amounts of @a rs must already be
 * initialized (so we can determine the currency).
 *
 * @param[in,out] rs reserve summary to (fully) initialize
 * @return transaction status code
 */
static enum GNUNET_DB_QueryStatus
load_auditor_reserve_summary (struct ReserveSummary *rs)
{
  enum GNUNET_DB_QueryStatus qs;
  uint64_t rowid;
  qs = TALER_ARL_adb->get_reserve_info (TALER_ARL_adb->cls,
                                        TALER_ARL_asession,
                                        &rs->reserve_pub,
                                        &TALER_ARL_master_pub,
                                        &rowid,
                                        &rs->a_balance,
                                        &rs->a_withdraw_fee_balance,
                                        &rs->a_expiration_date,
                                        &rs->sender_account);
  if (0 > qs)
  {
    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    return qs;
  }
  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
  {
    rs->had_ri = GNUNET_NO;
    GNUNET_assert (GNUNET_OK ==
                   TALER_amount_get_zero (rs->total_in.currency,
                                          &rs->a_balance));
    GNUNET_assert (GNUNET_OK ==
                   TALER_amount_get_zero (rs->total_in.currency,
                                          &rs->a_withdraw_fee_balance));
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "Creating fresh reserve `%s' with starting balance %s\n",
                TALER_B2S (&rs->reserve_pub),
                TALER_amount2s (&rs->a_balance));
    return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
  }
  rs->had_ri = GNUNET_YES;
  if ( (GNUNET_YES !=
        TALER_amount_cmp_currency (&rs->a_balance,
                                   &rs->a_withdraw_fee_balance)) ||
       (GNUNET_YES !=
        TALER_amount_cmp_currency (&rs->total_in,
                                   &rs->a_balance)) )
  {
    GNUNET_break (0);
    return GNUNET_DB_STATUS_HARD_ERROR;
  }
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Auditor remembers reserve `%s' has balance %s\n",
              TALER_B2S (&rs->reserve_pub),
              TALER_amount2s (&rs->a_balance));
  return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
}
/**
 * Closure to the various callbacks we make while checking a reserve.
 */
struct ReserveContext
{
  /**
   * Map from hash of reserve's public key to a `struct ReserveSummary`.
   */
  struct GNUNET_CONTAINER_MultiHashMap *reserves;
  /**
   * Map from hash of denomination's public key to a
   * static string "revoked" for keys that have been revoked,
   * or "master signature invalid" in case the revocation is
   * there but bogus.
   */
  struct GNUNET_CONTAINER_MultiHashMap *revoked;
  /**
   * Transaction status code, set to error codes if applicable.
   */
  enum GNUNET_DB_QueryStatus qs;
};
/**
 * Function called with details about incoming wire transfers.
 *
 * @param cls our `struct ReserveContext`
 * @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 reference identifying the wire transfer
 * @param execution_date when did we receive the funds
 * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
 */
static int
handle_reserve_in (void *cls,
                   uint64_t rowid,
                   const struct TALER_ReservePublicKeyP *reserve_pub,
                   const struct TALER_Amount *credit,
                   const char *sender_account_details,
                   uint64_t wire_reference,
                   struct GNUNET_TIME_Absolute execution_date)
{
  struct ReserveContext *rc = cls;
  struct GNUNET_HashCode key;
  struct ReserveSummary *rs;
  struct GNUNET_TIME_Absolute expiry;
  enum GNUNET_DB_QueryStatus qs;
  (void) wire_reference;
  /* should be monotonically increasing */
  GNUNET_assert (rowid >= ppr.last_reserve_in_serial_id);
  ppr.last_reserve_in_serial_id = rowid + 1;
  GNUNET_CRYPTO_hash (reserve_pub,
                      sizeof (*reserve_pub),
                      &key);
  rs = GNUNET_CONTAINER_multihashmap_get (rc->reserves,
                                          &key);
  if (NULL == rs)
  {
    rs = GNUNET_new (struct ReserveSummary);
    rs->sender_account = GNUNET_strdup (sender_account_details);
    rs->reserve_pub = *reserve_pub;
    rs->total_in = *credit;
    GNUNET_assert (GNUNET_OK ==
                   TALER_amount_get_zero (credit->currency,
                                          &rs->total_out));
    GNUNET_assert (GNUNET_OK ==
                   TALER_amount_get_zero (credit->currency,
                                          &rs->total_fee));
    if (0 > (qs = load_auditor_reserve_summary (rs)))
    {
      GNUNET_break (0);
      GNUNET_free (rs);
      rc->qs = qs;
      return GNUNET_SYSERR;
    }
    GNUNET_assert (GNUNET_OK ==
                   GNUNET_CONTAINER_multihashmap_put (rc->reserves,
                                                      &key,
                                                      rs,
                                                      GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
  }
  else
  {
    GNUNET_assert (GNUNET_OK ==
                   TALER_amount_add (&rs->total_in,
                                     &rs->total_in,
                                     credit));
    if (NULL == rs->sender_account)
      rs->sender_account = GNUNET_strdup (sender_account_details);
  }
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Additional incoming wire transfer for reserve `%s' of %s\n",
              TALER_B2S (reserve_pub),
              TALER_amount2s (credit));
  expiry = GNUNET_TIME_absolute_add (execution_date,
                                     idle_reserve_expiration_time);
  rs->a_expiration_date = GNUNET_TIME_absolute_max (rs->a_expiration_date,
                                                    expiry);
  return GNUNET_OK;
}
/**
 * Function called with details about withdraw operations.  Verifies
 * the signature and updates the reserve's balance.
 *
 * @param cls our `struct ReserveContext`
 * @param rowid unique serial ID for the refresh session in our DB
 * @param h_blind_ev blinded hash of the coin's public key
 * @param denom_pub public denomination key of the deposited coin
 * @param reserve_pub public key of the reserve
 * @param reserve_sig signature over the withdraw operation
 * @param execution_date when did the wallet withdraw the coin
 * @param amount_with_fee amount that was withdrawn
 * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
 */
static int
handle_reserve_out (void *cls,
                    uint64_t rowid,
                    const struct GNUNET_HashCode *h_blind_ev,
                    const struct TALER_DenominationPublicKey *denom_pub,
                    const struct TALER_ReservePublicKeyP *reserve_pub,
                    const struct TALER_ReserveSignatureP *reserve_sig,
                    struct GNUNET_TIME_Absolute execution_date,
                    const struct TALER_Amount *amount_with_fee)
{
  struct ReserveContext *rc = cls;
  struct TALER_WithdrawRequestPS wsrd;
  struct GNUNET_HashCode key;
  struct ReserveSummary *rs;
  const struct TALER_DenominationKeyValidityPS *issue;
  struct TALER_Amount withdraw_fee;
  struct GNUNET_TIME_Absolute valid_start;
  struct GNUNET_TIME_Absolute expire_withdraw;
  enum GNUNET_DB_QueryStatus qs;
  /* should be monotonically increasing */
  GNUNET_assert (rowid >= ppr.last_reserve_out_serial_id);
  ppr.last_reserve_out_serial_id = rowid + 1;
  /* lookup denomination pub data (make sure denom_pub is valid, establish fees) */
  qs = TALER_ARL_get_denomination_info (denom_pub,
                                        &issue,
                                        &wsrd.h_denomination_pub);
  if (0 > qs)
  {
    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    if (GNUNET_DB_STATUS_HARD_ERROR == qs)
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Hard database error trying to get denomination %s (%s) from database!\n",
                  TALER_B2S (denom_pub),
                  TALER_amount2s (amount_with_fee));
    rc->qs = qs;
    return GNUNET_SYSERR;
  }
  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
  {
    report_row_inconsistency ("withdraw",
                              rowid,
                              "denomination key not found");
    return GNUNET_OK;
  }
  /* check that execution date is within withdraw range for denom_pub  */
  valid_start = GNUNET_TIME_absolute_ntoh (issue->start);
  expire_withdraw = GNUNET_TIME_absolute_ntoh (issue->expire_withdraw);
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Checking withdraw timing: %llu, expire: %llu, timing: %llu\n",
              (unsigned long long) valid_start.abs_value_us,
              (unsigned long long) expire_withdraw.abs_value_us,
              (unsigned long long) execution_date.abs_value_us);
  if ( (valid_start.abs_value_us > execution_date.abs_value_us) ||
       (expire_withdraw.abs_value_us < execution_date.abs_value_us) )
  {
    TALER_ARL_report (denomination_key_validity_withdraw_inconsistencies,
                      json_pack ("{s:I, s:o, s:o, s:o}",
                                 "row", (json_int_t) rowid,
                                 "execution_date",
                                 TALER_ARL_json_from_time_abs (execution_date),
                                 "reserve_pub", GNUNET_JSON_from_data_auto (
                                   reserve_pub),
                                 "denompub_h", GNUNET_JSON_from_data_auto (
                                   &wsrd.h_denomination_pub)));
  }
  /* check reserve_sig */
  wsrd.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW);
  wsrd.purpose.size = htonl (sizeof (wsrd));
  wsrd.reserve_pub = *reserve_pub;
  TALER_amount_hton (&wsrd.amount_with_fee,
                     amount_with_fee);
  wsrd.withdraw_fee = issue->fee_withdraw;
  wsrd.h_coin_envelope = *h_blind_ev;
  if (GNUNET_OK !=
      GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW,
                                  &wsrd.purpose,
                                  &reserve_sig->eddsa_signature,
                                  &reserve_pub->eddsa_pub))
  {
    TALER_ARL_report (report_bad_sig_losses,
                      json_pack ("{s:s, s:I, s:o, s:o}",
                                 "operation", "withdraw",
                                 "row", (json_int_t) rowid,
                                 "loss", TALER_JSON_from_amount (
                                   amount_with_fee),
                                 "key_pub", GNUNET_JSON_from_data_auto (
                                   reserve_pub)));
    GNUNET_break (GNUNET_OK ==
                  TALER_amount_add (&total_bad_sig_loss,
                                    &total_bad_sig_loss,
                                    amount_with_fee));
    return GNUNET_OK;
  }
  GNUNET_CRYPTO_hash (reserve_pub,
                      sizeof (*reserve_pub),
                      &key);
  rs = GNUNET_CONTAINER_multihashmap_get (rc->reserves,
                                          &key);
  if (NULL == rs)
  {
    rs = GNUNET_new (struct ReserveSummary);
    rs->reserve_pub = *reserve_pub;
    rs->total_out = *amount_with_fee;
    GNUNET_assert (GNUNET_OK ==
                   TALER_amount_get_zero (amount_with_fee->currency,
                                          &rs->total_in));
    GNUNET_assert (GNUNET_OK ==
                   TALER_amount_get_zero (amount_with_fee->currency,
                                          &rs->total_fee));
    qs = load_auditor_reserve_summary (rs);
    if (0 > qs)
    {
      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
      GNUNET_free (rs);
      rc->qs = qs;
      return GNUNET_SYSERR;
    }
    GNUNET_assert (GNUNET_OK ==
                   GNUNET_CONTAINER_multihashmap_put (rc->reserves,
                                                      &key,
                                                      rs,
                                                      GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
  }
  else
  {
    GNUNET_assert (GNUNET_OK ==
                   TALER_amount_add (&rs->total_out,
                                     &rs->total_out,
                                     amount_with_fee));
  }
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Reserve `%s' reduced by %s from withdraw\n",
              TALER_B2S (reserve_pub),
              TALER_amount2s (amount_with_fee));
  TALER_amount_ntoh (&withdraw_fee,
                     &issue->fee_withdraw);
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Increasing withdraw profits by fee %s\n",
              TALER_amount2s (&withdraw_fee));
  GNUNET_assert (GNUNET_OK ==
                 TALER_amount_add (&rs->total_fee,
                                   &rs->total_fee,
                                   &withdraw_fee));
  return GNUNET_OK;
}
/**
 * Function called with details about withdraw operations.  Verifies
 * the signature and updates the reserve's balance.
 *
 * @param cls our `struct ReserveContext`
 * @param rowid unique serial ID for the refresh session in our DB
 * @param timestamp when did we receive the recoup request
 * @param amount how much should be added back to the reserve
 * @param reserve_pub public key of the reserve
 * @param coin public information about the coin, denomination signature is
 *        already verified in #check_recoup()
 * @param denom_pub public key of the denomionation of @a coin
 * @param coin_sig signature with @e coin_pub of type #TALER_SIGNATURE_WALLET_COIN_RECOUP
 * @param coin_blind blinding factor used to blind the coin
 * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
 */
static int
handle_recoup_by_reserve (void *cls,
                          uint64_t rowid,
                          struct GNUNET_TIME_Absolute timestamp,
                          const struct TALER_Amount *amount,
                          const struct TALER_ReservePublicKeyP *reserve_pub,
                          const struct TALER_CoinPublicInfo *coin,
                          const struct TALER_DenominationPublicKey *denom_pub,
                          const struct TALER_CoinSpendSignatureP *coin_sig,
                          const struct
                          TALER_DenominationBlindingKeyP *coin_blind)
{
  struct ReserveContext *rc = cls;
  struct GNUNET_HashCode key;
  struct ReserveSummary *rs;
  struct GNUNET_TIME_Absolute expiry;
  struct TALER_RecoupRequestPS pr;
  struct TALER_MasterSignatureP msig;
  uint64_t rev_rowid;
  enum GNUNET_DB_QueryStatus qs;
  const char *rev;
  (void) denom_pub;
  /* should be monotonically increasing */
  GNUNET_assert (rowid >= ppr.last_reserve_recoup_serial_id);
  ppr.last_reserve_recoup_serial_id = rowid + 1;
  /* We know that denom_pub matches denom_pub_hash because this
     is how the SQL statement joined the tables. */
  pr.h_denom_pub = coin->denom_pub_hash;
  pr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_RECOUP);
  pr.purpose.size = htonl (sizeof (pr));
  pr.coin_pub = coin->coin_pub;
  pr.coin_blind = *coin_blind;
  if (GNUNET_OK !=
      GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_RECOUP,
                                  &pr.purpose,
                                  &coin_sig->eddsa_signature,
                                  &coin->coin_pub.eddsa_pub))
  {
    TALER_ARL_report (report_bad_sig_losses,
                      json_pack ("{s:s, s:I, s:o, s:o}",
                                 "operation", "recoup",
                                 "row", (json_int_t) rowid,
                                 "loss", TALER_JSON_from_amount (amount),
                                 "key_pub", GNUNET_JSON_from_data_auto (
                                   &coin->coin_pub)));
    GNUNET_break (GNUNET_OK ==
                  TALER_amount_add (&total_bad_sig_loss,
                                    &total_bad_sig_loss,
                                    amount));
  }
  /* check that the coin was eligible for recoup!*/
  rev = GNUNET_CONTAINER_multihashmap_get (rc->revoked,
                                           &pr.h_denom_pub);
  if (NULL == rev)
  {
    qs = TALER_ARL_edb->get_denomination_revocation (TALER_ARL_edb->cls,
                                                     TALER_ARL_esession,
                                                     &pr.h_denom_pub,
                                                     &msig,
                                                     &rev_rowid);
    if (0 > qs)
    {
      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
      rc->qs = qs;
      return GNUNET_SYSERR;
    }
    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    {
      report_row_inconsistency ("recoup",
                                rowid,
                                "denomination key not in revocation set");
      GNUNET_break (GNUNET_OK ==
                    TALER_amount_add (&total_irregular_recoups,
                                      &total_irregular_recoups,
                                      amount));
    }
    else
    {
      /* verify msig */
      struct TALER_MasterDenominationKeyRevocationPS kr;
      kr.purpose.purpose = htonl (
        TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED);
      kr.purpose.size = htonl (sizeof (kr));
      kr.h_denom_pub = pr.h_denom_pub;
      if (GNUNET_OK !=
          GNUNET_CRYPTO_eddsa_verify (
            TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED,
            &kr.purpose,
            &msig.eddsa_signature,
            &TALER_ARL_master_pub.eddsa_pub))
      {
        rev = "master signature invalid";
      }
      else
      {
        rev = "revoked";
      }
      GNUNET_assert (GNUNET_OK ==
                     GNUNET_CONTAINER_multihashmap_put (rc->revoked,
                                                        &pr.h_denom_pub,
                                                        (void *) rev,
                                                        GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
    }
  }
  else
  {
    rev_rowid = 0; /* reported elsewhere */
  }
  if ( (NULL != rev) &&
       (0 == strcmp (rev, "master signature invalid")) )
  {
    TALER_ARL_report (report_bad_sig_losses,
                      json_pack ("{s:s, s:I, s:o, s:o}",
                                 "operation", "recoup-master",
                                 "row", (json_int_t) rev_rowid,
                                 "loss", TALER_JSON_from_amount (amount),
                                 "key_pub", GNUNET_JSON_from_data_auto (
                                   &TALER_ARL_master_pub)));
    GNUNET_break (GNUNET_OK ==
                  TALER_amount_add (&total_bad_sig_loss,
                                    &total_bad_sig_loss,
                                    amount));
  }
  GNUNET_CRYPTO_hash (reserve_pub,
                      sizeof (*reserve_pub),
                      &key);
  rs = GNUNET_CONTAINER_multihashmap_get (rc->reserves,
                                          &key);
  if (NULL == rs)
  {
    rs = GNUNET_new (struct ReserveSummary);
    rs->reserve_pub = *reserve_pub;
    rs->total_in = *amount;
    GNUNET_assert (GNUNET_OK ==
                   TALER_amount_get_zero (amount->currency,
                                          &rs->total_out));
    GNUNET_assert (GNUNET_OK ==
                   TALER_amount_get_zero (amount->currency,
                                          &rs->total_fee));
    qs = load_auditor_reserve_summary (rs);
    if (0 > qs)
    {
      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
      GNUNET_free (rs);
      rc->qs = qs;
      return GNUNET_SYSERR;
    }
    GNUNET_assert (GNUNET_OK ==
                   GNUNET_CONTAINER_multihashmap_put (rc->reserves,
                                                      &key,
                                                      rs,
                                                      GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
  }
  else
  {
    GNUNET_assert (GNUNET_OK ==
                   TALER_amount_add (&rs->total_in,
                                     &rs->total_in,
                                     amount));
  }
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Additional /recoup value to for reserve `%s' of %s\n",
              TALER_B2S (reserve_pub),
              TALER_amount2s (amount));
  expiry = GNUNET_TIME_absolute_add (timestamp,
                                     idle_reserve_expiration_time);
  rs->a_expiration_date = GNUNET_TIME_absolute_max (rs->a_expiration_date,
                                                    expiry);
  return GNUNET_OK;
}
/**
 * Obtain the closing fee for a transfer at @a time for target
 * @a receiver_account.
 *
 * @param receiver_account payto:// URI of the target account
 * @param atime when was the transfer made
 * @param[out] fee set to the closing fee
 * @return #GNUNET_OK on success
 */
static int
get_closing_fee (const char *receiver_account,
                 struct GNUNET_TIME_Absolute atime,
                 struct TALER_Amount *fee)
{
  struct TALER_MasterSignatureP master_sig;
  struct GNUNET_TIME_Absolute start_date;
  struct GNUNET_TIME_Absolute end_date;
  struct TALER_Amount wire_fee;
  char *method;
  method = TALER_payto_get_method (receiver_account);
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Method is `%s'\n",
              method);
  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
      TALER_ARL_edb->get_wire_fee (TALER_ARL_edb->cls,
                                   TALER_ARL_esession,
                                   method,
                                   atime,
                                   &start_date,
                                   &end_date,
                                   &wire_fee,
                                   fee,
                                   &master_sig))
  {
    report_row_inconsistency ("closing-fee",
                              atime.abs_value_us,
                              "closing fee unavailable at given time");
    GNUNET_free (method);
    return GNUNET_SYSERR;
  }
  GNUNET_free (method);
  return GNUNET_OK;
}
/**
 * Function called about reserve closing operations
 * the aggregator triggered.
 *
 * @param cls closure
 * @param rowid row identifier used to uniquely identify the reserve closing operation
 * @param execution_date when did we execute the close operation
 * @param amount_with_fee how much did we debit the reserve
 * @param closing_fee how much did we charge for closing the reserve
 * @param reserve_pub public key of the reserve
 * @param receiver_account where did we send the funds
 * @param transfer_details details about the wire transfer
 * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
 */
static int
handle_reserve_closed (void *cls,
                       uint64_t rowid,
                       struct GNUNET_TIME_Absolute execution_date,
                       const struct TALER_Amount *amount_with_fee,
                       const struct TALER_Amount *closing_fee,
                       const struct TALER_ReservePublicKeyP *reserve_pub,
                       const char *receiver_account,
                       const struct
                       TALER_WireTransferIdentifierRawP *transfer_details)
{
  struct ReserveContext *rc = cls;
  struct GNUNET_HashCode key;
  struct ReserveSummary *rs;
  enum GNUNET_DB_QueryStatus qs;
  (void) transfer_details;
  /* should be monotonically increasing */
  GNUNET_assert (rowid >= ppr.last_reserve_close_serial_id);
  ppr.last_reserve_close_serial_id = rowid + 1;
  GNUNET_CRYPTO_hash (reserve_pub,
                      sizeof (*reserve_pub),
                      &key);
  rs = GNUNET_CONTAINER_multihashmap_get (rc->reserves,
                                          &key);
  if (NULL == rs)
  {
    rs = GNUNET_new (struct ReserveSummary);
    rs->reserve_pub = *reserve_pub;
    rs->total_out = *amount_with_fee;
    rs->total_fee = *closing_fee;
    GNUNET_assert (GNUNET_OK ==
                   TALER_amount_get_zero (amount_with_fee->currency,
                                          &rs->total_in));
    qs = load_auditor_reserve_summary (rs);
    if (0 > qs)
    {
      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
      GNUNET_free (rs);
      rc->qs = qs;
      return GNUNET_SYSERR;
    }
    GNUNET_assert (GNUNET_OK ==
                   GNUNET_CONTAINER_multihashmap_put (rc->reserves,
                                                      &key,
                                                      rs,
                                                      GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
  }
  else
  {
    struct TALER_Amount expected_fee;
    GNUNET_assert (GNUNET_OK ==
                   TALER_amount_add (&rs->total_out,
                                     &rs->total_out,
                                     amount_with_fee));
    GNUNET_assert (GNUNET_OK ==
                   TALER_amount_add (&rs->total_fee,
                                     &rs->total_fee,
                                     closing_fee));
    /* verify closing_fee is correct! */
    if (GNUNET_OK !=
        get_closing_fee (receiver_account,
                         execution_date,
                         &expected_fee))
    {
      GNUNET_break (0);
    }
    else if (0 != TALER_amount_cmp (&expected_fee,
                                    closing_fee))
    {
      report_amount_arithmetic_inconsistency (
        "closing aggregation fee",
        rowid,
        closing_fee,
        &expected_fee,
        1);
    }
  }
  if (NULL == rs->sender_account)
  {
    GNUNET_break (GNUNET_NO == rs->had_ri);
    report_row_inconsistency ("reserves_close",
                              rowid,
                              "target account not verified, auditor does not know reserve");
  }
  else if (0 != strcmp (rs->sender_account,
                        receiver_account))
  {
    report_row_inconsistency ("reserves_close",
                              rowid,
                              "target account does not match origin account");
  }
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Additional closing operation for reserve `%s' of %s\n",
              TALER_B2S (reserve_pub),
              TALER_amount2s (amount_with_fee));
  return GNUNET_OK;
}
/**
 * Check that the reserve summary matches what the exchange database
 * thinks about the reserve, and update our own state of the reserve.
 *
 * Remove all reserves that we are happy with from the DB.
 *
 * @param cls our `struct ReserveContext`
 * @param key hash of the reserve public key
 * @param value a `struct ReserveSummary`
 * @return #GNUNET_OK to process more entries
 */
static int
verify_reserve_balance (void *cls,
                        const struct GNUNET_HashCode *key,
                        void *value)
{
  struct ReserveContext *rc = cls;
  struct ReserveSummary *rs = value;
  struct TALER_EXCHANGEDB_Reserve reserve;
  struct TALER_Amount balance;
  struct TALER_Amount nbalance;
  struct TALER_Amount cfee;
  enum GNUNET_DB_QueryStatus qs;
  int ret;
  ret = GNUNET_OK;
  reserve.pub = rs->reserve_pub;
  qs = TALER_ARL_edb->reserves_get (TALER_ARL_edb->cls,
                                    TALER_ARL_esession,
                                    &reserve);
  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
  {
    char *diag;
    GNUNET_asprintf (&diag,
                     "Failed to find summary for reserve `%s'\n",
                     TALER_B2S (&rs->reserve_pub));
    report_row_inconsistency ("reserve-summary",
                              UINT64_MAX,
                              diag);
    GNUNET_free (diag);
    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    {
      GNUNET_break (0);
      qs = GNUNET_DB_STATUS_HARD_ERROR;
    }
    rc->qs = qs;
    return GNUNET_OK;
  }
  if (GNUNET_OK !=
      TALER_amount_add (&balance,
                        &rs->total_in,
                        &rs->a_balance))
  {
    GNUNET_break (0);
    goto cleanup;
  }
  if (GNUNET_SYSERR ==
      TALER_amount_subtract (&nbalance,
                             &balance,
                             &rs->total_out))
  {
    struct TALER_Amount loss;
    GNUNET_break (GNUNET_SYSERR !=
                  TALER_amount_subtract (&loss,
                                         &rs->total_out,
                                         &balance));
    GNUNET_break (GNUNET_OK ==
                  TALER_amount_add (&total_balance_insufficient_loss,
                                    &total_balance_insufficient_loss,
                                    &loss));
    TALER_ARL_report (
      report_reserve_balance_insufficient_inconsistencies,
      json_pack ("{s:o, s:o}",
                 "reserve_pub",
                 GNUNET_JSON_from_data_auto (&rs->reserve_pub),
                 "loss",
                 TALER_JSON_from_amount (&loss)));
    goto cleanup;
  }
  if (0 != TALER_amount_cmp (&nbalance,
                             &reserve.balance))
  {
    struct TALER_Amount delta;
    if (0 < TALER_amount_cmp (&nbalance,
                              &reserve.balance))
    {
      /* balance > reserve.balance */
      GNUNET_assert (GNUNET_OK ==
                     TALER_amount_subtract (&delta,
                                            &nbalance,
                                            &reserve.balance));
      GNUNET_assert (GNUNET_OK ==
                     TALER_amount_add (&total_balance_summary_delta_plus,
                                       &total_balance_summary_delta_plus,
                                       &delta));
    }
    else
    {
      /* balance < reserve.balance */
      GNUNET_assert (GNUNET_OK ==
                     TALER_amount_subtract (&delta,
                                            &reserve.balance,
                                            &nbalance));
      GNUNET_assert (GNUNET_OK ==
                     TALER_amount_add (&total_balance_summary_delta_minus,
                                       &total_balance_summary_delta_minus,
                                       &delta));
    }
    TALER_ARL_report (
      report_reserve_balance_summary_wrong_inconsistencies,
      json_pack ("{s:o, s:o, s:o}",
                 "reserve_pub",
                 GNUNET_JSON_from_data_auto (&rs->reserve_pub),
                 "exchange",
                 TALER_JSON_from_amount (&reserve.balance),
                 "auditor",
                 TALER_JSON_from_amount (&nbalance)));
    goto cleanup;
  }
  /* Check that reserve is being closed if it is past its expiration date */
  if (CLOSING_GRACE_PERIOD.rel_value_us <
      GNUNET_TIME_absolute_get_duration (rs->a_expiration_date).rel_value_us)
  {
    if ( (NULL != rs->sender_account) &&
         (GNUNET_OK ==
          get_closing_fee (rs->sender_account,
                           rs->a_expiration_date,
                           &cfee)) )
    {
      if (1 == TALER_amount_cmp (&nbalance,
                                 &cfee))
      {
        GNUNET_assert (GNUNET_OK ==
                       TALER_amount_add (&total_balance_reserve_not_closed,
                                         &total_balance_reserve_not_closed,
                                         &nbalance));
        TALER_ARL_report (report_reserve_not_closed_inconsistencies,
                          json_pack ("{s:o, s:o, s:o}",
                                     "reserve_pub",
                                     GNUNET_JSON_from_data_auto (
                                       &rs->reserve_pub),
                                     "balance",
                                     TALER_JSON_from_amount (&nbalance),
                                     "expiration_time",
                                     TALER_ARL_json_from_time_abs (
                                       rs->a_expiration_date)));
      }
    }
    else
    {
      GNUNET_assert (GNUNET_OK ==
                     TALER_amount_add (&total_balance_reserve_not_closed,
                                       &total_balance_reserve_not_closed,
                                       &nbalance));
      TALER_ARL_report (report_reserve_not_closed_inconsistencies,
                        json_pack ("{s:o, s:o, s:o, s:s}",
                                   "reserve_pub",
                                   GNUNET_JSON_from_data_auto (
                                     &rs->reserve_pub),
                                   "balance",
                                   TALER_JSON_from_amount (&nbalance),
                                   "expiration_time",
                                   TALER_ARL_json_from_time_abs (
                                     rs->a_expiration_date),
                                   "diagnostic",
                                   "could not determine closing fee"));
    }
  }
  /* Add withdraw fees we encountered to totals */
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Reserve reserve `%s' made %s in withdraw fees\n",
              TALER_B2S (&rs->reserve_pub),
              TALER_amount2s (&rs->total_fee));
  if (GNUNET_YES !=
      TALER_amount_add (&rs->a_withdraw_fee_balance,
                        &rs->a_withdraw_fee_balance,
                        &rs->total_fee))
  {
    GNUNET_break (0);
    ret = GNUNET_SYSERR;
    goto cleanup;
  }
  if ( (GNUNET_YES !=
        TALER_amount_add (&total_escrow_balance,
                          &total_escrow_balance,
                          &rs->total_in)) ||
       (GNUNET_SYSERR ==
        TALER_amount_subtract (&total_escrow_balance,
                               &total_escrow_balance,
                               &rs->total_out)) ||
       (GNUNET_YES !=
        TALER_amount_add (&total_withdraw_fee_income,
                          &total_withdraw_fee_income,
                          &rs->total_fee)) )
  {
    GNUNET_break (0);
    ret = GNUNET_SYSERR;
    goto cleanup;
  }
  if ( (0ULL == balance.value) &&
       (0U == balance.fraction) )
  {
    /* balance is zero, drop reserve details (and then do not update/insert) */
    if (rs->had_ri)
    {
      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "Final balance of reserve `%s' is %s, dropping it\n",
                  TALER_B2S (&rs->reserve_pub),
                  TALER_amount2s (&nbalance));
      qs = TALER_ARL_adb->del_reserve_info (TALER_ARL_adb->cls,
                                            TALER_ARL_asession,
                                            &rs->reserve_pub,
                                            &TALER_ARL_master_pub);
      if (0 >= qs)
      {
        GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
        ret = GNUNET_SYSERR;
        rc->qs = qs;
        goto cleanup;
      }
    }
    else
    {
      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "Final balance of reserve `%s' is %s, no need to remember it\n",
                  TALER_B2S (&rs->reserve_pub),
                  TALER_amount2s (&nbalance));
    }
    ret = GNUNET_OK;
    goto cleanup;
  }
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Remembering final balance of reserve `%s' as %s\n",
              TALER_B2S (&rs->reserve_pub),
              TALER_amount2s (&nbalance));
  if (rs->had_ri)
    qs = TALER_ARL_adb->update_reserve_info (TALER_ARL_adb->cls,
                                             TALER_ARL_asession,
                                             &rs->reserve_pub,
                                             &TALER_ARL_master_pub,
                                             &nbalance,
                                             &rs->a_withdraw_fee_balance,
                                             rs->a_expiration_date);
  else
    qs = TALER_ARL_adb->insert_reserve_info (TALER_ARL_adb->cls,
                                             TALER_ARL_asession,
                                             &rs->reserve_pub,
                                             &TALER_ARL_master_pub,
                                             &nbalance,
                                             &rs->a_withdraw_fee_balance,
                                             rs->a_expiration_date,
                                             rs->sender_account);
  if (0 >= qs)
  {
    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    ret = GNUNET_SYSERR;
    rc->qs = qs;
  }
cleanup:
  GNUNET_assert (GNUNET_YES ==
                 GNUNET_CONTAINER_multihashmap_remove (rc->reserves,
                                                       key,
                                                       rs));
  GNUNET_free_non_null (rs->sender_account);
  GNUNET_free (rs);
  return ret;
}
/**
 * Analyze reserves for being well-formed.
 *
 * @param cls NULL
 * @return transaction status code
 */
static enum GNUNET_DB_QueryStatus
analyze_reserves (void *cls)
{
  struct ReserveContext rc;
  enum GNUNET_DB_QueryStatus qsx;
  enum GNUNET_DB_QueryStatus qs;
  enum GNUNET_DB_QueryStatus qsp;
  (void) cls;
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Analyzing reserves\n");
  qsp = TALER_ARL_adb->get_auditor_progress_reserve (TALER_ARL_adb->cls,
                                                     TALER_ARL_asession,
                                                     &TALER_ARL_master_pub,
                                                     &ppr);
  if (0 > qsp)
  {
    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qsp);
    return qsp;
  }
  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qsp)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
                _ (
                  "First analysis using this auditor, starting audit from scratch\n"));
  }
  else
  {
    ppr_start = ppr;
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                _ ("Resuming reserve audit at %llu/%llu/%llu/%llu\n"),
                (unsigned long long) ppr.last_reserve_in_serial_id,
                (unsigned long long) ppr.last_reserve_out_serial_id,
                (unsigned long long) ppr.last_reserve_recoup_serial_id,
                (unsigned long long) ppr.last_reserve_close_serial_id);
  }
  rc.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
  qsx = TALER_ARL_adb->get_reserve_summary (TALER_ARL_adb->cls,
                                            TALER_ARL_asession,
                                            &TALER_ARL_master_pub,
                                            &total_escrow_balance,
                                            &total_withdraw_fee_income);
  if (qsx < 0)
  {
    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qsx);
    return qsx;
  }
  rc.reserves = GNUNET_CONTAINER_multihashmap_create (512,
                                                      GNUNET_NO);
  rc.revoked = GNUNET_CONTAINER_multihashmap_create (4,
                                                     GNUNET_NO);
  qs = TALER_ARL_edb->select_reserves_in_above_serial_id (TALER_ARL_edb->cls,
                                                          TALER_ARL_esession,
                                                          ppr.
                                                          last_reserve_in_serial_id,
                                                          &handle_reserve_in,
                                                          &rc);
  if (qs < 0)
  {
    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    return qs;
  }
  qs = TALER_ARL_edb->select_withdrawals_above_serial_id (TALER_ARL_edb->cls,
                                                          TALER_ARL_esession,
                                                          ppr.
                                                          last_reserve_out_serial_id,
                                                          &handle_reserve_out,
                                                          &rc);
  if (qs < 0)
  {
    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    return qs;
  }
  qs = TALER_ARL_edb->select_recoup_above_serial_id (TALER_ARL_edb->cls,
                                                     TALER_ARL_esession,
                                                     ppr.
                                                     last_reserve_recoup_serial_id,
                                                     &handle_recoup_by_reserve,
                                                     &rc);
  if (qs < 0)
  {
    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    return qs;
  }
  qs = TALER_ARL_edb->select_reserve_closed_above_serial_id (TALER_ARL_edb->cls,
                                                             TALER_ARL_esession,
                                                             ppr.
                                                             last_reserve_close_serial_id,
                                                             &
                                                             handle_reserve_closed,
                                                             &rc);
  if (qs < 0)
  {
    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    return qs;
  }
  GNUNET_CONTAINER_multihashmap_iterate (rc.reserves,
                                         &verify_reserve_balance,
                                         &rc);
  GNUNET_break (0 ==
                GNUNET_CONTAINER_multihashmap_size (rc.reserves));
  GNUNET_CONTAINER_multihashmap_destroy (rc.reserves);
  GNUNET_CONTAINER_multihashmap_destroy (rc.revoked);
  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != rc.qs)
    return qs;
  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qsx)
  {
    qs = TALER_ARL_adb->insert_reserve_summary (TALER_ARL_adb->cls,
                                                TALER_ARL_asession,
                                                &TALER_ARL_master_pub,
                                                &total_escrow_balance,
                                                &total_withdraw_fee_income);
  }
  else
  {
    qs = TALER_ARL_adb->update_reserve_summary (TALER_ARL_adb->cls,
                                                TALER_ARL_asession,
                                                &TALER_ARL_master_pub,
                                                &total_escrow_balance,
                                                &total_withdraw_fee_income);
  }
  if (0 >= qs)
  {
    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    return qs;
  }
  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qsp)
    qs = TALER_ARL_adb->update_auditor_progress_reserve (TALER_ARL_adb->cls,
                                                         TALER_ARL_asession,
                                                         &TALER_ARL_master_pub,
                                                         &ppr);
  else
    qs = TALER_ARL_adb->insert_auditor_progress_reserve (TALER_ARL_adb->cls,
                                                         TALER_ARL_asession,
                                                         &TALER_ARL_master_pub,
                                                         &ppr);
  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 reserve audit step at %llu/%llu/%llu/%llu\n"),
              (unsigned long long) ppr.last_reserve_in_serial_id,
              (unsigned long long) ppr.last_reserve_out_serial_id,
              (unsigned long long) ppr.last_reserve_recoup_serial_id,
              (unsigned long long) ppr.last_reserve_close_serial_id);
  return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
}
/**
 * Main function that will be run.
 *
 * @param cls closure
 * @param args remaining command-line arguments
 * @param TALER_ARL_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 *TALER_ARL_cfgfile,
     const struct GNUNET_CONFIGURATION_Handle *c)
{
  (void) cls;
  (void) args;
  (void) TALER_ARL_cfgfile;
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Launching auditor\n");
  if (GNUNET_OK !=
      TALER_ARL_init (c))
  {
    global_ret = 1;
    return;
  }
  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_time (TALER_ARL_cfg,
                                           "exchangedb",
                                           "IDLE_RESERVE_EXPIRATION_TIME",
                                           &idle_reserve_expiration_time))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                               "exchangedb",
                               "IDLE_RESERVE_EXPIRATION_TIME");
    global_ret = 1;
    return;
  }
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Starting audit\n");
  GNUNET_assert (GNUNET_OK ==
                 TALER_amount_get_zero (TALER_ARL_currency,
                                        &total_escrow_balance));
  GNUNET_assert (GNUNET_OK ==
                 TALER_amount_get_zero (TALER_ARL_currency,
                                        &total_irregular_recoups));
  GNUNET_assert (GNUNET_OK ==
                 TALER_amount_get_zero (TALER_ARL_currency,
                                        &total_withdraw_fee_income));
  GNUNET_assert (GNUNET_OK ==
                 TALER_amount_get_zero (TALER_ARL_currency,
                                        &total_balance_insufficient_loss));
  GNUNET_assert (GNUNET_OK ==
                 TALER_amount_get_zero (TALER_ARL_currency,
                                        &total_balance_summary_delta_plus));
  GNUNET_assert (GNUNET_OK ==
                 TALER_amount_get_zero (TALER_ARL_currency,
                                        &total_balance_summary_delta_minus));
  GNUNET_assert (GNUNET_OK ==
                 TALER_amount_get_zero (TALER_ARL_currency,
                                        &total_arithmetic_delta_plus));
  GNUNET_assert (GNUNET_OK ==
                 TALER_amount_get_zero (TALER_ARL_currency,
                                        &total_arithmetic_delta_minus));
  GNUNET_assert (GNUNET_OK ==
                 TALER_amount_get_zero (TALER_ARL_currency,
                                        &total_balance_reserve_not_closed));
  GNUNET_assert (GNUNET_OK ==
                 TALER_amount_get_zero (TALER_ARL_currency,
                                        &total_bad_sig_loss));
  GNUNET_assert (NULL !=
                 (report_row_inconsistencies = json_array ()));
  GNUNET_assert (NULL !=
                 (denomination_key_validity_withdraw_inconsistencies =
                    json_array ()));
  GNUNET_assert (NULL !=
                 (report_reserve_balance_summary_wrong_inconsistencies
                    =
                      json_array ()));
  GNUNET_assert (NULL !=
                 (report_reserve_balance_insufficient_inconsistencies
                    =
                      json_array ()));
  GNUNET_assert (NULL !=
                 (report_reserve_not_closed_inconsistencies =
                    json_array ()));
  GNUNET_assert (NULL !=
                 (report_amount_arithmetic_inconsistencies =
                    json_array ()));
  GNUNET_assert (NULL !=
                 (report_bad_sig_losses = json_array ()));
  if (GNUNET_OK !=
      TALER_ARL_setup_sessions_and_run (&analyze_reserves,
                                        NULL))
  {
    global_ret = 1;
    return;
  }
  {
    json_t *report;
    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, s:o,"
                        " s:o, s:o, s:o, s:o, s:I,"
                        " s:I, s:I, s:I, s:I, s:I,"
                        " s:I, s:I }",
                        /* blocks #1 */
                        "reserve_balance_insufficient_inconsistencies",
                        report_reserve_balance_insufficient_inconsistencies,
                        /* Tested in test-auditor.sh #3 */
                        "total_loss_balance_insufficient",
                        TALER_JSON_from_amount (
                          &total_balance_insufficient_loss),
                        /* Tested in test-auditor.sh #3 */
                        "reserve_balance_summary_wrong_inconsistencies",
                        report_reserve_balance_summary_wrong_inconsistencies,
                        "total_balance_summary_delta_plus",
                        TALER_JSON_from_amount (
                          &total_balance_summary_delta_plus),
                        "total_balance_summary_delta_minus",
                        TALER_JSON_from_amount (
                          &total_balance_summary_delta_minus),
                        /* blocks #2 */
                        "total_escrow_balance",
                        TALER_JSON_from_amount (&total_escrow_balance),
                        "total_withdraw_fee_income",
                        TALER_JSON_from_amount (
                          &total_withdraw_fee_income),
                        /* Tested in test-auditor.sh #21 */
                        "reserve_not_closed_inconsistencies",
                        report_reserve_not_closed_inconsistencies,
                        /* Tested in test-auditor.sh #21 */
                        "total_balance_reserve_not_closed",
                        TALER_JSON_from_amount (
                          &total_balance_reserve_not_closed),
                        /* Tested in test-auditor.sh #7 */
                        "bad_sig_losses",
                        report_bad_sig_losses,
                        /* blocks #3 */
                        /* Tested in test-auditor.sh #7 */
                        "total_bad_sig_loss",
                        TALER_JSON_from_amount (&total_bad_sig_loss),
                        /* FIXME: Tested in test-auditor.sh #?? */
                        "row_inconsistencies",
                        report_row_inconsistencies,
                        /* Tested in test-auditor.sh #23 */
                        "denomination_key_validity_withdraw_inconsistencies",
                        denomination_key_validity_withdraw_inconsistencies,
                        "amount_arithmetic_inconsistencies",
                        report_amount_arithmetic_inconsistencies,
                        "total_arithmetic_delta_plus",
                        TALER_JSON_from_amount (
                          &total_arithmetic_delta_plus),
                        /* blocks #4 */
                        "total_arithmetic_delta_minus",
                        TALER_JSON_from_amount (
                          &total_arithmetic_delta_minus),
                        "auditor_start_time",
                        TALER_ARL_json_from_time_abs (
                          start_time),
                        "auditor_end_time",
                        TALER_ARL_json_from_time_abs (
                          GNUNET_TIME_absolute_get ()),
                        "total_irregular_recoups",
                        TALER_JSON_from_amount (
                          &total_irregular_recoups),
                        "start_ppr_reserve_in_serial_id",
                        (json_int_t) ppr_start.last_reserve_in_serial_id,
                        /* blocks #5 */
                        "start_ppr_reserve_out_serial_id",
                        (json_int_t) ppr_start.
                        last_reserve_out_serial_id,
                        "start_ppr_reserve_recoup_serial_id",
                        (json_int_t) ppr_start.
                        last_reserve_recoup_serial_id,
                        "start_ppr_reserve_close_serial_id",
                        (json_int_t) ppr_start.
                        last_reserve_close_serial_id,
                        "end_ppr_reserve_in_serial_id",
                        (json_int_t) ppr.last_reserve_in_serial_id,
                        "end_ppr_reserve_out_serial_id",
                        (json_int_t) ppr.last_reserve_out_serial_id,
                        /* blocks #6 */
                        "end_ppr_reserve_recoup_serial_id",
                        (json_int_t) ppr.last_reserve_recoup_serial_id,
                        "end_ppr_reserve_close_serial_id",
                        (json_int_t) ppr.last_reserve_close_serial_id
                        );
    GNUNET_break (NULL != report);
    TALER_ARL_done (report);
  }
}
/**
 * The main function to check the database's handling of reserves.
 *
 * @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)",
                                      &TALER_ARL_master_pub),
    GNUNET_GETOPT_option_timetravel ('T',
                                     "timetravel"),
    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-helper-auditor-reserves",
                                   "MESSAGE",
                                   NULL));
  if (GNUNET_OK !=
      GNUNET_PROGRAM_run (argc,
                          argv,
                          "taler-helper-auditor-reserves",
                          "Audit Taler exchange reserve handling",
                          options,
                          &run,
                          NULL))
    return 1;
  return global_ret;
}
/* end of taler-helper-auditor-reserves.c */