diff options
Diffstat (limited to 'src/auditor/taler-helper-auditor-purses.c')
| -rw-r--r-- | src/auditor/taler-helper-auditor-purses.c | 1123 | 
1 files changed, 1123 insertions, 0 deletions
diff --git a/src/auditor/taler-helper-auditor-purses.c b/src/auditor/taler-helper-auditor-purses.c new file mode 100644 index 00000000..e0c939fa --- /dev/null +++ b/src/auditor/taler-helper-auditor-purses.c @@ -0,0 +1,1123 @@ +/* +  This file is part of TALER +  Copyright (C) 2016-2022 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 <http://www.gnu.org/licenses/> +*/ +/** + * @file auditor/taler-helper-auditor-purses.c + * @brief audits the purses of an exchange database + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#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; + +/** + * Checkpointing our progress for purses. + */ +static struct TALER_AUDITORDB_ProgressPointPurse ppp; + +/** + * Checkpointing our progress for purses. + */ +static struct TALER_AUDITORDB_ProgressPointPurse ppp_start; + +/** + * Array of reports about row inconsitencies. + */ +static json_t *report_row_inconsistencies; + +/** + * Array of reports about purse balance insufficient inconsitencies. + */ +static json_t *report_purse_balance_insufficient_inconsistencies; + +/** + * Total amount purses were merged with insufficient balance. + */ +static struct TALER_Amount total_balance_insufficient_loss; + +/** + * Array of reports about purses's not being closed inconsitencies. + */ +static json_t *report_purse_not_closed_inconsistencies; + +/** + * Total amount affected by purses not having been closed on time. + */ +static struct TALER_Amount total_balance_purse_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; + +/** + * 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; + +/** + * Should we run checks that only work for exchange-internal audits? + */ +static int internal_checks; + +/* ***************************** 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, 0 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 */ +    TALER_ARL_amount_subtract (&delta, +                               exchange, +                               auditor); +  } +  else +  { +    /* auditor < exchange */ +    profitable = -profitable; +    TALER_ARL_amount_subtract (&delta, +                               auditor, +                               exchange); +  } +  TALER_ARL_report (report_amount_arithmetic_inconsistencies, +                    GNUNET_JSON_PACK ( +                      GNUNET_JSON_pack_string ("operation", +                                               operation), +                      GNUNET_JSON_pack_uint64 ("rowid", +                                               rowid), +                      TALER_JSON_pack_amount ("exchange", +                                              exchange), +                      TALER_JSON_pack_amount ("auditor", +                                              auditor), +                      GNUNET_JSON_pack_int64 ("profitable", +                                              profitable))); +  if (0 != profitable) +  { +    target = (1 == profitable) +        ? &total_arithmetic_delta_plus +        : &total_arithmetic_delta_minus; +    TALER_ARL_amount_add (target, +                          target, +                          &delta); +  } +} + + +/** + * Report a (serious) inconsistency in the exchange's database. + * + * @param table affected table + * @param rowid affected row, 0 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, +                    GNUNET_JSON_PACK ( +                      GNUNET_JSON_pack_string ("table", +                                               table), +                      GNUNET_JSON_pack_uint64 ("row", +                                               rowid), +                      GNUNET_JSON_pack_string ("diagnostic", +                                               diagnostic))); +} + + +/* ***************************** Analyze purses ************************ */ +/* This logic checks the purses_requests, purse_deposits, +   purse_refunds, purse_merges and account_merges */ + +/** + * Summary data we keep per purse. + */ +struct PurseSummary +{ +  /** +   * Public key of the purse. +   * Always set when the struct is first initialized. +   */ +  struct TALER_PursePublicKeyP purse_pub; + +  /** +   * Sum of all incoming transfers during this transaction. +   * Updated only in #handle_purse_deposits(). +   */ +  struct TALER_Amount total_in; + +  /** +   * Expected value of the purse. +   */ +  struct TALER_Amount total_value; + +  /** +   * Purse expiration date. +   */ +  struct GNUNET_TIME_Timestamp expiration_date; + +  /** +   * Did we have a previous purse info?  Used to decide between UPDATE and +   * INSERT later.  Initialized in #load_auditor_purse_summary(). +   */ +  bool had_pi; + +}; + + +/** + * Load the auditor's remembered state about the purse into @a ps. + * + * @param[in,out] ps purse summary to (fully) initialize + * @return transaction status code + */ +static enum GNUNET_DB_QueryStatus +load_auditor_purse_summary (struct PurseSummary *ps) +{ +  enum GNUNET_DB_QueryStatus qs; +  uint64_t rowid; + +  qs = TALER_ARL_adb->get_purse_info (TALER_ARL_adb->cls, +                                      &ps->purse_pub, +                                      &TALER_ARL_master_pub, +                                      &rowid, +                                      &ps->total_in, +                                      &ps->expiration_date); +  if (0 > qs) +  { +    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); +    return qs; +  } +  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) +  { +    ps->had_pi = false; +    GNUNET_assert (GNUNET_OK == +                   TALER_amount_set_zero (TALER_ARL_currency, +                                          &ps->prev_balance.reserve_balance)); +    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, +                "Creating fresh purse `%s'\n", +                TALER_B2S (&ps->purse_pub)); +    return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; +  } +  ps->had_ri = true; +  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, +              "Auditor remembers purse `%s' has balance %s\n", +              TALER_B2S (&ps->purse_pub), +              TALER_amount2s (&ps->total_in)); +  return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; +} + + +/** + * Closure to the various callbacks we make while checking a purse. + */ +struct PurseContext +{ +  /** +   * Map from hash of purse's public key to a `struct PurseSummary`. +   */ +  struct GNUNET_CONTAINER_MultiHashMap *purses; + +  /** +   * Transaction status code, set to error codes if applicable. +   */ +  enum GNUNET_DB_QueryStatus qs; + +}; + + +/** + * Create a new reserve for @a reserve_pub in @a rc. + * + * @param[in,out] pc context to update + * @param purse_pub key for which to create a purse + * @return NULL on error + */ +static struct PurseSummary * +setup_purse (struct PurseContext *pc, +             const struct TALER_PursePublicKeyP *purse_pub) +{ +  struct PurseSummary *rs; +  struct GNUNET_HashCode key; +  enum GNUNET_DB_QueryStatus qs; + +  GNUNET_CRYPTO_hash (purse_pub, +                      sizeof (*purse_pub), +                      &key); +  ps = GNUNET_CONTAINER_multihashmap_get (rc->purses, +                                          &key); +  if (NULL != ps) +    return ps; +  ps = GNUNET_new (struct PurseSummary); +  ps->purse_pub = *purse_pub; +  GNUNET_assert (GNUNET_OK == +                 TALER_amount_set_zero (TALER_ARL_currency, +                                        &ps->total_in)); +  if (0 > (qs = load_auditor_purse_summary (ps))) +  { +    GNUNET_free (ps); +    rc->qs = qs; +    return NULL; +  } +  GNUNET_assert (GNUNET_OK == +                 GNUNET_CONTAINER_multihashmap_put (rc->purses, +                                                    &key, +                                                    ps, +                                                    GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); +  return ps; +} + + +/** + * Check that the purse summary matches what the exchange database + * thinks about the purse, and update our own state of the purse. + * + * Remove all purses that we are happy with from the DB. + * + * @param cls our `struct PurseContext` + * @param key hash of the purse public key + * @param value a `struct PurseSummary` + * @return #GNUNET_OK to process more entries + */ +static enum GNUNET_GenericReturnValue +verify_purse_balance (void *cls, +                      const struct GNUNET_HashCode *key, +                      void *value) +{ +  struct PurseContext *rc = cls; +  struct PurseSummary *ps = value; +  enum GNUNET_GenericReturnValue ret; + +  ret = GNUNET_OK; +  // FIXME: implement! +  GNUNET_assert (GNUNET_YES == +                 GNUNET_CONTAINER_multihashmap_remove (rc->purses, +                                                       key, +                                                       rp)); +  GNUNET_free (ps); +  return ret; +} + + +/** + * Function called with details about purse deposits that have been made, with + * the goal of auditing the deposit's execution. + * + * @param cls closure + * @param rowid unique serial ID for the deposit in our DB + * @param deposit deposit details + * @param reserve_pub which reserve is the purse merged into, NULL if unknown + * @param flags purse flags + * @param auditor_balance purse balance (according to the + *          auditor during auditing) + * @param purse_total target amount the purse should reach + * @param denom_pub denomination public key of @a coin_pub + * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop + */ +static enum GNUNET_GenericReturnValue +handle_purse_deposits ( +  void *cls, +  uint64_t rowid, +  const struct TALER_EXCHANGEDB_PurseDeposit *deposit, +  const struct TALER_ReservePublicKeyP *reserve_pub, +  enum TALER_WalletAccountMergeFlags flags, +  const struct TALER_Amount *auditor_balance, +  const struct TALER_Amount *purse_total, +  const struct TALER_DenominationPublicKey *denom_pub) +{ +  struct PurseContext *rc = cls; +  const char *base_url +    = (NULL == deposit->exchange_base_url) +    ? TALER_ARL_exchange_url +    : deposit->exchange_base_url; +  enum GNUNET_DB_QueryStatus qs; +  struct TALER_Amount amount_minus_fee; +  struct TALER_Amount new_balance; +  struct PurseSummary *rs; +  struct TALER_DenominationHashP h_denom_pub; + +  /* should be monotonically increasing */ +  GNUNET_assert (rowid >= ppr.last_purse_deposits_serial_id); +  ppr.last_purse_deposits_serial_id = rowid + 1; + +  { +    const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue; +    enum GNUNET_DB_QueryStatus qs; + +    qs = TALER_ARL_get_denomination_info (denom_pub, +                                          &issue, +                                          &h_denom_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 from database!\n", +                    TALER_B2S (denom_pub)); +      rc->qs = qs; +      return GNUNET_SYSERR; +    } +    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) +    { +      report_row_inconsistency ("purse-deposit", +                                rowid, +                                "denomination key not found"); +      if (TALER_ARL_do_abort ()) +        return GNUNET_SYSERR; +      return GNUNET_OK; +    } +    TALER_ARL_amount_subtract (&amount_minus_fee, +                               &deposit->amount, +                               &issue->fees.deposit); +  } + +  if (GNUNET_OK != +      TALER_wallet_purse_deposit_verify (base_url, +                                         &deposit->purse_pub, +                                         &deposit->amount, +                                         &h_denom_pub, +                                         &deposit->h_age_commitment, +                                         &deposit->coin_pub, +                                         &deposit->coin_sig)) +  { +    TALER_ARL_report (report_bad_sig_losses, +                      GNUNET_JSON_PACK ( +                        GNUNET_JSON_pack_string ("operation", +                                                 "purse-deposit"), +                        GNUNET_JSON_pack_uint64 ("row", +                                                 rowid), +                        TALER_JSON_pack_amount ("loss", +                                                &deposit->amount), +                        GNUNET_JSON_pack_data_auto ("key_pub", +                                                    &deposit->coin_pub))); +    TALER_ARL_amount_add (&total_bad_sig_loss, +                          &total_bad_sig_loss, +                          &deposit->amount); +    return GNUNET_OK; +  } + +  TALER_ARL_amount_add (&new_balance, +                        auditor_balance, +                        &amount_minus_fee); +  qs = TALER_ARL_edb->set_purse_balance (TALER_ARL_edb->cls, +                                         &deposit->purse_pub, +                                         &new_balance); +  GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs); +  if (qs < 0) +  { +    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); +    GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); +    rc->qs = qs; +    return GNUNET_SYSERR; +  } +  if (TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE != +      (flags & TALER_WAMF_MERGE_MODE_MASK)) +  { +    /* This just created the purse, actual credit to +       the reserve will be done in handle_account_merged() */ +    return GNUNET_OK; +  } +  if ( (NULL != deposit->exchange_base_url) && +       (0 != strcmp (deposit->exchange_base_url, +                     TALER_ARL_exchange_url)) ) +  { +    /* credited reserve is at another exchange, do NOT credit here! */ +    return GNUNET_OK; +  } + +  rs = setup_reserve (rc, +                      reserve_pub); +  if (NULL == rs) +  { +    GNUNET_break (0); +    return GNUNET_SYSERR; +  } +  if ( (-1 != TALER_amount_cmp (&new_balance, +                                purse_total)) && +       (-1 == TALER_amount_cmp (auditor_balance, +                                purse_total)) ) +  { +    /* new balance at or above purse_total +       (and previous balance was below); thus +       credit reserve with purse value! */ +    TALER_ARL_amount_add (&rs->total_in, +                          &rs->total_in, +                          purse_total); +  } +  return GNUNET_OK; +} + + +/** + * Function called with details about purse + * merges that have been made, with + * the goal of auditing the purse merge execution. + * + * @param cls closure + * @param rowid unique serial ID for the deposit in our DB + * @param partner_base_url where is the reserve, NULL for this exchange + * @param amount total amount expected in the purse + * @param balance current balance in the purse (according to the auditor) + * @param flags purse flags + * @param merge_pub merge capability key + * @param reserve_pub reserve the merge affects + * @param merge_sig signature affirming the merge + * @param purse_pub purse key + * @param merge_timestamp when did the merge happen + * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop + */ +static enum GNUNET_GenericReturnValue +handle_purse_merged ( +  void *cls, +  uint64_t rowid, +  const char *partner_base_url, +  const struct TALER_Amount *amount, +  const struct TALER_Amount *balance, +  enum TALER_WalletAccountMergeFlags flags, +  const struct TALER_PurseMergePublicKeyP *merge_pub, +  const struct TALER_ReservePublicKeyP *reserve_pub, +  const struct TALER_PurseMergeSignatureP *merge_sig, +  const struct TALER_PurseContractPublicKeyP *purse_pub, +  struct GNUNET_TIME_Timestamp merge_timestamp) +{ +  struct PurseContext *rc = cls; +  struct PurseSummary *rs; +  char *reserve_url; + +  /* should be monotonically increasing */ +  GNUNET_assert (rowid >= ppr.last_purse_merges_serial_id); +  ppr.last_purse_merges_serial_id = rowid + 1; +  reserve_url +    = TALER_reserve_make_payto (NULL == partner_base_url +                                ? TALER_ARL_exchange_url +                                : partner_base_url, +                                reserve_pub); +  if (GNUNET_OK != +      TALER_wallet_purse_merge_verify (reserve_url, +                                       merge_timestamp, +                                       purse_pub, +                                       merge_pub, +                                       merge_sig)) +  { +    GNUNET_free (reserve_url); +    TALER_ARL_report (report_bad_sig_losses, +                      GNUNET_JSON_PACK ( +                        GNUNET_JSON_pack_string ("operation", +                                                 "merge-purse"), +                        GNUNET_JSON_pack_uint64 ("row", +                                                 rowid), +                        TALER_JSON_pack_amount ("loss", +                                                amount), +                        GNUNET_JSON_pack_data_auto ("key_pub", +                                                    merge_pub))); +    TALER_ARL_amount_add (&total_bad_sig_loss, +                          &total_bad_sig_loss, +                          amount); +    return GNUNET_OK; +  } +  GNUNET_free (reserve_url); +  if (TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE == +      (flags & TALER_WAMF_MERGE_MODE_MASK)) +  { +    /* This just created the purse, actual credit to +       the reserve will be done in handle_purse_deposits() */ +    return GNUNET_OK; +  } +  if ( (NULL != partner_base_url) && +       (0 != strcmp (partner_base_url, +                     TALER_ARL_exchange_url)) ) +  { +    /* credited reserve is at another exchange, do NOT credit here! */ +    return GNUNET_OK; +  } +  rs = setup_reserve (rc, +                      reserve_pub); +  if (NULL == rs) +  { +    GNUNET_break (0); +    return GNUNET_SYSERR; +  } +  if (-1 == TALER_amount_cmp (balance, +                              amount)) +  { +    struct TALER_Amount loss; + +    TALER_ARL_amount_subtract (&loss, +                               amount, +                               balance); +    /* illegal merge, balance is still below total purse value */ +    TALER_ARL_report (report_purse_balance_insufficient_inconsistencies, +                      GNUNET_JSON_PACK ( +                        GNUNET_JSON_pack_string ("operation", +                                                 "merge-purse"), +                        GNUNET_JSON_pack_uint64 ("row", +                                                 rowid), +                        TALER_JSON_pack_amount ("loss", +                                                &loss), +                        GNUNET_JSON_pack_data_auto ("purse_pub", +                                                    purse_pub))); +    TALER_ARL_amount_add (&total_balance_insufficient_loss, +                          &total_balance_insufficient_loss, +                          &loss); +    return GNUNET_OK; +  } +  TALER_ARL_amount_add (&rs->total_in, +                        &rs->total_in, +                        amount); +  // rs->a_expiration_date = FIXME: do we care? If so, set to what (so that the auditor no longer complains about the reserve not being closed) +  return GNUNET_OK; +} + + +/** + * Function called with details about + * account merge requests that have been made, with + * the goal of auditing the account merge execution. + * + * @param cls closure + * @param rowid unique serial ID for the deposit in our DB + * @param reserve_pub reserve affected by the merge + * @param purse_pub purse being merged + * @param h_contract_terms hash over contract of the purse + * @param purse_expiration when would the purse expire + * @param amount total amount in the purse + * @param min_age minimum age of all coins deposited into the purse + * @param flags how was the purse created + * @param purse_fee if a purse fee was paid, how high is it + * @param merge_timestamp when was the merge approved + * @param reserve_sig signature by reserve approving the merge + * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop + */ +static enum GNUNET_GenericReturnValue +handle_account_merged ( +  void *cls, +  uint64_t rowid, +  const struct TALER_ReservePublicKeyP *reserve_pub, +  const struct TALER_PurseContractPublicKeyP *purse_pub, +  const struct TALER_PrivateContractHashP *h_contract_terms, +  struct GNUNET_TIME_Timestamp purse_expiration, +  const struct TALER_Amount *amount, +  uint32_t min_age, +  enum TALER_WalletAccountMergeFlags flags, +  const struct TALER_Amount *purse_fee, +  struct GNUNET_TIME_Timestamp merge_timestamp, +  const struct TALER_PursesignatureP *reserve_sig) +{ +  struct PurseContext *rc = cls; +  struct PurseSummary *rs; + +  /* should be monotonically increasing */ +  GNUNET_assert (rowid >= ppr.last_account_merges_serial_id); +  ppr.last_account_merges_serial_id = rowid + 1; +  if (GNUNET_OK != +      TALER_wallet_account_merge_verify (merge_timestamp, +                                         purse_pub, +                                         purse_expiration, +                                         h_contract_terms, +                                         amount, +                                         purse_fee, +                                         min_age, +                                         flags, +                                         reserve_pub, +                                         reserve_sig)) +  { +    TALER_ARL_report (report_bad_sig_losses, +                      GNUNET_JSON_PACK ( +                        GNUNET_JSON_pack_string ("operation", +                                                 "account-merge"), +                        GNUNET_JSON_pack_uint64 ("row", +                                                 rowid), +                        TALER_JSON_pack_amount ("loss", +                                                purse_fee), +                        GNUNET_JSON_pack_data_auto ("key_pub", +                                                    reserve_pub))); +    TALER_ARL_amount_add (&total_bad_sig_loss, +                          &total_bad_sig_loss, +                          purse_fee); +    return GNUNET_OK; +  } +  rs = setup_reserve (rc, +                      reserve_pub); +  if (NULL == rs) +  { +    GNUNET_break (0); +    return GNUNET_SYSERR; +  } +  // FIXME: This is to be done per purse_requests, +  // not per account_merges! +  if ( (flags & TALER_WAMF_MERGE_MODE_MASK) == +       TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE) +  { +    TALER_ARL_amount_add (&balance.purse_fee_balance, +                          &balance.purse_fee_balance, +                          purse_fee); +    TALER_ARL_amount_add (&rs->curr_balance.purse_fee_balance, +                          &rs->curr_balance.purse_fee_balance, +                          purse_fee); +    TALER_ARL_amount_add (&rs->total_out, +                          &rs->total_out, +                          purse_fee); +  } +  TALER_ARL_amount_add (&rs->total_in, +                        &rs->total_in, +                        amount); +  return GNUNET_OK; +} + + +/** + * Analyze purses for being well-formed. + * + * @param cls NULL + * @return transaction status code + */ +static enum GNUNET_DB_QueryStatus +analyze_purses (void *cls) +{ +  struct PurseContext 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 purses\n"); +  qsp = TALER_ARL_adb->get_auditor_progress_purse (TALER_ARL_adb->cls, +                                                   &TALER_ARL_master_pub, +                                                   &ppp); +  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 purse audit at %llu/%llu/%llu\n", +                (unsigned long long) ppp.last_purse_merges_serial_id, +                (unsigned long long) ppp.last_purse_deposits_serial_id, +                (unsigned long long) ppp.last_account_merges_serial_id); +  } +  rc.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; +  qsx = TALER_ARL_adb->get_reserve_summary (TALER_ARL_adb->cls, +                                            &TALER_ARL_master_pub, +                                            &balance); +  if (qsx < 0) +  { +    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qsx); +    return qsx; +  } +  rc.purses = GNUNET_CONTAINER_multihashmap_create (512, +                                                    GNUNET_NO); +  qs = TALER_ARL_edb->select_purse_merges_above_serial_id ( +    TALER_ARL_edb->cls, +    ppr.last_purse_merges_serial_id, +    &handle_purse_merged, +    &rc); +  if (qs < 0) +  { +    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); +    return qs; +  } +  qs = TALER_ARL_edb->select_purse_deposits_above_serial_id ( +    TALER_ARL_edb->cls, +    ppr.last_purse_deposits_serial_id, +    &handle_purse_deposits, +    &rc); +  if (qs < 0) +  { +    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); +    return qs; +  } +  /* Charge purse fee! */ +  qs = TALER_ARL_edb->select_account_merges_above_serial_id ( +    TALER_ARL_edb->cls, +    ppr.last_account_merges_serial_id, +    &handle_account_merged, +    &rc); +  if (qs < 0) +  { +    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); +    return qs; +  } + +  GNUNET_CONTAINER_multihashmap_iterate (rc.purses, +                                         &verify_purse_balance, +                                         &rc); +  GNUNET_break (0 == +                GNUNET_CONTAINER_multihashmap_size (rc.purses)); +  GNUNET_CONTAINER_multihashmap_destroy (rc.purses); +  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_master_pub, +                                                &balance); +  } +  else +  { +    qs = TALER_ARL_adb->update_reserve_summary (TALER_ARL_adb->cls, +                                                &TALER_ARL_master_pub, +                                                &balance); +  } +  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_purse (TALER_ARL_adb->cls, +                                                       &TALER_ARL_master_pub, +                                                       &ppp); +  else +    qs = TALER_ARL_adb->insert_auditor_progress_purse (TALER_ARL_adb->cls, +                                                       &TALER_ARL_master_pub, +                                                       &ppp); +  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 purse audit step at %llu/%llu/%llu\n", +              (unsigned long long) ppp.last_purse_merges_serial_id, +              (unsigned long long) ppp.last_purse_deposits_serial_id, +              (unsigned long long) ppp.last_account_merges_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 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) +{ +  (void) cls; +  (void) args; +  (void) cfgfile; +  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, +              "Launching auditor\n"); +  if (GNUNET_OK != +      TALER_ARL_init (c)) +  { +    global_ret = EXIT_FAILURE; +    return; +  } +  GNUNET_assert (GNUNET_OK == +                 TALER_amount_set_zero (TALER_ARL_currency, +                                        &balance.reserve_balance)); +  GNUNET_assert (GNUNET_OK == +                 TALER_amount_set_zero (TALER_ARL_currency, +                                        &balance.reserve_loss)); +  GNUNET_assert (GNUNET_OK == +                 TALER_amount_set_zero (TALER_ARL_currency, +                                        &balance.withdraw_fee_balance)); +  GNUNET_assert (GNUNET_OK == +                 TALER_amount_set_zero (TALER_ARL_currency, +                                        &balance.close_fee_balance)); +  GNUNET_assert (GNUNET_OK == +                 TALER_amount_set_zero (TALER_ARL_currency, +                                        &balance.purse_fee_balance)); +  GNUNET_assert (GNUNET_OK == +                 TALER_amount_set_zero (TALER_ARL_currency, +                                        &balance.open_fee_balance)); +  GNUNET_assert (GNUNET_OK == +                 TALER_amount_set_zero (TALER_ARL_currency, +                                        &balance.history_fee_balance)); +  // REVIEW: +  GNUNET_assert (GNUNET_OK == +                 TALER_amount_set_zero (TALER_ARL_currency, +                                        &total_balance_summary_delta_plus)); +  GNUNET_assert (GNUNET_OK == +                 TALER_amount_set_zero (TALER_ARL_currency, +                                        &total_balance_summary_delta_minus)); +  GNUNET_assert (GNUNET_OK == +                 TALER_amount_set_zero (TALER_ARL_currency, +                                        &total_arithmetic_delta_plus)); +  GNUNET_assert (GNUNET_OK == +                 TALER_amount_set_zero (TALER_ARL_currency, +                                        &total_arithmetic_delta_minus)); +  GNUNET_assert (GNUNET_OK == +                 TALER_amount_set_zero (TALER_ARL_currency, +                                        &total_balance_reserve_not_closed)); +  GNUNET_assert (GNUNET_OK == +                 TALER_amount_set_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_purse_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_purses, +                                        NULL)) +  { +    global_ret = EXIT_FAILURE; +    return; +  } +  TALER_ARL_done ( +    GNUNET_JSON_PACK ( +      /* Globals (REVIEW!) */ +      /* Tested in test-auditor.sh #3 */ +      TALER_JSON_pack_amount ("total_loss_balance_insufficient", +                              &total_balance_insufficient_loss), +      /* Tested in test-auditor.sh #3 */ +      GNUNET_JSON_pack_array_steal ( +        "reserve_balance_summary_wrong_inconsistencies", +        report_reserve_balance_summary_wrong_inconsistencies), +      TALER_JSON_pack_amount ("total_balance_summary_delta_plus", +                              &total_balance_summary_delta_plus), +      TALER_JSON_pack_amount ("total_balance_summary_delta_minus", +                              &total_balance_summary_delta_minus), +      /* Tested in test-auditor.sh #21 */ +      TALER_JSON_pack_amount ("total_balance_reserve_not_closed", +                              &total_balance_reserve_not_closed), +      /* Tested in test-auditor.sh #7 */ +      TALER_JSON_pack_amount ("total_bad_sig_loss", +                              &total_bad_sig_loss), +      TALER_JSON_pack_amount ("total_arithmetic_delta_plus", +                              &total_arithmetic_delta_plus), +      TALER_JSON_pack_amount ("total_arithmetic_delta_minus", +                              &total_arithmetic_delta_minus), + +      /* Global 'balances' */ +      TALER_JSON_pack_amount ("total_escrow_balance", +                              &balance.reserve_balance), +      TALER_JSON_pack_amount ("total_irregular_loss", +                              &balance.reserve_loss), +      TALER_JSON_pack_amount ("total_withdraw_fee_income", +                              &balance.withdraw_fee_balance), +      TALER_JSON_pack_amount ("total_close_fee_income", +                              &balance.close_fee_balance), +      TALER_JSON_pack_amount ("total_purse_fee_income", +                              &balance.purse_fee_balance), +      TALER_JSON_pack_amount ("total_open_fee_income", +                              &balance.open_fee_balance), +      TALER_JSON_pack_amount ("total_history_fee_income", +                              &balance.history_fee_balance), + +      /* Detailed report tables */ +      GNUNET_JSON_pack_array_steal ( +        "reserve_balance_insufficient_inconsistencies", +        report_reserve_balance_insufficient_inconsistencies), +      GNUNET_JSON_pack_array_steal ( +        "purse_balance_insufficient_inconsistencies", +        report_purse_balance_insufficient_inconsistencies), +      /* Tested in test-auditor.sh #21 */ +      GNUNET_JSON_pack_array_steal ("reserve_not_closed_inconsistencies", +                                    report_reserve_not_closed_inconsistencies), +      /* Tested in test-auditor.sh #7 */ +      GNUNET_JSON_pack_array_steal ("bad_sig_losses", +                                    report_bad_sig_losses), +      /* Tested in test-revocation.sh #4 */ +      GNUNET_JSON_pack_array_steal ("row_inconsistencies", +                                    report_row_inconsistencies), +      /* Tested in test-auditor.sh #23 */ +      GNUNET_JSON_pack_array_steal ( +        "denomination_key_validity_withdraw_inconsistencies", +        denomination_key_validity_withdraw_inconsistencies), +      GNUNET_JSON_pack_array_steal ("amount_arithmetic_inconsistencies", +                                    report_amount_arithmetic_inconsistencies), + +      /* Information about audited range ... */ +      TALER_JSON_pack_time_abs_human ("auditor_start_time", +                                      start_time), +      TALER_JSON_pack_time_abs_human ("auditor_end_time", +                                      GNUNET_TIME_absolute_get ()), +      GNUNET_JSON_pack_uint64 ("start_ppr_reserve_in_serial_id", +                               ppr_start.last_reserve_in_serial_id), +      GNUNET_JSON_pack_uint64 ("start_ppr_reserve_out_serial_id", +                               ppr_start.last_reserve_out_serial_id), +      GNUNET_JSON_pack_uint64 ("start_ppr_reserve_recoup_serial_id", +                               ppr_start.last_reserve_recoup_serial_id), +      GNUNET_JSON_pack_uint64 ("start_ppr_reserve_open_serial_id", +                               ppr_start.last_reserve_open_serial_id), +      GNUNET_JSON_pack_uint64 ("start_ppr_reserve_close_serial_id", +                               ppr_start.last_reserve_close_serial_id), +      GNUNET_JSON_pack_uint64 ("start_ppr_purse_merges_serial_id", +                               ppr_start.last_purse_merges_serial_id), +      GNUNET_JSON_pack_uint64 ("start_ppr_purse_deposits_serial_id", +                               ppr_start.last_purse_deposits_serial_id), +      GNUNET_JSON_pack_uint64 ("start_ppr_account_merges_serial_id", +                               ppr_start.last_account_merges_serial_id), +      GNUNET_JSON_pack_uint64 ("start_ppr_history_requests_serial_id", +                               ppr_start.last_history_requests_serial_id), +      GNUNET_JSON_pack_uint64 ("end_ppr_reserve_in_serial_id", +                               ppr.last_reserve_in_serial_id), +      GNUNET_JSON_pack_uint64 ("end_ppr_reserve_out_serial_id", +                               ppr.last_reserve_out_serial_id), +      GNUNET_JSON_pack_uint64 ("end_ppr_reserve_recoup_serial_id", +                               ppr.last_reserve_recoup_serial_id), +      GNUNET_JSON_pack_uint64 ("end_ppr_reserve_open_serial_id", +                               ppr.last_reserve_open_serial_id), +      GNUNET_JSON_pack_uint64 ("end_ppr_reserve_close_serial_id", +                               ppr.last_reserve_close_serial_id), +      GNUNET_JSON_pack_uint64 ("end_ppr_purse_merges_serial_id", +                               ppr.last_purse_merges_serial_id), +      GNUNET_JSON_pack_uint64 ("end_ppr_purse_deposits_serial_id", +                               ppr.last_purse_deposits_serial_id), +      GNUNET_JSON_pack_uint64 ("end_ppr_account_merges_serial_id", +                               ppr.last_account_merges_serial_id), +      GNUNET_JSON_pack_uint64 ("end_ppr_history_requests_serial_id", +                               ppr.last_history_requests_serial_id))); +} + + +/** + * The main function to check the database's handling of purses. + * + * @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_flag ('i', +                               "internal", +                               "perform checks only applicable for exchange-internal audits", +                               &internal_checks), +    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 +  }; +  enum GNUNET_GenericReturnValue ret; + +  /* 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 (); +  if (GNUNET_OK != +      GNUNET_STRINGS_get_utf8_args (argc, argv, +                                    &argc, &argv)) +    return EXIT_INVALIDARGUMENT; +  ret = GNUNET_PROGRAM_run ( +    argc, +    argv, +    "taler-helper-auditor-purses", +    gettext_noop ("Audit Taler exchange purse handling"), +    options, +    &run, +    NULL); +  GNUNET_free_nz ((void *) argv); +  if (GNUNET_SYSERR == ret) +    return EXIT_INVALIDARGUMENT; +  if (GNUNET_NO == ret) +    return EXIT_SUCCESS; +  return global_ret; +} + + +/* end of taler-helper-auditor-purses.c */  | 
