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


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