exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

taler-helper-auditor-reserves.c (81849B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2016-2024 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Affero Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU Affero Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file auditor/taler-helper-auditor-reserves.c
     18  * @brief audits the reserves of an exchange database
     19  * @author Christian Grothoff
     20  */
     21 #include "platform.h"
     22 /* RESERVE_IN's callback typedef also lives in exchangedb_lib.h (pulled in
     23    transitively by report-lib.h below), so its closure override must be
     24    established before any of those includes. */
     25 struct ReserveContext;
     26 #define TALER_EXCHANGEDB_RESERVE_IN_RESULT_CLOSURE struct ReserveContext
     27 #include "auditordb_lib.h"
     28 #include "report-lib.h"
     29 #include "taler/taler_dbevents.h"
     30 #include "exchangedb_lib.h"
     31 #include "exchange-database/get_reserve_close_request_info.h"
     32 #include "auditor-database/delete_reserve_info.h"
     33 #include "auditor-database/event_listen.h"
     34 #include "auditor-database/get_auditor_progress.h"
     35 #include "auditor-database/get_balance.h"
     36 #include "auditor-database/get_reserve_info.h"
     37 #include "auditor-database/insert_amount_arithmetic_inconsistency.h"
     38 #include "auditor-database/insert_auditor_progress.h"
     39 #include "auditor-database/insert_bad_sig_losses.h"
     40 #include "auditor-database/insert_balance.h"
     41 #include \
     42   "auditor-database/insert_denomination_key_validity_withdraw_inconsistency.h"
     43 #include \
     44   "auditor-database/insert_reserve_balance_insufficient_inconsistency.h"
     45 #include \
     46   "auditor-database/insert_reserve_balance_summary_wrong_inconsistency.h"
     47 #include "auditor-database/insert_reserve_info.h"
     48 #include "auditor-database/insert_reserve_not_closed_inconsistency.h"
     49 #include "auditor-database/insert_row_inconsistency.h"
     50 #include "auditor-database/update_reserve_info.h"
     51 #include "exchange-database/get_denomination_revocation.h"
     52 #include "exchange-database/get_wire_fee.h"
     53 #include "exchange-database/get_reserve.h"
     54 struct ReserveContext;
     55 #define TALER_EXCHANGEDB_ACCOUNT_MERGE_RESULT_CLOSURE struct ReserveContext
     56 #define TALER_EXCHANGEDB_PURSE_DECISION_RESULT_CLOSURE struct ReserveContext
     57 #define TALER_EXCHANGEDB_RECOUP_RESULT_CLOSURE struct ReserveContext
     58 #define TALER_EXCHANGEDB_RESERVE_CLOSED_RESULT_CLOSURE struct ReserveContext
     59 #define TALER_EXCHANGEDB_RESERVE_OPEN_RESULT_CLOSURE struct ReserveContext
     60 #define TALER_EXCHANGEDB_WITHDRAW_RESULT_CLOSURE struct ReserveContext
     61 #include "exchange-database/iterate_account_merges_above_serial_id.h"
     62 #include "exchange-database/iterate_purse_decisions_above_serial_id.h"
     63 #include "exchange-database/iterate_recoups_above_serial_id.h"
     64 #include "exchange-database/iterate_reserve_closed_above_serial_id.h"
     65 #include "exchange-database/iterate_reserve_open_above_serial_id.h"
     66 #include "exchange-database/iterate_reserves_in_above_serial_id.h"
     67 #include "exchange-database/iterate_withdrawals_above_serial_id.h"
     68 
     69 /**
     70  * Use a 1 day grace period to deal with clocks not being perfectly synchronized.
     71  */
     72 #define CLOSING_GRACE_PERIOD GNUNET_TIME_UNIT_DAYS
     73 
     74 /**
     75  * Return value from main().
     76  */
     77 static int global_ret;
     78 
     79 /**
     80  * State of the last database transaction.
     81  */
     82 static enum GNUNET_DB_QueryStatus global_qs;
     83 
     84 /**
     85  * Run in test mode. Exit when idle instead of
     86  * going to sleep and waiting for more work.
     87  */
     88 static int test_mode;
     89 
     90 /**
     91  * After how long should idle reserves be closed?
     92  */
     93 static struct GNUNET_TIME_Relative idle_reserve_expiration_time;
     94 
     95 /**
     96  * Checkpointing our progress for reserves.
     97  */
     98 static TALER_ARL_DEF_PP (reserves_reserve_in_serial_id);
     99 static TALER_ARL_DEF_PP (reserves_withdraw_serial_id);
    100 static TALER_ARL_DEF_PP (reserves_reserve_recoup_serial_id);
    101 static TALER_ARL_DEF_PP (reserves_reserve_open_serial_id);
    102 static TALER_ARL_DEF_PP (reserves_reserve_close_serial_id);
    103 static TALER_ARL_DEF_PP (reserves_purse_decisions_serial_id);
    104 static TALER_ARL_DEF_PP (reserves_account_merges_serial_id);
    105 static TALER_ARL_DEF_PP (reserves_history_requests_serial_id);
    106 
    107 /**
    108  * Tracked global reserve balances.
    109  */
    110 static TALER_ARL_DEF_AB (reserves_reserve_total_balance);
    111 static TALER_ARL_DEF_AB (reserves_reserve_loss);
    112 static TALER_ARL_DEF_AB (reserves_withdraw_fee_revenue);
    113 static TALER_ARL_DEF_AB (reserves_close_fee_revenue);
    114 static TALER_ARL_DEF_AB (reserves_purse_fee_revenue);
    115 static TALER_ARL_DEF_AB (reserves_open_fee_revenue);
    116 static TALER_ARL_DEF_AB (reserves_history_fee_revenue);
    117 
    118 /**
    119  * Total amount lost by operations for which signatures were invalid.
    120  */
    121 static TALER_ARL_DEF_AB (reserves_total_bad_sig_loss);
    122 
    123 /**
    124  * Total amount affected by reserves not having been closed on time.
    125  */
    126 static TALER_ARL_DEF_AB (total_balance_reserve_not_closed);
    127 
    128 /**
    129  * Total delta between expected and stored reserve balance summaries,
    130  * for positive deltas.  Used only when internal checks are
    131  * enabled.
    132  */
    133 static TALER_ARL_DEF_AB (total_balance_summary_delta_plus);
    134 
    135 /**
    136  * Total delta between expected and stored reserve balance summaries,
    137  * for negative deltas.  Used only when internal checks are
    138  * enabled.
    139  */
    140 static TALER_ARL_DEF_AB (total_balance_summary_delta_minus);
    141 
    142 /**
    143  * Profits the exchange made by bad amount calculations.
    144  */
    145 static TALER_ARL_DEF_AB (reserves_total_arithmetic_delta_plus);
    146 
    147 /**
    148  * Losses the exchange made by bad amount calculations.
    149  */
    150 static TALER_ARL_DEF_AB (reserves_total_arithmetic_delta_minus);
    151 
    152 /**
    153  * Should we run checks that only work for exchange-internal audits?
    154  */
    155 static int internal_checks;
    156 
    157 static struct GNUNET_DB_EventHandler *eh;
    158 
    159 /**
    160  * The auditors's configuration.
    161  */
    162 static const struct GNUNET_CONFIGURATION_Handle *cfg;
    163 
    164 /* ***************************** Report logic **************************** */
    165 
    166 
    167 /**
    168  * Report a (serious) inconsistency in the exchange's database with
    169  * respect to calculations involving amounts.
    170  *
    171  * @param operation what operation had the inconsistency
    172  * @param rowid affected row, 0 if row is missing
    173  * @param exchange amount calculated by exchange
    174  * @param auditor amount calculated by auditor
    175  * @param profitable 1 if @a exchange being larger than @a auditor is
    176  *           profitable for the exchange for this operation,
    177  *           -1 if @a exchange being smaller than @a auditor is
    178  *           profitable for the exchange, and 0 if it is unclear
    179  */
    180 static void
    181 report_amount_arithmetic_inconsistency (
    182   const char *operation,
    183   uint64_t rowid,
    184   const struct TALER_Amount *exchange,
    185   const struct TALER_Amount *auditor,
    186   int profitable)
    187 {
    188   struct TALER_Amount delta;
    189   struct TALER_Amount *target;
    190   enum GNUNET_DB_QueryStatus qs;
    191 
    192   if (0 < TALER_amount_cmp (exchange,
    193                             auditor))
    194   {
    195     /* exchange > auditor */
    196     TALER_ARL_amount_subtract (&delta,
    197                                exchange,
    198                                auditor);
    199   }
    200   else
    201   {
    202     /* exchange <= auditor */
    203     profitable = -profitable;
    204     TALER_ARL_amount_subtract (&delta,
    205                                auditor,
    206                                exchange);
    207   }
    208 
    209   {
    210     struct TALER_AUDITORDB_AmountArithmeticInconsistency aai = {
    211       .problem_row_id = rowid,
    212       .profitable = profitable,
    213       .operation = (char *) operation,
    214       .exchange_amount = *exchange,
    215       .auditor_amount = *auditor,
    216     };
    217 
    218     qs = TALER_AUDITORDB_insert_amount_arithmetic_inconsistency (
    219       TALER_ARL_adb,
    220       &aai);
    221 
    222     if (qs < 0)
    223     {
    224       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    225       global_qs = qs;
    226       return;
    227     }
    228   }
    229 
    230   if (0 != profitable)
    231   {
    232     target = (1 == profitable)
    233       ? &TALER_ARL_USE_AB (reserves_total_arithmetic_delta_plus)
    234       : &TALER_ARL_USE_AB (reserves_total_arithmetic_delta_minus);
    235     TALER_ARL_amount_add (target,
    236                           target,
    237                           &delta);
    238   }
    239 }
    240 
    241 
    242 /**
    243  * Report a (serious) inconsistency in the exchange's database.
    244  *
    245  * @param table affected table
    246  * @param rowid affected row, 0 if row is missing
    247  * @param diagnostic message explaining the problem
    248  */
    249 static void
    250 report_row_inconsistency (const char *table,
    251                           uint64_t rowid,
    252                           const char *diagnostic)
    253 {
    254   enum GNUNET_DB_QueryStatus qs;
    255   struct TALER_AUDITORDB_RowInconsistency ri = {
    256     .diagnostic = (char *) diagnostic,
    257     .row_table = (char *) table,
    258     .problem_row_id = rowid
    259   };
    260 
    261   qs = TALER_AUDITORDB_insert_row_inconsistency (
    262     TALER_ARL_adb,
    263     &ri);
    264 
    265   if (qs < 0)
    266   {
    267     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    268     global_qs = qs;
    269     return;
    270   }
    271 }
    272 
    273 
    274 /* ***************************** Analyze reserves ************************ */
    275 /* This logic checks the reserves_in, withdraw and reserves-tables */
    276 
    277 /**
    278  * Summary data we keep per reserve.
    279  */
    280 struct ReserveSummary
    281 {
    282   /**
    283    * Public key of the reserve.
    284    * Always set when the struct is first initialized.
    285    */
    286   struct TALER_ReservePublicKeyP reserve_pub;
    287 
    288   /**
    289    * Sum of all incoming transfers during this transaction.
    290    * Updated only in #handle_reserve_in().
    291    */
    292   struct TALER_Amount total_in;
    293 
    294   /**
    295    * Sum of all outgoing transfers during this transaction (includes fees).
    296    * Updated only in #handle_withdrawals().
    297    */
    298   struct TALER_Amount total_out;
    299 
    300   /**
    301    * Sum of balance and fees encountered during this transaction.
    302    */
    303   struct TALER_AUDITORDB_ReserveFeeBalance curr_balance;
    304 
    305   /**
    306    * Previous balances of the reserve as remembered by the auditor.
    307    * (updated based on @e total_in and @e total_out at the end).
    308    */
    309   struct TALER_AUDITORDB_ReserveFeeBalance prev_balance;
    310 
    311   /**
    312    * Previous reserve expiration data, as remembered by the auditor.
    313    * (updated on-the-fly in #handle_reserve_in()).
    314    */
    315   struct GNUNET_TIME_Timestamp a_expiration_date;
    316 
    317   /**
    318    * Which account did originally put money into the reserve?
    319    */
    320   struct TALER_FullPayto sender_account;
    321 
    322   /**
    323    * Did we have a previous reserve info?  Used to decide between
    324    * UPDATE and INSERT later.  Initialized in
    325    * #load_auditor_reserve_summary() together with the a-* values
    326    * (if available).
    327    */
    328   bool had_ri;
    329 
    330 };
    331 
    332 
    333 /**
    334  * Load the auditor's remembered state about the reserve into @a rs.
    335  * The "total_in" and "total_out" amounts of @a rs must already be
    336  * initialized (so we can determine the currency).
    337  *
    338  * @param[in,out] rs reserve summary to (fully) initialize
    339  * @return transaction status code
    340  */
    341 static enum GNUNET_DB_QueryStatus
    342 load_auditor_reserve_summary (struct ReserveSummary *rs)
    343 {
    344   enum GNUNET_DB_QueryStatus qs;
    345   uint64_t rowid;
    346 
    347   qs = TALER_AUDITORDB_get_reserve_info (TALER_ARL_adb,
    348                                          &rs->reserve_pub,
    349                                          &rowid,
    350                                          &rs->prev_balance,
    351                                          &rs->a_expiration_date,
    352                                          &rs->sender_account);
    353   if (0 > qs)
    354   {
    355     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    356     return qs;
    357   }
    358   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    359   {
    360     rs->had_ri = false;
    361     GNUNET_assert (GNUNET_OK ==
    362                    TALER_amount_set_zero (rs->total_in.currency,
    363                                           &rs->prev_balance.reserve_balance));
    364     GNUNET_assert (GNUNET_OK ==
    365                    TALER_amount_set_zero (rs->total_in.currency,
    366                                           &rs->prev_balance.reserve_loss));
    367     GNUNET_assert (GNUNET_OK ==
    368                    TALER_amount_set_zero (rs->total_in.currency,
    369                                           &rs->prev_balance.withdraw_fee_balance
    370                                           ));
    371     GNUNET_assert (GNUNET_OK ==
    372                    TALER_amount_set_zero (rs->total_in.currency,
    373                                           &rs->prev_balance.close_fee_balance));
    374     GNUNET_assert (GNUNET_OK ==
    375                    TALER_amount_set_zero (rs->total_in.currency,
    376                                           &rs->prev_balance.purse_fee_balance));
    377     GNUNET_assert (GNUNET_OK ==
    378                    TALER_amount_set_zero (rs->total_in.currency,
    379                                           &rs->prev_balance.open_fee_balance));
    380     GNUNET_assert (GNUNET_OK ==
    381                    TALER_amount_set_zero (rs->total_in.currency,
    382                                           &rs->prev_balance.history_fee_balance)
    383                    );
    384     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    385                 "Creating fresh reserve `%s'\n",
    386                 TALER_B2S (&rs->reserve_pub));
    387     return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
    388   }
    389   rs->had_ri = true;
    390   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    391               "Auditor remembers reserve `%s' has balance %s\n",
    392               TALER_B2S (&rs->reserve_pub),
    393               TALER_amount2s (&rs->prev_balance.reserve_balance));
    394   return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
    395 }
    396 
    397 
    398 /**
    399  * Closure to the various callbacks we make while checking a reserve.
    400  */
    401 struct ReserveContext
    402 {
    403   /**
    404    * Map from hash of reserve's public key to a `struct ReserveSummary`.
    405    */
    406   struct GNUNET_CONTAINER_MultiHashMap *reserves;
    407 
    408   /**
    409    * Map from hash of denomination's public key to a
    410    * static string "revoked" for keys that have been revoked,
    411    * or "master signature invalid" in case the revocation is
    412    * there but bogus.
    413    */
    414   struct GNUNET_CONTAINER_MultiHashMap *revoked;
    415 
    416   /**
    417    * Transaction status code, set to error codes if applicable.
    418    */
    419   enum GNUNET_DB_QueryStatus qs;
    420 
    421 };
    422 
    423 
    424 /**
    425  * Create a new reserve for @a reserve_pub in @a rc.
    426  *
    427  * @param[in,out] rc context to update
    428  * @param reserve_pub key for which to create a reserve
    429  * @return NULL on error
    430  */
    431 static struct ReserveSummary *
    432 setup_reserve (struct ReserveContext *rc,
    433                const struct TALER_ReservePublicKeyP *reserve_pub)
    434 {
    435   struct ReserveSummary *rs;
    436   struct GNUNET_HashCode key;
    437   enum GNUNET_DB_QueryStatus qs;
    438 
    439   GNUNET_CRYPTO_hash (reserve_pub,
    440                       sizeof (*reserve_pub),
    441                       &key);
    442   rs = GNUNET_CONTAINER_multihashmap_get (rc->reserves,
    443                                           &key);
    444   if (NULL != rs)
    445     return rs;
    446   rs = GNUNET_new (struct ReserveSummary);
    447   rs->reserve_pub = *reserve_pub;
    448   GNUNET_assert (GNUNET_OK ==
    449                  TALER_amount_set_zero (TALER_ARL_currency,
    450                                         &rs->total_in));
    451   GNUNET_assert (GNUNET_OK ==
    452                  TALER_amount_set_zero (TALER_ARL_currency,
    453                                         &rs->total_out));
    454   GNUNET_assert (GNUNET_OK ==
    455                  TALER_amount_set_zero (TALER_ARL_currency,
    456                                         &rs->curr_balance.reserve_balance));
    457   GNUNET_assert (GNUNET_OK ==
    458                  TALER_amount_set_zero (TALER_ARL_currency,
    459                                         &rs->curr_balance.reserve_loss));
    460   GNUNET_assert (GNUNET_OK ==
    461                  TALER_amount_set_zero (TALER_ARL_currency,
    462                                         &rs->curr_balance.withdraw_fee_balance))
    463   ;
    464   GNUNET_assert (GNUNET_OK ==
    465                  TALER_amount_set_zero (TALER_ARL_currency,
    466                                         &rs->curr_balance.close_fee_balance));
    467   GNUNET_assert (GNUNET_OK ==
    468                  TALER_amount_set_zero (TALER_ARL_currency,
    469                                         &rs->curr_balance.purse_fee_balance));
    470   GNUNET_assert (GNUNET_OK ==
    471                  TALER_amount_set_zero (TALER_ARL_currency,
    472                                         &rs->curr_balance.open_fee_balance));
    473   GNUNET_assert (GNUNET_OK ==
    474                  TALER_amount_set_zero (TALER_ARL_currency,
    475                                         &rs->curr_balance.history_fee_balance));
    476   if (0 > (qs = load_auditor_reserve_summary (rs)))
    477   {
    478     GNUNET_free (rs);
    479     rc->qs = qs;
    480     return NULL;
    481   }
    482   GNUNET_assert (GNUNET_OK ==
    483                  GNUNET_CONTAINER_multihashmap_put (rc->reserves,
    484                                                     &key,
    485                                                     rs,
    486                                                     GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)
    487                  );
    488   return rs;
    489 }
    490 
    491 
    492 /**
    493  * Function called with details about incoming wire transfers.
    494  *
    495  * @param rc our `struct ReserveContext`
    496  * @param rowid unique serial ID for the refresh session in our DB
    497  * @param reserve_pub public key of the reserve (also the WTID)
    498  * @param credit amount that was received
    499  * @param sender_account_details information about the sender's bank account
    500  * @param wire_reference unique reference identifying the wire transfer
    501  * @param execution_date when did we receive the funds
    502  * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
    503  */
    504 static enum GNUNET_GenericReturnValue
    505 handle_reserve_in (
    506   struct ReserveContext *rc,
    507   uint64_t rowid,
    508   const struct TALER_ReservePublicKeyP *reserve_pub,
    509   const struct TALER_Amount *credit,
    510   const struct TALER_FullPayto sender_account_details,
    511   uint64_t wire_reference,
    512   struct GNUNET_TIME_Timestamp execution_date)
    513 {
    514   struct ReserveSummary *rs;
    515   struct GNUNET_TIME_Timestamp expiry;
    516 
    517   (void) wire_reference;
    518   /* should be monotonically increasing */
    519   GNUNET_assert (rowid >= TALER_ARL_USE_PP (reserves_reserve_in_serial_id));
    520   TALER_ARL_USE_PP (reserves_reserve_in_serial_id) = rowid + 1;
    521   rs = setup_reserve (rc,
    522                       reserve_pub);
    523   if (NULL == rs)
    524   {
    525     GNUNET_break (0);
    526     return GNUNET_SYSERR;
    527   }
    528   if (NULL == rs->sender_account.full_payto)
    529     rs->sender_account.full_payto
    530       = GNUNET_strdup (sender_account_details.full_payto);
    531   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    532               "Additional incoming wire transfer for reserve `%s' of %s\n",
    533               TALER_B2S (reserve_pub),
    534               TALER_amount2s (credit));
    535   expiry = GNUNET_TIME_absolute_to_timestamp (
    536     GNUNET_TIME_absolute_add (execution_date.abs_time,
    537                               idle_reserve_expiration_time));
    538   rs->a_expiration_date = GNUNET_TIME_timestamp_max (rs->a_expiration_date,
    539                                                      expiry);
    540   TALER_ARL_amount_add (&rs->total_in,
    541                         &rs->total_in,
    542                         credit);
    543   return GNUNET_OK;
    544 }
    545 
    546 
    547 /**
    548  * Function called with details about withdraw operations.  Verifies
    549  * the signature and updates the reserve's balance.
    550  *
    551  * @param rc our `struct ReserveContext`
    552  * @param rowid unique serial ID for the refresh session in our DB
    553  * @param num_denom_serials number of elements in @e denom_serials array
    554  * @param denom_serials array with length @e num_denom_serials of serial ID's of denominations in our DB
    555  * @param selected_h hash over the gamma-selected planchets
    556  * @param h_planchets running hash over all hashes of blinded planchets in the original withdraw request
    557  * @param blinding_seed the blinding seed for CS denominations that was provided during withdraw; might be NULL
    558  * @param age_proof_required true if the withdraw request required an age proof.
    559  * @param max_age if @e age_proof_required is true, the maximum age that was set on the coins.
    560  * @param noreveal_index if @e age_proof_required is true, the index that was returned by the exchange for the reveal phase.
    561  * @param reserve_pub public key of the reserve
    562  * @param reserve_sig signature over the withdraw operation
    563  * @param execution_date when did the wallet withdraw the coin
    564  * @param amount_with_fee amount that was withdrawn
    565  * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
    566  */
    567 static enum GNUNET_GenericReturnValue
    568 handle_withdrawals (
    569   struct ReserveContext *rc,
    570   uint64_t rowid,
    571   size_t num_denom_serials,
    572   const uint64_t *denom_serials,
    573   const struct TALER_HashBlindedPlanchetsP *selected_h,
    574   const struct TALER_HashBlindedPlanchetsP *h_planchets,
    575   const struct TALER_BlindingMasterSeedP *blinding_seed,
    576   bool age_proof_required,
    577   uint8_t max_age,
    578   uint8_t noreveal_index,
    579   const struct TALER_ReservePublicKeyP *reserve_pub,
    580   const struct TALER_ReserveSignatureP *reserve_sig,
    581   struct GNUNET_TIME_Timestamp execution_date,
    582   const struct TALER_Amount *amount_with_fee)
    583 {
    584   struct ReserveSummary *rs;
    585   struct TALER_Amount auditor_amount;
    586   struct TALER_Amount auditor_fee;
    587   struct TALER_Amount auditor_amount_with_fee;
    588   struct TALER_AgeMask age_mask = { 0 };
    589   bool have_age_mask = false;
    590   bool missing_denom = false;
    591   enum GNUNET_DB_QueryStatus qs;
    592 
    593   /* should be monotonically increasing */
    594   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    595               "Analyzing withdrawal row %llu\n",
    596               (unsigned long long) rowid);
    597   GNUNET_assert (rowid >= TALER_ARL_USE_PP (reserves_withdraw_serial_id));
    598   TALER_ARL_USE_PP (reserves_withdraw_serial_id) = rowid + 1;
    599 
    600   GNUNET_assert (GNUNET_OK ==
    601                  TALER_amount_set_zero (TALER_ARL_currency,
    602                                         &auditor_amount));
    603   GNUNET_assert (GNUNET_OK ==
    604                  TALER_amount_set_zero (TALER_ARL_currency,
    605                                         &auditor_fee));
    606   GNUNET_assert (GNUNET_OK ==
    607                  TALER_amount_set_zero (TALER_ARL_currency,
    608                                         &auditor_amount_with_fee));
    609   rs = setup_reserve (rc,
    610                       reserve_pub);
    611   if (NULL == rs)
    612   {
    613     GNUNET_break (0);
    614     return GNUNET_SYSERR;
    615   }
    616   for (size_t i = 0; i < num_denom_serials; i++)
    617   {
    618     const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue;
    619 
    620     /* lookup denomination pub data (make sure denom_pub is valid, establish fees);
    621        initializes wsrd.h_denomination_pub! */
    622     qs = TALER_ARL_get_denomination_info_by_serial (denom_serials[i],
    623                                                     &issue);
    624     if (0 > qs)
    625     {
    626       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    627       if (GNUNET_DB_STATUS_HARD_ERROR == qs)
    628         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    629                     "Hard database error trying to get denomination by serial %llu (%s) from database!\n",
    630                     (unsigned long long) denom_serials[i],
    631                     GNUNET_h2s (&h_planchets->hash));
    632       rc->qs = qs;
    633       return GNUNET_SYSERR;
    634     }
    635     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    636     {
    637       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    638                   "Denomination #%llu not found\n",
    639                   (unsigned long long) denom_serials[i]);
    640       report_row_inconsistency ("withdraw",
    641                                 rowid,
    642                                 "denomination key not found");
    643       if (global_qs < 0)
    644         return GNUNET_SYSERR;
    645       /* We cannot account for this planchet, but the other planchets
    646          of the batch must still be processed. */
    647       missing_denom = true;
    648       continue;
    649     }
    650     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    651                 "Analyzing withdrawn denomination #%llu (%s)\n",
    652                 (unsigned long long) denom_serials[i],
    653                 TALER_amount2s (&issue->value));
    654     if (! have_age_mask)
    655     {
    656       age_mask = issue->age_mask;
    657       have_age_mask = true;
    658     }
    659     else if (age_mask.bits != issue->age_mask.bits)
    660     {
    661       /* All denominations of one batch must share the age mask, as the
    662          reserve signature is made over a single age mask. */
    663       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    664                   "Denominations of withdraw row %llu disagree on the age mask\n",
    665                   (unsigned long long) rowid);
    666       report_row_inconsistency ("withdraw",
    667                                 rowid,
    668                                 "denominations of the batch disagree on the age mask");
    669       if (global_qs < 0)
    670         return GNUNET_SYSERR;
    671     }
    672 
    673     /* check that execution date is within withdraw range for denom_pub  */
    674     GNUNET_log (
    675       GNUNET_ERROR_TYPE_DEBUG,
    676       "Checking withdraw timing: %llu, expire: %llu, timing: %llu\n",
    677       (unsigned long long) issue->start.abs_time.abs_value_us,
    678       (unsigned long long) issue->expire_withdraw.abs_time.abs_value_us,
    679       (unsigned long long) execution_date.abs_time.abs_value_us);
    680     if (GNUNET_TIME_timestamp_cmp (issue->start,
    681                                    >,
    682                                    execution_date) ||
    683         GNUNET_TIME_timestamp_cmp (issue->expire_withdraw,
    684                                    <,
    685                                    execution_date))
    686     {
    687       struct TALER_AUDITORDB_DenominationKeyValidityWithdrawInconsistency
    688         dkvwi = {
    689         .problem_row_id = rowid,
    690         .execution_date = execution_date.abs_time,
    691         .denompub_h = issue->denom_hash,
    692         .reserve_pub = *reserve_pub
    693       };
    694 
    695       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    696                   "Withdraw outside of denomination #%llu validity period detected\n",
    697                   (unsigned long long) denom_serials[i]);
    698       qs =
    699         TALER_AUDITORDB_insert_denomination_key_validity_withdraw_inconsistency
    700         (
    701           TALER_ARL_adb,
    702           &dkvwi);
    703 
    704       if (qs < 0)
    705       {
    706         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    707         rc->qs = qs;
    708         return GNUNET_SYSERR;
    709       }
    710     }
    711     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    712                 "Adding withdraw fee of denomination (%s)\n",
    713                 TALER_amount2s (&issue->fees.withdraw));
    714     TALER_ARL_amount_add (&auditor_amount,
    715                           &auditor_amount,
    716                           &issue->value);
    717     TALER_ARL_amount_add (&auditor_fee,
    718                           &auditor_fee,
    719                           &issue->fees.withdraw);
    720     {
    721       struct TALER_Amount issue_amount_with_fee;
    722 
    723       TALER_ARL_amount_add (&issue_amount_with_fee,
    724                             &issue->value,
    725                             &issue->fees.withdraw);
    726       TALER_ARL_amount_add (&auditor_amount_with_fee,
    727                             &auditor_amount_with_fee,
    728                             &issue_amount_with_fee);
    729     }
    730     TALER_ARL_amount_add (&rs->curr_balance.withdraw_fee_balance,
    731                           &rs->curr_balance.withdraw_fee_balance,
    732                           &issue->fees.withdraw);
    733     TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_withdraw_fee_revenue),
    734                           &TALER_ARL_USE_AB (reserves_withdraw_fee_revenue),
    735                           &issue->fees.withdraw);
    736   } /* end for all coins in batch */
    737 
    738   if (missing_denom)
    739   {
    740     /* We do not know the value of the entire batch, so we can check
    741        neither the reserve signature nor the total; still debit the
    742        reserve by the amount the exchange claims. The row inconsistency
    743        reported above states that we could not verify it. */
    744     TALER_ARL_amount_add (&rs->total_out,
    745                           &rs->total_out,
    746                           amount_with_fee);
    747     return GNUNET_OK;
    748   }
    749   if (age_proof_required &&
    750       (! have_age_mask))
    751   {
    752     /* Without a denomination we do not know the age mask the reserve
    753        signature was made over. */
    754     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    755                 "Age-restricted withdraw row %llu has no denominations\n",
    756                 (unsigned long long) rowid);
    757     report_row_inconsistency ("withdraw",
    758                               rowid,
    759                               "no denomination given for age-restricted withdraw");
    760     if (global_qs < 0)
    761       return GNUNET_SYSERR;
    762     return GNUNET_OK;
    763   }
    764 
    765   /* check reserve_sig (first: setup remaining members of wsrd) */
    766   if (GNUNET_OK !=
    767       TALER_wallet_withdraw_verify (
    768         &auditor_amount,
    769         &auditor_fee,
    770         h_planchets,
    771         blinding_seed,
    772         age_proof_required
    773         ? &age_mask
    774         : NULL,
    775         age_proof_required
    776         ? max_age
    777         : 0,
    778         reserve_pub,
    779         reserve_sig))
    780   {
    781     struct TALER_AUDITORDB_BadSigLosses bsl = {
    782       .problem_row_id = rowid,
    783       .operation = (char *) "withdraw",
    784       .loss = *amount_with_fee,
    785       .operation_specific_pub = reserve_pub->eddsa_pub
    786     };
    787 
    788     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    789                 "Withdraw signature invalid (row #%llu)\n",
    790                 (unsigned long long) rowid);
    791     qs = TALER_AUDITORDB_insert_bad_sig_losses (
    792       TALER_ARL_adb,
    793       &bsl);
    794     if (qs < 0)
    795     {
    796       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    797       rc->qs = qs;
    798       return GNUNET_SYSERR;
    799     }
    800     TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_total_bad_sig_loss),
    801                           &TALER_ARL_USE_AB (reserves_total_bad_sig_loss),
    802                           amount_with_fee);
    803     return GNUNET_OK;     /* exit function here, we cannot add this to the legitimate withdrawals */
    804   }
    805 
    806   if (0 !=
    807       TALER_amount_cmp (&auditor_amount_with_fee,
    808                         amount_with_fee))
    809   {
    810     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    811                 "Withdraw fee inconsistent (row #%llu)\n",
    812                 (unsigned long long) rowid);
    813     report_row_inconsistency ("withdraw",
    814                               rowid,
    815                               "amount with fee from exchange does not match denomination value plus fee");
    816     if (global_qs < 0)
    817     {
    818       GNUNET_break (0);
    819       return GNUNET_SYSERR;
    820     }
    821   }
    822   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    823               "Reserve `%s' reduced by %s from withdraw\n",
    824               TALER_B2S (reserve_pub),
    825               TALER_amount2s (&auditor_amount_with_fee));
    826   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    827               "Increasing withdraw profits by fee %s\n",
    828               TALER_amount2s (&auditor_fee));
    829   TALER_ARL_amount_add (&rs->total_out,
    830                         &rs->total_out,
    831                         &auditor_amount_with_fee);
    832   return GNUNET_OK;
    833 }
    834 
    835 
    836 /**
    837  * Function called with details about withdraw operations.  Verifies
    838  * the signature and updates the reserve's balance.
    839  *
    840  * @param rc our `struct ReserveContext`
    841  * @param rowid unique serial ID for the refresh session in our DB
    842  * @param timestamp when did we receive the recoup request
    843  * @param amount how much should be added back to the reserve
    844  * @param reserve_pub public key of the reserve
    845  * @param coin public information about the coin, denomination signature is
    846  *        already verified in #check_recoup()
    847  * @param denom_pub public key of the denomionation of @a coin
    848  * @param coin_sig signature with @e coin_pub of type #TALER_SIGNATURE_WALLET_COIN_RECOUP
    849  * @param coin_blind blinding factor used to blind the coin
    850  * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
    851  */
    852 static enum GNUNET_GenericReturnValue
    853 handle_recoup_by_reserve (
    854   struct ReserveContext *rc,
    855   uint64_t rowid,
    856   struct GNUNET_TIME_Timestamp timestamp,
    857   const struct TALER_Amount *amount,
    858   const struct TALER_ReservePublicKeyP *reserve_pub,
    859   const struct TALER_CoinPublicInfo *coin,
    860   const struct TALER_DenominationPublicKey *denom_pub,
    861   const struct TALER_CoinSpendSignatureP *coin_sig,
    862   const union GNUNET_CRYPTO_BlindingSecretP *coin_blind)
    863 {
    864   struct ReserveSummary *rs;
    865   struct GNUNET_TIME_Timestamp expiry;
    866   struct TALER_MasterSignatureP msig;
    867   uint64_t rev_rowid;
    868   enum GNUNET_DB_QueryStatus qs;
    869   const char *rev;
    870 
    871   (void) denom_pub;
    872   /* should be monotonically increasing */
    873   GNUNET_assert (rowid >= TALER_ARL_USE_PP (reserves_reserve_recoup_serial_id));
    874   TALER_ARL_USE_PP (reserves_reserve_recoup_serial_id) = rowid + 1;
    875   /* We know that denom_pub matches denom_pub_hash because this
    876      is how the SQL statement joined the tables. */
    877   if (GNUNET_OK !=
    878       TALER_wallet_recoup_verify (&coin->denom_pub_hash,
    879                                   coin_blind,
    880                                   &coin->coin_pub,
    881                                   coin_sig))
    882   {
    883     struct TALER_AUDITORDB_BadSigLosses bslr = {
    884       .problem_row_id = rowid,
    885       .operation = (char *) "recoup",
    886       .loss = *amount,
    887       .operation_specific_pub = coin->coin_pub.eddsa_pub
    888     };
    889 
    890     qs = TALER_AUDITORDB_insert_bad_sig_losses (
    891       TALER_ARL_adb,
    892       &bslr);
    893 
    894     if (qs < 0)
    895     {
    896       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    897       rc->qs = qs;
    898       return GNUNET_SYSERR;
    899     }
    900     TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_total_bad_sig_loss),
    901                           &TALER_ARL_USE_AB (reserves_total_bad_sig_loss),
    902                           amount);
    903     /* We continue here intentionally to credit the reserve anyway,
    904        we already accounted for the loss, so we need to continue to
    905        avoid getting another error when the reserve is drained which
    906        would double-count the damage. */
    907   }
    908 
    909   /* check that the coin was eligible for recoup!*/
    910   rev = GNUNET_CONTAINER_multihashmap_get (rc->revoked,
    911                                            &coin->denom_pub_hash.hash);
    912   if (NULL == rev)
    913   {
    914     qs = TALER_EXCHANGEDB_get_denomination_revocation (TALER_ARL_edb,
    915                                                        &coin->denom_pub_hash,
    916                                                        &msig,
    917                                                        &rev_rowid);
    918     if (0 > qs)
    919     {
    920       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    921       rc->qs = qs;
    922       return GNUNET_SYSERR;
    923     }
    924     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    925     {
    926       report_row_inconsistency ("recoup",
    927                                 rowid,
    928                                 "denomination key not in revocation set");
    929       if (global_qs < 0)
    930         return GNUNET_SYSERR;
    931       TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_reserve_loss),
    932                             &TALER_ARL_USE_AB (reserves_reserve_loss),
    933                             amount);
    934     }
    935     else
    936     {
    937       if (GNUNET_OK !=
    938           TALER_exchange_offline_denomination_revoke_verify (
    939             &coin->denom_pub_hash,
    940             &TALER_ARL_master_pub,
    941             &msig))
    942       {
    943         rev = "master signature invalid";
    944       }
    945       else
    946       {
    947         rev = "revoked";
    948       }
    949       GNUNET_assert (
    950         GNUNET_OK ==
    951         GNUNET_CONTAINER_multihashmap_put (
    952           rc->revoked,
    953           &coin->denom_pub_hash.hash,
    954           (void *) rev,
    955           GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
    956     }
    957   }
    958   else
    959   {
    960     rev_rowid = 0;   /* reported elsewhere */
    961   }
    962   if ((NULL != rev) &&
    963       (0 == strcmp (rev,
    964                     "master signature invalid")))
    965   {
    966     struct TALER_AUDITORDB_BadSigLosses bslrm = {
    967       .problem_row_id = rev_rowid,
    968       .operation = (char *) "recoup-master",
    969       .loss = *amount,
    970       .operation_specific_pub = TALER_ARL_master_pub.eddsa_pub
    971     };
    972 
    973     qs = TALER_AUDITORDB_insert_bad_sig_losses (
    974       TALER_ARL_adb,
    975       &bslrm);
    976 
    977     if (qs < 0)
    978     {
    979       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    980       rc->qs = qs;
    981       return GNUNET_SYSERR;
    982     }
    983     TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_total_bad_sig_loss),
    984                           &TALER_ARL_USE_AB (reserves_total_bad_sig_loss),
    985                           amount);
    986   }
    987 
    988   rs = setup_reserve (rc,
    989                       reserve_pub);
    990   if (NULL == rs)
    991   {
    992     GNUNET_break (0);
    993     return GNUNET_SYSERR;
    994   }
    995   TALER_ARL_amount_add (&rs->total_in,
    996                         &rs->total_in,
    997                         amount);
    998   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    999               "Additional /recoup value to for reserve `%s' of %s\n",
   1000               TALER_B2S (reserve_pub),
   1001               TALER_amount2s (amount));
   1002   expiry = GNUNET_TIME_absolute_to_timestamp (
   1003     GNUNET_TIME_absolute_add (timestamp.abs_time,
   1004                               idle_reserve_expiration_time));
   1005   rs->a_expiration_date = GNUNET_TIME_timestamp_max (rs->a_expiration_date,
   1006                                                      expiry);
   1007   return GNUNET_OK;
   1008 }
   1009 
   1010 
   1011 /**
   1012  * Obtain the closing fee for a transfer at @a time for target
   1013  * @a receiver_account.
   1014  *
   1015  * @param receiver_account payto:// URI of the target account
   1016  * @param atime when was the transfer made
   1017  * @param[out] fee set to the closing fee
   1018  * @return #GNUNET_OK on success
   1019  */
   1020 static enum GNUNET_GenericReturnValue
   1021 get_closing_fee (const struct TALER_FullPayto receiver_account,
   1022                  struct GNUNET_TIME_Timestamp atime,
   1023                  struct TALER_Amount *fee)
   1024 {
   1025   struct TALER_MasterSignatureP master_sig;
   1026   struct GNUNET_TIME_Timestamp start_date;
   1027   struct GNUNET_TIME_Timestamp end_date;
   1028   struct TALER_WireFeeSet fees;
   1029   char *method;
   1030   /* Only set by TALER_EXCHANGEDB_get_wire_fee() if a fee was found;
   1031      0 means 'no such row' in the diagnostic below. */
   1032   uint64_t rowid = 0;
   1033 
   1034   method = TALER_payto_get_method (receiver_account.full_payto);
   1035   if (NULL == method)
   1036   {
   1037     report_row_inconsistency ("closing-fee",
   1038                               0,
   1039                               "specified wire address lacks method");
   1040     return GNUNET_SYSERR;
   1041   }
   1042   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1043               "Method is `%s'\n",
   1044               method);
   1045   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
   1046       TALER_EXCHANGEDB_get_wire_fee (TALER_ARL_edb,
   1047                                      method,
   1048                                      atime,
   1049                                      &rowid,
   1050                                      &start_date,
   1051                                      &end_date,
   1052                                      &fees,
   1053                                      &master_sig))
   1054   {
   1055     char *diag;
   1056 
   1057     GNUNET_asprintf (&diag,
   1058                      "closing fee for `%s' unavailable at %s\n",
   1059                      method,
   1060                      GNUNET_TIME_timestamp2s (atime));
   1061     report_row_inconsistency ("closing-fee",
   1062                               rowid,
   1063                               diag);
   1064     GNUNET_free (diag);
   1065     GNUNET_free (method);
   1066     return GNUNET_SYSERR;
   1067   }
   1068   *fee = fees.closing;
   1069   GNUNET_free (method);
   1070   return GNUNET_OK;
   1071 }
   1072 
   1073 
   1074 /**
   1075  * Function called about reserve opening operations.
   1076  *
   1077  * @param rc closure
   1078  * @param rowid row identifier used to uniquely identify the reserve closing operation
   1079  * @param reserve_payment how much to pay from the
   1080  *        reserve's own balance for opening the reserve
   1081  * @param request_timestamp when was the request created
   1082  * @param reserve_expiration desired expiration time for the reserve
   1083  * @param purse_limit minimum number of purses the client
   1084  *       wants to have concurrently open for this reserve
   1085  * @param reserve_pub public key of the reserve
   1086  * @param reserve_sig signature affirming the operation
   1087  * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
   1088  */
   1089 static enum GNUNET_GenericReturnValue
   1090 handle_reserve_open (
   1091   struct ReserveContext *rc,
   1092   uint64_t rowid,
   1093   const struct TALER_Amount *reserve_payment,
   1094   struct GNUNET_TIME_Timestamp request_timestamp,
   1095   struct GNUNET_TIME_Timestamp reserve_expiration,
   1096   uint32_t purse_limit,
   1097   const struct TALER_ReservePublicKeyP *reserve_pub,
   1098   const struct TALER_ReserveSignatureP *reserve_sig)
   1099 {
   1100   struct ReserveSummary *rs;
   1101   enum GNUNET_DB_QueryStatus qs;
   1102 
   1103   /* should be monotonically increasing */
   1104   GNUNET_assert (rowid >= TALER_ARL_USE_PP (reserves_reserve_open_serial_id));
   1105   TALER_ARL_USE_PP (reserves_reserve_open_serial_id) = rowid + 1;
   1106 
   1107   rs = setup_reserve (rc,
   1108                       reserve_pub);
   1109   if (NULL == rs)
   1110   {
   1111     GNUNET_break (0);
   1112     return GNUNET_SYSERR;
   1113   }
   1114   if (GNUNET_OK !=
   1115       TALER_wallet_reserve_open_verify (reserve_payment,
   1116                                         request_timestamp,
   1117                                         reserve_expiration,
   1118                                         purse_limit,
   1119                                         reserve_pub,
   1120                                         reserve_sig))
   1121   {
   1122     struct TALER_AUDITORDB_BadSigLosses bsl = {
   1123       .problem_row_id = rowid,
   1124       .operation = (char *) "reserve-open",
   1125       .loss = *reserve_payment,
   1126       .operation_specific_pub = reserve_pub->eddsa_pub
   1127     };
   1128 
   1129     qs = TALER_AUDITORDB_insert_bad_sig_losses (
   1130       TALER_ARL_adb,
   1131       &bsl);
   1132 
   1133     if (qs < 0)
   1134     {
   1135       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1136       rc->qs = qs;
   1137       return GNUNET_SYSERR;
   1138     }
   1139     TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_total_bad_sig_loss),
   1140                           &TALER_ARL_USE_AB (reserves_total_bad_sig_loss),
   1141                           reserve_payment);
   1142     return GNUNET_OK;
   1143   }
   1144   TALER_ARL_amount_add (&rs->curr_balance.open_fee_balance,
   1145                         &rs->curr_balance.open_fee_balance,
   1146                         reserve_payment);
   1147   TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_open_fee_revenue),
   1148                         &TALER_ARL_USE_AB (reserves_open_fee_revenue),
   1149                         reserve_payment);
   1150   TALER_ARL_amount_add (&rs->total_out,
   1151                         &rs->total_out,
   1152                         reserve_payment);
   1153   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1154               "Additional open operation for reserve `%s' of %s\n",
   1155               TALER_B2S (reserve_pub),
   1156               TALER_amount2s (reserve_payment));
   1157   return GNUNET_OK;
   1158 }
   1159 
   1160 
   1161 /**
   1162  * Function called about reserve closing operations
   1163  * the aggregator triggered.
   1164  *
   1165  * @param rc closure
   1166  * @param rowid row identifier used to uniquely identify the reserve closing operation
   1167  * @param execution_date when did we execute the close operation
   1168  * @param amount_with_fee how much did we debit the reserve
   1169  * @param closing_fee how much did we charge for closing the reserve
   1170  * @param reserve_pub public key of the reserve
   1171  * @param receiver_account where did we send the funds
   1172  * @param transfer_details details about the wire transfer
   1173  * @param close_request_row which close request triggered the operation?
   1174  *         0 if it was a timeout
   1175  * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
   1176  */
   1177 static enum GNUNET_GenericReturnValue
   1178 handle_reserve_closed (
   1179   struct ReserveContext *rc,
   1180   uint64_t rowid,
   1181   struct GNUNET_TIME_Timestamp execution_date,
   1182   const struct TALER_Amount *amount_with_fee,
   1183   const struct TALER_Amount *closing_fee,
   1184   const struct TALER_ReservePublicKeyP *reserve_pub,
   1185   const struct TALER_FullPayto receiver_account,
   1186   const struct TALER_WireTransferIdentifierRawP *transfer_details,
   1187   uint64_t close_request_row)
   1188 {
   1189   struct ReserveSummary *rs;
   1190 
   1191   (void) transfer_details;
   1192   /* should be monotonically increasing */
   1193   GNUNET_assert (rowid >= TALER_ARL_USE_PP (reserves_reserve_close_serial_id));
   1194   TALER_ARL_USE_PP (reserves_reserve_close_serial_id) = rowid + 1;
   1195 
   1196   rs = setup_reserve (rc,
   1197                       reserve_pub);
   1198   if (NULL == rs)
   1199   {
   1200     GNUNET_break (0);
   1201     return GNUNET_SYSERR;
   1202   }
   1203   {
   1204     struct TALER_Amount expected_fee;
   1205 
   1206     /* verify closing_fee is correct! */
   1207     if (GNUNET_OK !=
   1208         get_closing_fee (receiver_account,
   1209                          execution_date,
   1210                          &expected_fee))
   1211     {
   1212       GNUNET_break (0);
   1213     }
   1214     else
   1215     {
   1216       /* taler-exchange-closer never charges more than the remaining
   1217          balance: if the closing fee is not below it, the entire balance
   1218          is booked as the fee and nothing is wired. */
   1219       if (0 > TALER_amount_cmp (amount_with_fee,
   1220                                 &expected_fee))
   1221         expected_fee = *amount_with_fee;
   1222       if (0 != TALER_amount_cmp (&expected_fee,
   1223                                  closing_fee))
   1224       {
   1225         report_amount_arithmetic_inconsistency (
   1226           "closing aggregation fee",
   1227           rowid,
   1228           closing_fee,
   1229           &expected_fee,
   1230           1);
   1231         if (global_qs < 0)
   1232           return GNUNET_SYSERR;
   1233       }
   1234     }
   1235   }
   1236 
   1237   TALER_ARL_amount_add (&rs->curr_balance.close_fee_balance,
   1238                         &rs->curr_balance.close_fee_balance,
   1239                         closing_fee);
   1240   TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_close_fee_revenue),
   1241                         &TALER_ARL_USE_AB (reserves_close_fee_revenue),
   1242                         closing_fee);
   1243   TALER_ARL_amount_add (&rs->total_out,
   1244                         &rs->total_out,
   1245                         amount_with_fee);
   1246   if (0 != close_request_row)
   1247   {
   1248     struct TALER_ReserveSignatureP reserve_sig;
   1249     struct GNUNET_TIME_Timestamp request_timestamp;
   1250     struct TALER_Amount close_balance;
   1251     struct TALER_Amount close_fee;
   1252     struct TALER_FullPayto payto_uri = { NULL };
   1253     enum GNUNET_DB_QueryStatus qs;
   1254 
   1255     qs = TALER_EXCHANGEDB_get_reserve_close_request_info (
   1256       TALER_ARL_edb,
   1257       reserve_pub,
   1258       close_request_row,
   1259       &reserve_sig,
   1260       &request_timestamp,
   1261       &close_balance,
   1262       &close_fee,
   1263       &payto_uri);
   1264     if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
   1265     {
   1266       report_row_inconsistency ("reserves_close",
   1267                                 rowid,
   1268                                 "reserve close request unknown");
   1269       if (global_qs < 0)
   1270         return GNUNET_SYSERR;
   1271     }
   1272     else
   1273     {
   1274       struct TALER_FullPaytoHashP h_payto;
   1275 
   1276       TALER_full_payto_hash (payto_uri,
   1277                              &h_payto);
   1278       if (GNUNET_OK !=
   1279           TALER_wallet_reserve_close_verify (
   1280             request_timestamp,
   1281             &h_payto,
   1282             reserve_pub,
   1283             &reserve_sig))
   1284       {
   1285         struct TALER_AUDITORDB_BadSigLosses bsl = {
   1286           .problem_row_id = close_request_row,
   1287           .operation = (char *) "close-request",
   1288           .loss = *amount_with_fee,
   1289           .operation_specific_pub = reserve_pub->eddsa_pub
   1290         };
   1291 
   1292         qs = TALER_AUDITORDB_insert_bad_sig_losses (
   1293           TALER_ARL_adb,
   1294           &bsl);
   1295 
   1296         if (qs < 0)
   1297         {
   1298           GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1299           rc->qs = qs;
   1300           GNUNET_free (payto_uri.full_payto);
   1301           return GNUNET_SYSERR;
   1302         }
   1303         TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_total_bad_sig_loss),
   1304                               &TALER_ARL_USE_AB (reserves_total_bad_sig_loss),
   1305                               amount_with_fee);
   1306       }
   1307     }
   1308     if ( (NULL == payto_uri.full_payto) &&
   1309          (NULL == rs->sender_account.full_payto) )
   1310     {
   1311       GNUNET_break (! rs->had_ri);
   1312       report_row_inconsistency ("reserves_close",
   1313                                 rowid,
   1314                                 "target account not verified, auditor does not know reserve");
   1315       if (global_qs < 0)
   1316         return GNUNET_SYSERR;
   1317     }
   1318     if (NULL == payto_uri.full_payto)
   1319     {
   1320       if ((NULL == rs->sender_account.full_payto) ||
   1321           (0 != TALER_full_payto_cmp (rs->sender_account,
   1322                                       receiver_account)))
   1323       {
   1324         report_row_inconsistency ("reserves_close",
   1325                                   rowid,
   1326                                   "target account does not match origin account");
   1327         if (global_qs < 0)
   1328           return GNUNET_SYSERR;
   1329       }
   1330     }
   1331     else
   1332     {
   1333       if (0 != TALER_full_payto_cmp (payto_uri,
   1334                                      receiver_account))
   1335       {
   1336         report_row_inconsistency ("reserves_close",
   1337                                   rowid,
   1338                                   "target account does not match origin account");
   1339         if (global_qs < 0)
   1340         {
   1341           GNUNET_free (payto_uri.full_payto);
   1342           return GNUNET_SYSERR;
   1343         }
   1344       }
   1345     }
   1346     GNUNET_free (payto_uri.full_payto);
   1347   }
   1348   else
   1349   {
   1350     if (NULL == rs->sender_account.full_payto)
   1351     {
   1352       GNUNET_break (! rs->had_ri);
   1353       report_row_inconsistency ("reserves_close",
   1354                                 rowid,
   1355                                 "target account not verified, auditor does not know reserve");
   1356       if (global_qs < 0)
   1357         return GNUNET_SYSERR;
   1358     }
   1359     else if (0 != TALER_full_payto_cmp (rs->sender_account,
   1360                                         receiver_account))
   1361     {
   1362       report_row_inconsistency ("reserves_close",
   1363                                 rowid,
   1364                                 "target account does not match origin account");
   1365       if (global_qs < 0)
   1366         return GNUNET_SYSERR;
   1367     }
   1368   }
   1369 
   1370   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1371               "Additional closing operation for reserve `%s' of %s\n",
   1372               TALER_B2S (reserve_pub),
   1373               TALER_amount2s (amount_with_fee));
   1374   return GNUNET_OK;
   1375 }
   1376 
   1377 
   1378 /**
   1379  * Function called with details about account merge requests that have been
   1380  * made, with the goal of accounting for the merge fee paid by the reserve (if
   1381  * applicable).
   1382  *
   1383  * @param rc closure
   1384  * @param rowid unique serial ID for the deposit in our DB
   1385  * @param reserve_pub reserve affected by the merge
   1386  * @param purse_pub purse being merged
   1387  * @param h_contract_terms hash over contract of the purse
   1388  * @param purse_expiration when would the purse expire
   1389  * @param amount total amount in the purse
   1390  * @param min_age minimum age of all coins deposited into the purse
   1391  * @param flags how was the purse created
   1392  * @param purse_fee if a purse fee was paid, how high is it
   1393  * @param merge_timestamp when was the merge approved
   1394  * @param reserve_sig signature by reserve approving the merge
   1395  * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
   1396  */
   1397 static enum GNUNET_GenericReturnValue
   1398 handle_account_merged (
   1399   struct ReserveContext *rc,
   1400   uint64_t rowid,
   1401   const struct TALER_ReservePublicKeyP *reserve_pub,
   1402   const struct TALER_PurseContractPublicKeyP *purse_pub,
   1403   const struct TALER_PrivateContractHashP *h_contract_terms,
   1404   struct GNUNET_TIME_Timestamp purse_expiration,
   1405   const struct TALER_Amount *amount,
   1406   uint32_t min_age,
   1407   enum TALER_WalletAccountMergeFlags flags,
   1408   const struct TALER_Amount *purse_fee,
   1409   struct GNUNET_TIME_Timestamp merge_timestamp,
   1410   const struct TALER_ReserveSignatureP *reserve_sig)
   1411 {
   1412   struct ReserveSummary *rs;
   1413   enum GNUNET_DB_QueryStatus qs;
   1414 
   1415   /* should be monotonically increasing */
   1416   GNUNET_assert (rowid >= TALER_ARL_USE_PP (reserves_account_merges_serial_id));
   1417   TALER_ARL_USE_PP (reserves_account_merges_serial_id) = rowid + 1;
   1418   if (GNUNET_OK !=
   1419       TALER_wallet_account_merge_verify (merge_timestamp,
   1420                                          purse_pub,
   1421                                          purse_expiration,
   1422                                          h_contract_terms,
   1423                                          amount,
   1424                                          purse_fee,
   1425                                          min_age,
   1426                                          flags,
   1427                                          reserve_pub,
   1428                                          reserve_sig))
   1429   {
   1430     struct TALER_AUDITORDB_BadSigLosses bsl = {
   1431       .problem_row_id = rowid,
   1432       .operation = (char *) "account-merge",
   1433       .loss = *purse_fee,
   1434       .operation_specific_pub = reserve_pub->eddsa_pub
   1435     };
   1436 
   1437     qs = TALER_AUDITORDB_insert_bad_sig_losses (
   1438       TALER_ARL_adb,
   1439       &bsl);
   1440     if (qs < 0)
   1441     {
   1442       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1443       rc->qs = qs;
   1444       return GNUNET_SYSERR;
   1445     }
   1446     TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_total_bad_sig_loss),
   1447                           &TALER_ARL_USE_AB (reserves_total_bad_sig_loss),
   1448                           purse_fee);
   1449     return GNUNET_OK;
   1450   }
   1451   if ((flags & TALER_WAMF_MERGE_MODE_MASK) !=
   1452       TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE)
   1453     return GNUNET_OK; /* no impact on reserve balance */
   1454   rs = setup_reserve (rc,
   1455                       reserve_pub);
   1456   if (NULL == rs)
   1457   {
   1458     GNUNET_break (0);
   1459     return GNUNET_SYSERR;
   1460   }
   1461   TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_purse_fee_revenue),
   1462                         &TALER_ARL_USE_AB (reserves_purse_fee_revenue),
   1463                         purse_fee);
   1464   TALER_ARL_amount_add (&rs->curr_balance.purse_fee_balance,
   1465                         &rs->curr_balance.purse_fee_balance,
   1466                         purse_fee);
   1467   TALER_ARL_amount_add (&rs->total_out,
   1468                         &rs->total_out,
   1469                         purse_fee);
   1470   return GNUNET_OK;
   1471 }
   1472 
   1473 
   1474 /**
   1475  * Function called with details about a purse that was merged into an account.
   1476  * Only updates the reserve balance, the actual verifications are done in the
   1477  * purse helper.
   1478  *
   1479  * @param rc closure
   1480  * @param rowid unique serial ID for the refund in our DB
   1481  * @param purse_pub public key of the purse
   1482  * @param reserve_pub which reserve is the purse credited to
   1483  * @param purse_value what is the target value of the purse
   1484  * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
   1485  */
   1486 static enum GNUNET_GenericReturnValue
   1487 purse_decision_cb (struct ReserveContext *rc,
   1488                    uint64_t rowid,
   1489                    const struct TALER_PurseContractPublicKeyP *purse_pub,
   1490                    const struct TALER_ReservePublicKeyP *reserve_pub,
   1491                    const struct TALER_Amount *purse_value)
   1492 {
   1493   struct ReserveSummary *rs;
   1494 
   1495   GNUNET_assert (rowid >= TALER_ARL_USE_PP (
   1496                    reserves_purse_decisions_serial_id)); /* should be monotonically increasing */
   1497   TALER_ARL_USE_PP (reserves_purse_decisions_serial_id) = rowid + 1;
   1498   rs = setup_reserve (rc,
   1499                       reserve_pub);
   1500   if (NULL == rs)
   1501   {
   1502     GNUNET_break (0);
   1503     return GNUNET_SYSERR;
   1504   }
   1505   TALER_ARL_amount_add (&rs->total_in,
   1506                         &rs->total_in,
   1507                         purse_value);
   1508   return GNUNET_OK;
   1509 }
   1510 
   1511 
   1512 /**
   1513  * Check that the reserve summary matches what the exchange database
   1514  * thinks about the reserve, and update our own state of the reserve.
   1515  *
   1516  * Remove all reserves that we are happy with from the DB.
   1517  *
   1518  * @param cls our `struct ReserveContext`
   1519  * @param key hash of the reserve public key
   1520  * @param value a `struct ReserveSummary`
   1521  * @return #GNUNET_OK to process more entries
   1522  */
   1523 static enum GNUNET_GenericReturnValue
   1524 verify_reserve_balance (void *cls,
   1525                         const struct GNUNET_HashCode *key,
   1526                         void *value)
   1527 {
   1528   struct ReserveContext *rc = cls;
   1529   struct ReserveSummary *rs = value;
   1530   struct TALER_Amount mbalance;
   1531   struct TALER_Amount nbalance;
   1532   enum GNUNET_DB_QueryStatus qs;
   1533   enum GNUNET_GenericReturnValue ret;
   1534 
   1535   ret = GNUNET_OK;
   1536   /* Check our reserve summary balance calculation shows that
   1537      the reserve balance is acceptable (i.e. non-negative) */
   1538   TALER_ARL_amount_add (&mbalance,
   1539                         &rs->total_in,
   1540                         &rs->prev_balance.reserve_balance);
   1541   if (TALER_ARL_SR_INVALID_NEGATIVE ==
   1542       TALER_ARL_amount_subtract_neg (&nbalance,
   1543                                      &mbalance,
   1544                                      &rs->total_out))
   1545   {
   1546     struct TALER_AUDITORDB_ReserveBalanceInsufficientInconsistency rbiil = {
   1547       .reserve_pub = rs->reserve_pub.eddsa_pub,
   1548       .inconsistency_gain = false
   1549     };
   1550 
   1551     TALER_ARL_amount_subtract (&rbiil.inconsistency_amount,
   1552                                &rs->total_out,
   1553                                &mbalance);
   1554     /* Note: 'curr_balance' is folded into 'prev_balance' at the end of
   1555        this function, so we must accumulate into 'curr_balance' here
   1556        (which starts at zero), not into 'prev_balance'. */
   1557     TALER_ARL_amount_add (&rs->curr_balance.reserve_loss,
   1558                           &rs->curr_balance.reserve_loss,
   1559                           &rbiil.inconsistency_amount);
   1560     TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_reserve_loss),
   1561                           &TALER_ARL_USE_AB (reserves_reserve_loss),
   1562                           &rbiil.inconsistency_amount);
   1563     qs = TALER_AUDITORDB_insert_reserve_balance_insufficient_inconsistency (
   1564       TALER_ARL_adb,
   1565       &rbiil);
   1566 
   1567     if (qs < 0)
   1568     {
   1569       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1570       rc->qs = qs;
   1571       return GNUNET_SYSERR;
   1572     }
   1573     /* Continue with a reserve balance of zero */
   1574     GNUNET_assert (GNUNET_OK ==
   1575                    TALER_amount_set_zero (TALER_ARL_currency,
   1576                                           &rs->curr_balance.reserve_balance));
   1577     nbalance = rs->curr_balance.reserve_balance;
   1578   }
   1579   else
   1580   {
   1581     /* Update remaining reserve balance! */
   1582     rs->curr_balance.reserve_balance = nbalance;
   1583   }
   1584 
   1585   if (internal_checks)
   1586   {
   1587     /* Now check OUR balance calculation vs. the one the exchange has
   1588        in its database. This can only be done when we are doing an
   1589        internal audit, as otherwise the balance of the 'reserves' table
   1590        is not replicated at the auditor. */
   1591     struct TALER_EXCHANGEDB_Reserve reserve = {
   1592       .pub = rs->reserve_pub
   1593     };
   1594 
   1595     qs = TALER_EXCHANGEDB_get_reserve (TALER_ARL_edb,
   1596                                        &reserve);
   1597     if (0 > qs)
   1598     {
   1599       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1600       rc->qs = qs;
   1601       return GNUNET_SYSERR;
   1602     }
   1603     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
   1604     {
   1605       /* If the exchange doesn't have this reserve in the summary, it
   1606          is like the exchange 'lost' that amount from its records,
   1607          making an illegitimate gain over the amount it dropped.
   1608          We don't add the amount to some total simply because it is
   1609          not an actualized gain and could be trivially corrected by
   1610          restoring the summary. */
   1611       struct TALER_AUDITORDB_ReserveBalanceInsufficientInconsistency rbiig = {
   1612         .reserve_pub = rs->reserve_pub.eddsa_pub,
   1613         .inconsistency_amount = nbalance,
   1614         .inconsistency_gain = true
   1615       };
   1616 
   1617       qs = TALER_AUDITORDB_insert_reserve_balance_insufficient_inconsistency (
   1618         TALER_ARL_adb,
   1619         &rbiig);
   1620 
   1621       if (qs < 0)
   1622       {
   1623         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1624         rc->qs = qs;
   1625         return GNUNET_SYSERR;
   1626       }
   1627       if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
   1628       {
   1629         GNUNET_break (0);
   1630         qs = GNUNET_DB_STATUS_HARD_ERROR;
   1631       }
   1632       rc->qs = qs;
   1633     }
   1634     else
   1635     {
   1636       /* Check that exchange's balance matches our expected balance for the reserve */
   1637       if (0 != TALER_amount_cmp (&rs->curr_balance.reserve_balance,
   1638                                  &reserve.balance))
   1639       {
   1640         struct TALER_Amount delta;
   1641         bool gain;
   1642 
   1643         if (0 < TALER_amount_cmp (&rs->curr_balance.reserve_balance,
   1644                                   &reserve.balance))
   1645         {
   1646           /* balance > reserve.balance: the exchange claims to owe
   1647              less than we computed, so it made an illegitimate gain */
   1648           gain = true;
   1649           TALER_ARL_amount_subtract (&delta,
   1650                                      &rs->curr_balance.reserve_balance,
   1651                                      &reserve.balance);
   1652           TALER_ARL_amount_add (&TALER_ARL_USE_AB (
   1653                                   total_balance_summary_delta_plus),
   1654                                 &TALER_ARL_USE_AB (
   1655                                   total_balance_summary_delta_plus),
   1656                                 &delta);
   1657         }
   1658         else
   1659         {
   1660           /* balance < reserve.balance */
   1661           gain = false;
   1662           TALER_ARL_amount_subtract (&delta,
   1663                                      &reserve.balance,
   1664                                      &rs->curr_balance.reserve_balance);
   1665           TALER_ARL_amount_add (&TALER_ARL_USE_AB (
   1666                                   total_balance_summary_delta_minus),
   1667                                 &TALER_ARL_USE_AB (
   1668                                   total_balance_summary_delta_minus),
   1669                                 &delta);
   1670         }
   1671 
   1672         {
   1673           struct TALER_AUDITORDB_ReserveBalanceInsufficientInconsistency rbiig =
   1674           {
   1675             .reserve_pub = rs->reserve_pub.eddsa_pub,
   1676             .inconsistency_amount = delta,
   1677             .inconsistency_gain = gain
   1678           };
   1679 
   1680           qs = TALER_AUDITORDB_insert_reserve_balance_insufficient_inconsistency
   1681                (
   1682             TALER_ARL_adb,
   1683             &rbiig);
   1684         }
   1685         if (qs < 0)
   1686         {
   1687           GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1688           rc->qs = qs;
   1689           return GNUNET_SYSERR;
   1690         }
   1691 
   1692         {
   1693           struct TALER_AUDITORDB_ReserveBalanceSummaryWrongInconsistency rbswi =
   1694           {
   1695             .exchange_amount = reserve.balance,
   1696             .auditor_amount = rs->curr_balance.reserve_balance,
   1697             .reserve_pub = rs->reserve_pub
   1698           };
   1699 
   1700           qs =
   1701             TALER_AUDITORDB_insert_reserve_balance_summary_wrong_inconsistency
   1702             (
   1703               TALER_ARL_adb,
   1704               &rbswi);
   1705         }
   1706         if (qs < 0)
   1707         {
   1708           GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1709           rc->qs = qs;
   1710           return GNUNET_SYSERR;
   1711         }
   1712       }
   1713     }
   1714   }   /* end of 'if (internal_checks)' */
   1715 
   1716   /* Check that reserve is being closed if it is past its expiration date
   1717      (and the closing fee would not exceed the remaining balance) */
   1718   if (GNUNET_TIME_relative_cmp (CLOSING_GRACE_PERIOD,
   1719                                 <,
   1720                                 GNUNET_TIME_absolute_get_duration (
   1721                                   rs->a_expiration_date.abs_time)))
   1722   {
   1723     /* Reserve is expired */
   1724     struct TALER_Amount cfee;
   1725 
   1726     if ( (NULL != rs->sender_account.full_payto) &&
   1727          (GNUNET_OK ==
   1728           get_closing_fee (rs->sender_account,
   1729                            rs->a_expiration_date,
   1730                            &cfee)) )
   1731     {
   1732       /* We got the closing fee */
   1733       if (1 == TALER_amount_cmp (&nbalance,
   1734                                  &cfee))
   1735       {
   1736         struct TALER_AUDITORDB_ReserveNotClosedInconsistency rnci = {
   1737           .reserve_pub = rs->reserve_pub,
   1738           .expiration_time = rs->a_expiration_date.abs_time,
   1739           .balance = nbalance,
   1740           .diagnostic = rs->sender_account.full_payto
   1741         };
   1742 
   1743         /* remaining balance (according to us) exceeds closing fee */
   1744         TALER_ARL_amount_add (&TALER_ARL_USE_AB (
   1745                                 total_balance_reserve_not_closed),
   1746                               &TALER_ARL_USE_AB (
   1747                                 total_balance_reserve_not_closed),
   1748                               &rnci.balance);
   1749         qs = TALER_AUDITORDB_insert_reserve_not_closed_inconsistency (
   1750           TALER_ARL_adb,
   1751           &rnci);
   1752         if (qs < 0)
   1753         {
   1754           GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1755           rc->qs = qs;
   1756           return GNUNET_SYSERR;
   1757         }
   1758       }
   1759     }
   1760     else
   1761     {
   1762       /* We failed to determine the closing fee, complain! */
   1763       struct TALER_AUDITORDB_ReserveNotClosedInconsistency rncid = {
   1764         .reserve_pub = rs->reserve_pub,
   1765         .balance = nbalance,
   1766         .expiration_time = rs->a_expiration_date.abs_time,
   1767         .diagnostic = (char *) "could not determine closing fee"
   1768       };
   1769 
   1770       /* Even if we don't know the closing fee, update the
   1771          total_balance_reserve_not_closed */
   1772       TALER_ARL_amount_add (&TALER_ARL_USE_AB (
   1773                               total_balance_reserve_not_closed),
   1774                             &TALER_ARL_USE_AB (
   1775                               total_balance_reserve_not_closed),
   1776                             &nbalance);
   1777       qs = TALER_AUDITORDB_insert_reserve_not_closed_inconsistency (
   1778         TALER_ARL_adb,
   1779         &rncid);
   1780       if (qs < 0)
   1781       {
   1782         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1783         rc->qs = qs;
   1784         return GNUNET_SYSERR;
   1785       }
   1786     }
   1787   }
   1788   /* We already computed the 'new' balance in 'curr_balance'
   1789      to include the previous balance, so this one is just
   1790      an assignment, not adding up! */
   1791   rs->prev_balance.reserve_balance = rs->curr_balance.reserve_balance;
   1792 
   1793   /* Add up new totals to previous totals  */
   1794   TALER_ARL_amount_add (&rs->prev_balance.reserve_loss,
   1795                         &rs->prev_balance.reserve_loss,
   1796                         &rs->curr_balance.reserve_loss);
   1797   TALER_ARL_amount_add (&rs->prev_balance.withdraw_fee_balance,
   1798                         &rs->prev_balance.withdraw_fee_balance,
   1799                         &rs->curr_balance.withdraw_fee_balance);
   1800   TALER_ARL_amount_add (&rs->prev_balance.close_fee_balance,
   1801                         &rs->prev_balance.close_fee_balance,
   1802                         &rs->curr_balance.close_fee_balance);
   1803   TALER_ARL_amount_add (&rs->prev_balance.purse_fee_balance,
   1804                         &rs->prev_balance.purse_fee_balance,
   1805                         &rs->curr_balance.purse_fee_balance);
   1806   TALER_ARL_amount_add (&rs->prev_balance.open_fee_balance,
   1807                         &rs->prev_balance.open_fee_balance,
   1808                         &rs->curr_balance.open_fee_balance);
   1809   TALER_ARL_amount_add (&rs->prev_balance.history_fee_balance,
   1810                         &rs->prev_balance.history_fee_balance,
   1811                         &rs->curr_balance.history_fee_balance);
   1812   /* Update global balance: add incoming first, then try
   1813      to subtract outgoing... */
   1814   TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_reserve_total_balance),
   1815                         &TALER_ARL_USE_AB (reserves_reserve_total_balance),
   1816                         &rs->total_in);
   1817   {
   1818     struct TALER_Amount r;
   1819 
   1820     if (TALER_ARL_SR_INVALID_NEGATIVE ==
   1821         TALER_ARL_amount_subtract_neg (&r,
   1822                                        &TALER_ARL_USE_AB (
   1823                                          reserves_reserve_total_balance),
   1824                                        &rs->total_out))
   1825     {
   1826       /* We could not reduce our total balance, i.e. exchange allowed IN TOTAL (!)
   1827          to be withdrawn more than it was IN TOTAL ever given (exchange balance
   1828          went negative!).  Woopsie. Calculate how badly it went and log. */
   1829       report_amount_arithmetic_inconsistency ("global escrow balance",
   1830                                               0,
   1831                                               &TALER_ARL_USE_AB (
   1832                                                 reserves_reserve_total_balance),                   /* what we had */
   1833                                               &rs->total_out,   /* what we needed */
   1834                                               0 /* specific profit/loss does not apply to the total summary */
   1835                                               );
   1836       if (global_qs < 0)
   1837         return GNUNET_SYSERR;
   1838       /* We unexpectedly went negative, so a sane value to continue from
   1839          would be zero. */
   1840       GNUNET_assert (GNUNET_OK ==
   1841                      TALER_amount_set_zero (TALER_ARL_currency,
   1842                                             &TALER_ARL_USE_AB (
   1843                                               reserves_reserve_total_balance)));
   1844     }
   1845     else
   1846     {
   1847       TALER_ARL_USE_AB (reserves_reserve_total_balance) = r;
   1848     }
   1849   }
   1850   if (TALER_amount_is_zero (&rs->prev_balance.reserve_balance))
   1851   {
   1852     /* balance is zero, drop reserve details (and then do not update/insert) */
   1853     if (rs->had_ri)
   1854     {
   1855       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1856                   "Final balance of reserve `%s' is zero, dropping it\n",
   1857                   TALER_B2S (&rs->reserve_pub));
   1858       qs = TALER_AUDITORDB_delete_reserve_info (TALER_ARL_adb,
   1859                                                 &rs->reserve_pub);
   1860       if (0 >= qs)
   1861       {
   1862         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1863         ret = GNUNET_SYSERR;
   1864         rc->qs = qs;
   1865       }
   1866     }
   1867     else
   1868     {
   1869       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1870                   "Final balance of reserve `%s' is zero, no need to remember it\n",
   1871                   TALER_B2S (&rs->reserve_pub));
   1872     }
   1873   }
   1874   else
   1875   {
   1876     /* balance is non-zero, persist for future audits */
   1877     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1878                 "Remembering final balance of reserve `%s' as %s\n",
   1879                 TALER_B2S (&rs->reserve_pub),
   1880                 TALER_amount2s (&rs->prev_balance.reserve_balance));
   1881     if (rs->had_ri)
   1882       qs = TALER_AUDITORDB_update_reserve_info (TALER_ARL_adb,
   1883                                                 &rs->reserve_pub,
   1884                                                 &rs->prev_balance,
   1885                                                 rs->a_expiration_date);
   1886     else
   1887       qs = TALER_AUDITORDB_insert_reserve_info (TALER_ARL_adb,
   1888                                                 &rs->reserve_pub,
   1889                                                 &rs->prev_balance,
   1890                                                 rs->a_expiration_date,
   1891                                                 rs->sender_account);
   1892     if (0 >= qs)
   1893     {
   1894       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1895       ret = GNUNET_SYSERR;
   1896       rc->qs = qs;
   1897     }
   1898   }
   1899   /* now we can discard the cached entry */
   1900   GNUNET_assert (GNUNET_YES ==
   1901                  GNUNET_CONTAINER_multihashmap_remove (rc->reserves,
   1902                                                        key,
   1903                                                        rs));
   1904   GNUNET_free (rs->sender_account.full_payto);
   1905   GNUNET_free (rs);
   1906   return ret;
   1907 }
   1908 
   1909 
   1910 #define CHECK_DB() do {                                       \
   1911           if (qs < 0) {                                       \
   1912             GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); \
   1913             goto cleanup;                                     \
   1914           }                                                   \
   1915           if (global_qs < 0) {                                \
   1916             GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == global_qs); \
   1917             qs = global_qs;                                          \
   1918             goto cleanup;                                            \
   1919           }                                                          \
   1920           if (rc.qs < 0) {                                           \
   1921             GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == rc.qs);     \
   1922             qs = rc.qs;                                              \
   1923             goto cleanup;                                            \
   1924           }                                                          \
   1925 } while (0)
   1926 
   1927 
   1928 /**
   1929  * Analyze reserves for being well-formed.
   1930  *
   1931  * @param cls NULL
   1932  * @return transaction status code
   1933  */
   1934 static enum GNUNET_DB_QueryStatus
   1935 analyze_reserves (void *cls)
   1936 {
   1937   struct ReserveContext rc = {
   1938     .qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT
   1939   };
   1940   enum GNUNET_DB_QueryStatus qs;
   1941 
   1942   (void) cls;
   1943   global_qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
   1944   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1945               "Analyzing reserves\n");
   1946   qs = TALER_AUDITORDB_get_auditor_progress (
   1947     TALER_ARL_adb,
   1948     TALER_ARL_GET_PP (reserves_reserve_in_serial_id),
   1949     TALER_ARL_GET_PP (reserves_withdraw_serial_id),
   1950     TALER_ARL_GET_PP (reserves_reserve_recoup_serial_id),
   1951     TALER_ARL_GET_PP (reserves_reserve_open_serial_id),
   1952     TALER_ARL_GET_PP (reserves_reserve_close_serial_id),
   1953     TALER_ARL_GET_PP (reserves_purse_decisions_serial_id),
   1954     TALER_ARL_GET_PP (reserves_account_merges_serial_id),
   1955     TALER_ARL_GET_PP (reserves_history_requests_serial_id),
   1956     NULL);
   1957   if (0 > qs)
   1958   {
   1959     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1960     return qs;
   1961   }
   1962   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
   1963   {
   1964     GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
   1965                 "First analysis using this auditor, starting audit from scratch\n");
   1966   }
   1967   else
   1968   {
   1969     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1970                 "Resuming reserve audit at %llu/%llu/%llu/%llu/%llu/%llu/%llu/%llu\n",
   1971                 (unsigned long long) TALER_ARL_USE_PP (
   1972                   reserves_reserve_in_serial_id),
   1973                 (unsigned long long) TALER_ARL_USE_PP (
   1974                   reserves_withdraw_serial_id),
   1975                 (unsigned long long) TALER_ARL_USE_PP (
   1976                   reserves_reserve_recoup_serial_id),
   1977                 (unsigned long long) TALER_ARL_USE_PP (
   1978                   reserves_reserve_open_serial_id),
   1979                 (unsigned long long) TALER_ARL_USE_PP (
   1980                   reserves_reserve_close_serial_id),
   1981                 (unsigned long long) TALER_ARL_USE_PP (
   1982                   reserves_purse_decisions_serial_id),
   1983                 (unsigned long long) TALER_ARL_USE_PP (
   1984                   reserves_account_merges_serial_id),
   1985                 (unsigned long long) TALER_ARL_USE_PP (
   1986                   reserves_history_requests_serial_id));
   1987   }
   1988   qs = TALER_AUDITORDB_get_balance (
   1989     TALER_ARL_adb,
   1990     TALER_ARL_GET_AB (reserves_reserve_total_balance),
   1991     TALER_ARL_GET_AB (reserves_reserve_loss),
   1992     TALER_ARL_GET_AB (reserves_withdraw_fee_revenue),
   1993     TALER_ARL_GET_AB (reserves_close_fee_revenue),
   1994     TALER_ARL_GET_AB (reserves_purse_fee_revenue),
   1995     TALER_ARL_GET_AB (reserves_open_fee_revenue),
   1996     TALER_ARL_GET_AB (reserves_history_fee_revenue),
   1997     TALER_ARL_GET_AB (reserves_total_bad_sig_loss),
   1998     TALER_ARL_GET_AB (total_balance_reserve_not_closed),
   1999     TALER_ARL_GET_AB (reserves_total_arithmetic_delta_plus),
   2000     TALER_ARL_GET_AB (reserves_total_arithmetic_delta_minus),
   2001     TALER_ARL_GET_AB (total_balance_summary_delta_plus),
   2002     TALER_ARL_GET_AB (total_balance_summary_delta_minus),
   2003     NULL);
   2004   if (qs < 0)
   2005   {
   2006     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   2007     return qs;
   2008   }
   2009   rc.reserves = GNUNET_CONTAINER_multihashmap_create (512,
   2010                                                       GNUNET_NO);
   2011   rc.revoked = GNUNET_CONTAINER_multihashmap_create (4,
   2012                                                      GNUNET_NO);
   2013 
   2014   qs = TALER_EXCHANGEDB_iterate_reserves_in_above_serial_id (
   2015     TALER_ARL_edb,
   2016     TALER_ARL_USE_PP (reserves_reserve_in_serial_id),
   2017     &handle_reserve_in,
   2018     &rc);
   2019   CHECK_DB ();
   2020   qs = TALER_EXCHANGEDB_iterate_withdrawals_above_serial_id (
   2021     TALER_ARL_edb,
   2022     TALER_ARL_USE_PP (reserves_withdraw_serial_id),
   2023     &handle_withdrawals,
   2024     &rc);
   2025   CHECK_DB ();
   2026   qs = TALER_EXCHANGEDB_iterate_recoups_above_serial_id (
   2027     TALER_ARL_edb,
   2028     TALER_ARL_USE_PP (reserves_reserve_recoup_serial_id),
   2029     &handle_recoup_by_reserve,
   2030     &rc);
   2031   CHECK_DB ();
   2032   qs = TALER_EXCHANGEDB_iterate_reserve_open_above_serial_id (
   2033     TALER_ARL_edb,
   2034     TALER_ARL_USE_PP (reserves_reserve_open_serial_id),
   2035     &handle_reserve_open,
   2036     &rc);
   2037   CHECK_DB ();
   2038   qs = TALER_EXCHANGEDB_iterate_reserve_closed_above_serial_id (
   2039     TALER_ARL_edb,
   2040     TALER_ARL_USE_PP (reserves_reserve_close_serial_id),
   2041     &handle_reserve_closed,
   2042     &rc);
   2043   CHECK_DB ();
   2044   /* process purse_decisions (to credit reserve) */
   2045   qs = TALER_EXCHANGEDB_iterate_purse_decisions_above_serial_id (
   2046     TALER_ARL_edb,
   2047     TALER_ARL_USE_PP (reserves_purse_decisions_serial_id),
   2048     false,      /* only go for merged purses! */
   2049     &purse_decision_cb,
   2050     &rc);
   2051   CHECK_DB ();
   2052   /* Charge purse fee! */
   2053 
   2054   qs = TALER_EXCHANGEDB_iterate_account_merges_above_serial_id (
   2055     TALER_ARL_edb,
   2056     TALER_ARL_USE_PP (reserves_account_merges_serial_id),
   2057     &handle_account_merged,
   2058     &rc);
   2059   CHECK_DB ();
   2060   GNUNET_CONTAINER_multihashmap_iterate (rc.reserves,
   2061                                          &verify_reserve_balance,
   2062                                          &rc);
   2063   CHECK_DB ();
   2064   GNUNET_break (0 ==
   2065                 GNUNET_CONTAINER_multihashmap_size (rc.reserves));
   2066 
   2067   qs = TALER_AUDITORDB_insert_balance (
   2068     TALER_ARL_adb,
   2069     TALER_ARL_SET_AB (reserves_reserve_total_balance),
   2070     TALER_ARL_SET_AB (reserves_reserve_loss),
   2071     TALER_ARL_SET_AB (reserves_withdraw_fee_revenue),
   2072     TALER_ARL_SET_AB (reserves_close_fee_revenue),
   2073     TALER_ARL_SET_AB (reserves_purse_fee_revenue),
   2074     TALER_ARL_SET_AB (reserves_open_fee_revenue),
   2075     TALER_ARL_SET_AB (reserves_history_fee_revenue),
   2076     TALER_ARL_SET_AB (reserves_total_bad_sig_loss),
   2077     TALER_ARL_SET_AB (total_balance_reserve_not_closed),
   2078     TALER_ARL_SET_AB (reserves_total_arithmetic_delta_plus),
   2079     TALER_ARL_SET_AB (reserves_total_arithmetic_delta_minus),
   2080     TALER_ARL_SET_AB (total_balance_summary_delta_plus),
   2081     TALER_ARL_SET_AB (total_balance_summary_delta_minus),
   2082     NULL);
   2083   if (0 > qs)
   2084   {
   2085     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   2086     goto cleanup;
   2087   }
   2088 
   2089   qs = TALER_AUDITORDB_insert_auditor_progress (
   2090     TALER_ARL_adb,
   2091     TALER_ARL_SET_PP (reserves_reserve_in_serial_id),
   2092     TALER_ARL_SET_PP (reserves_withdraw_serial_id),
   2093     TALER_ARL_SET_PP (reserves_reserve_recoup_serial_id),
   2094     TALER_ARL_SET_PP (reserves_reserve_open_serial_id),
   2095     TALER_ARL_SET_PP (reserves_reserve_close_serial_id),
   2096     TALER_ARL_SET_PP (reserves_purse_decisions_serial_id),
   2097     TALER_ARL_SET_PP (reserves_account_merges_serial_id),
   2098     TALER_ARL_SET_PP (reserves_history_requests_serial_id),
   2099     NULL);
   2100   if (0 > qs)
   2101   {
   2102     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   2103                 "Failed to update auditor DB, not recording progress\n");
   2104     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   2105     goto cleanup;
   2106   }
   2107 
   2108   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   2109               "Concluded reserve audit step at %llu/%llu/%llu/%llu/%llu/%llu/%llu/%llu\n",
   2110               (unsigned long long) TALER_ARL_USE_PP (
   2111                 reserves_reserve_in_serial_id),
   2112               (unsigned long long) TALER_ARL_USE_PP (
   2113                 reserves_withdraw_serial_id),
   2114               (unsigned long long) TALER_ARL_USE_PP (
   2115                 reserves_reserve_recoup_serial_id),
   2116               (unsigned long long) TALER_ARL_USE_PP (
   2117                 reserves_reserve_open_serial_id),
   2118               (unsigned long long) TALER_ARL_USE_PP (
   2119                 reserves_reserve_close_serial_id),
   2120               (unsigned long long) TALER_ARL_USE_PP (
   2121                 reserves_purse_decisions_serial_id),
   2122               (unsigned long long) TALER_ARL_USE_PP (
   2123                 reserves_account_merges_serial_id),
   2124               (unsigned long long) TALER_ARL_USE_PP (
   2125                 reserves_history_requests_serial_id));
   2126   qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
   2127 cleanup:
   2128   GNUNET_CONTAINER_multihashmap_destroy (rc.reserves);
   2129   GNUNET_CONTAINER_multihashmap_destroy (rc.revoked);
   2130   return qs;
   2131 }
   2132 
   2133 
   2134 #undef CHECK_DB
   2135 
   2136 
   2137 /**
   2138  * Function called on events received from Postgres.
   2139  *
   2140  * @param cls closure, NULL
   2141  * @param extra additional event data provided
   2142  * @param extra_size number of bytes in @a extra
   2143  */
   2144 static void
   2145 db_notify (void *cls,
   2146            const void *extra,
   2147            size_t extra_size)
   2148 {
   2149   (void) cls;
   2150   (void) extra;
   2151   (void) extra_size;
   2152 
   2153   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   2154               "Received notification to wake reserves helper\n");
   2155   if (GNUNET_OK !=
   2156       TALER_ARL_setup_sessions_and_run (&analyze_reserves,
   2157                                         NULL))
   2158   {
   2159     GNUNET_SCHEDULER_shutdown ();
   2160     global_ret = EXIT_FAILURE;
   2161     return;
   2162   }
   2163 }
   2164 
   2165 
   2166 /**
   2167  * Function called on shutdown.
   2168  */
   2169 static void
   2170 do_shutdown (void *cls)
   2171 {
   2172   (void) cls;
   2173   if (NULL != eh)
   2174   {
   2175     TALER_AUDITORDB_event_listen_cancel (eh);
   2176     eh = NULL;
   2177   }
   2178   TALER_ARL_done ();
   2179 }
   2180 
   2181 
   2182 /**
   2183  * Main function that will be run.
   2184  *
   2185  * @param cls closure
   2186  * @param args remaining command-line arguments
   2187  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
   2188  * @param c configuration
   2189  */
   2190 static void
   2191 run (void *cls,
   2192      char *const *args,
   2193      const char *cfgfile,
   2194      const struct GNUNET_CONFIGURATION_Handle *c)
   2195 {
   2196   (void) cls;
   2197   (void) args;
   2198   (void) cfgfile;
   2199 
   2200   cfg = c;
   2201   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
   2202                                  NULL);
   2203   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   2204               "Launching reserves auditor\n");
   2205   if (EXIT_SUCCESS !=
   2206       (global_ret = TALER_ARL_init (c)))
   2207   {
   2208     return;
   2209   }
   2210   if (GNUNET_OK !=
   2211       GNUNET_CONFIGURATION_get_value_time (TALER_ARL_cfg,
   2212                                            "exchangedb",
   2213                                            "IDLE_RESERVE_EXPIRATION_TIME",
   2214                                            &idle_reserve_expiration_time))
   2215   {
   2216     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
   2217                                "exchangedb",
   2218                                "IDLE_RESERVE_EXPIRATION_TIME");
   2219     GNUNET_SCHEDULER_shutdown ();
   2220     global_ret = EXIT_FAILURE;
   2221     return;
   2222   }
   2223   if (test_mode != 1)
   2224   {
   2225     struct GNUNET_DB_EventHeaderP es = {
   2226       .size = htons (sizeof (es)),
   2227       .type = htons (TALER_DBEVENT_EXCHANGE_AUDITOR_WAKE_HELPER_RESERVES)
   2228     };
   2229 
   2230     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   2231                 "Running helper indefinitely\n");
   2232     eh = TALER_AUDITORDB_event_listen (TALER_ARL_adb,
   2233                                        &es,
   2234                                        GNUNET_TIME_UNIT_FOREVER_REL,
   2235                                        &db_notify,
   2236                                        NULL);
   2237   }
   2238   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   2239               "Starting audit\n");
   2240   if (GNUNET_OK !=
   2241       TALER_ARL_setup_sessions_and_run (&analyze_reserves,
   2242                                         NULL))
   2243   {
   2244     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   2245                 "Audit failed\n");
   2246     GNUNET_SCHEDULER_shutdown ();
   2247     global_ret = EXIT_FAILURE;
   2248     return;
   2249   }
   2250 }
   2251 
   2252 
   2253 /**
   2254  * The main function to check the database's handling of reserves.
   2255  *
   2256  * @param argc number of arguments from the command line
   2257  * @param argv command line arguments
   2258  * @return 0 ok, 1 on error
   2259  */
   2260 int
   2261 main (int argc,
   2262       char *const *argv)
   2263 {
   2264   const struct GNUNET_GETOPT_CommandLineOption options[] = {
   2265     GNUNET_GETOPT_option_flag ('i',
   2266                                "internal",
   2267                                "perform checks only applicable for exchange-internal audits",
   2268                                &internal_checks),
   2269     GNUNET_GETOPT_option_flag ('t',
   2270                                "test",
   2271                                "run in test mode and exit when idle",
   2272                                &test_mode),
   2273     GNUNET_GETOPT_option_timetravel ('T',
   2274                                      "timetravel"),
   2275     GNUNET_GETOPT_OPTION_END
   2276   };
   2277   enum GNUNET_GenericReturnValue ret;
   2278 
   2279   ret = GNUNET_PROGRAM_run (
   2280     TALER_AUDITOR_project_data (),
   2281     argc,
   2282     argv,
   2283     "taler-helper-auditor-reserves",
   2284     gettext_noop ("Audit Taler exchange reserve handling"),
   2285     options,
   2286     &run,
   2287     NULL);
   2288   if (GNUNET_SYSERR == ret)
   2289     return EXIT_NOTCONFIGURED;
   2290   if (GNUNET_NO == ret)
   2291     return EXIT_SUCCESS;
   2292   return global_ret;
   2293 }
   2294 
   2295 
   2296 /* end of taler-helper-auditor-reserves.c */