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 (80685B)


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