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


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