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


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