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


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