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-purses.c (56579B)


      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-purses.c
     18  * @brief audits the purses of an exchange database
     19  * @author Christian Grothoff
     20  */
     21 #include "platform.h"
     22 #include <gnunet/gnunet_util_lib.h>
     23 #include "auditordb_lib.h"
     24 #include "exchangedb_lib.h"
     25 #include "taler/taler_bank_service.h"
     26 #include "taler/taler_signatures.h"
     27 #include "report-lib.h"
     28 #include "taler/taler_dbevents.h"
     29 #include "auditor-database/delete_purse_info.h"
     30 #include "auditor-database/event_listen.h"
     31 #include "auditor-database/get_auditor_progress.h"
     32 #include "auditor-database/get_balance.h"
     33 #include "auditor-database/get_purse_info.h"
     34 #include "auditor-database/insert_amount_arithmetic_inconsistency.h"
     35 #include "auditor-database/insert_auditor_progress.h"
     36 #include "auditor-database/insert_bad_sig_losses.h"
     37 #include "auditor-database/insert_balance.h"
     38 #include "auditor-database/insert_purse_info.h"
     39 #include "auditor-database/insert_purse_not_closed_inconsistencies.h"
     40 #include "auditor-database/insert_row_inconsistency.h"
     41 #include "auditor-database/select_purse_expired.h"
     42 #include "auditor-database/update_auditor_progress.h"
     43 #include "auditor-database/update_balance.h"
     44 #include "auditor-database/update_purse_info.h"
     45 #include "exchange-database/get_global_fee.h"
     46 #include "exchange-database/select_account_merges_above_serial_id.h"
     47 #include "exchange-database/select_all_purse_decisions_above_serial_id.h"
     48 #include "exchange-database/select_all_purse_deletions_above_serial_id.h"
     49 #include "exchange-database/select_purse.h"
     50 #include "exchange-database/select_purse_deposits_above_serial_id.h"
     51 #include "exchange-database/select_purse_merges_above_serial_id.h"
     52 #include "exchange-database/select_purse_requests_above_serial_id.h"
     53 
     54 
     55 /**
     56  * Use a 1 day grace period to deal with clocks not being perfectly synchronized.
     57  */
     58 #define EXPIRATION_GRACE_PERIOD GNUNET_TIME_UNIT_DAYS
     59 
     60 /**
     61  * Return value from main().
     62  */
     63 static int global_ret;
     64 
     65 /**
     66  * Run in test mode. Exit when idle instead of
     67  * going to sleep and waiting for more work.
     68  */
     69 static int test_mode;
     70 
     71 /**
     72  * Checkpointing our progress for purses.
     73  */
     74 static TALER_ARL_DEF_PP (purse_account_merge_serial_id);
     75 static TALER_ARL_DEF_PP (purse_decision_serial_id);
     76 static TALER_ARL_DEF_PP (purse_deletion_serial_id);
     77 static TALER_ARL_DEF_PP (purse_deposits_serial_id);
     78 static TALER_ARL_DEF_PP (purse_merges_serial_id);
     79 static TALER_ARL_DEF_PP (purse_request_serial_id);
     80 static TALER_ARL_DEF_PP (purse_open_counter);
     81 static TALER_ARL_DEF_AB (purse_global_balance);
     82 
     83 /**
     84  * Total amount purses were merged with insufficient balance.
     85  */
     86 static TALER_ARL_DEF_AB (purse_total_balance_insufficient_loss);
     87 
     88 /**
     89  * Total amount purse decisions are delayed past deadline.
     90  */
     91 static TALER_ARL_DEF_AB (purse_total_delayed_decisions);
     92 
     93 /**
     94  * Total amount affected by purses not having been closed on time.
     95  */
     96 static TALER_ARL_DEF_AB (purse_total_balance_purse_not_closed);
     97 
     98 /**
     99  * Profits the exchange made by bad amount calculations.
    100  */
    101 static TALER_ARL_DEF_AB (purse_total_arithmetic_delta_plus);
    102 
    103 /**
    104  * Losses the exchange made by bad amount calculations.
    105  */
    106 static TALER_ARL_DEF_AB (purse_total_arithmetic_delta_minus);
    107 
    108 /**
    109  * Total amount lost by operations for which signatures were invalid.
    110  */
    111 static TALER_ARL_DEF_AB (purse_total_bad_sig_loss);
    112 
    113 /**
    114  * Should we run checks that only work for exchange-internal audits?
    115  */
    116 static int internal_checks;
    117 
    118 static struct GNUNET_DB_EventHandler *eh;
    119 
    120 /**
    121  * The auditors's configuration.
    122  */
    123 static const struct GNUNET_CONFIGURATION_Handle *cfg;
    124 
    125 /* ***************************** Report logic **************************** */
    126 
    127 
    128 /**
    129  * Report a (serious) inconsistency in the exchange's database with
    130  * respect to calculations involving amounts.
    131  *
    132  * @param operation what operation had the inconsistency
    133  * @param rowid affected row, 0 if row is missing
    134  * @param exchange amount calculated by exchange
    135  * @param auditor amount calculated by auditor
    136  * @param profitable 1 if @a exchange being larger than @a auditor is
    137  *           profitable for the exchange for this operation,
    138  *           -1 if @a exchange being smaller than @a auditor is
    139  *           profitable for the exchange, and 0 if it is unclear
    140  * @return transaction status
    141  */
    142 static enum GNUNET_DB_QueryStatus
    143 report_amount_arithmetic_inconsistency (
    144   const char *operation,
    145   uint64_t rowid,
    146   const struct TALER_Amount *exchange,
    147   const struct TALER_Amount *auditor,
    148   int profitable)
    149 {
    150   struct TALER_Amount delta;
    151   struct TALER_Amount *target;
    152   enum GNUNET_DB_QueryStatus qs;
    153 
    154   if (0 < TALER_amount_cmp (exchange,
    155                             auditor))
    156   {
    157     /* exchange > auditor */
    158     TALER_ARL_amount_subtract (&delta,
    159                                exchange,
    160                                auditor);
    161   }
    162   else
    163   {
    164     /* auditor < exchange */
    165     profitable = -profitable;
    166     TALER_ARL_amount_subtract (&delta,
    167                                auditor,
    168                                exchange);
    169   }
    170 
    171   {
    172     struct TALER_AUDITORDB_AmountArithmeticInconsistency aai = {
    173       .profitable = profitable,
    174       .problem_row_id = rowid,
    175       .operation = (char *) operation,
    176       .exchange_amount = *exchange,
    177       .auditor_amount = *auditor
    178     };
    179 
    180     qs = TALER_AUDITORDB_insert_amount_arithmetic_inconsistency (
    181       TALER_ARL_adb,
    182       &aai);
    183 
    184     if (qs < 0)
    185     {
    186       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    187       return qs;
    188     }
    189   }
    190 
    191   if (0 != profitable)
    192   {
    193     target = (1 == profitable)
    194       ? &TALER_ARL_USE_AB (purse_total_arithmetic_delta_plus)
    195       : &TALER_ARL_USE_AB (purse_total_arithmetic_delta_minus);
    196     TALER_ARL_amount_add (target,
    197                           target,
    198                           &delta);
    199   }
    200   return qs;
    201 }
    202 
    203 
    204 /**
    205  * Report a (serious) inconsistency in the exchange's database.
    206  *
    207  * @param table affected table
    208  * @param rowid affected row, 0 if row is missing
    209  * @param diagnostic message explaining the problem
    210  * @return transaction status
    211  */
    212 static enum GNUNET_DB_QueryStatus
    213 report_row_inconsistency (const char *table,
    214                           uint64_t rowid,
    215                           const char *diagnostic)
    216 {
    217   enum GNUNET_DB_QueryStatus qs;
    218   struct TALER_AUDITORDB_RowInconsistency ri = {
    219     .diagnostic = (char *) diagnostic,
    220     .row_table = (char *) table,
    221     .row_id = rowid
    222   };
    223 
    224   qs = TALER_AUDITORDB_insert_row_inconsistency (
    225     TALER_ARL_adb,
    226     &ri);
    227 
    228   if (qs < 0)
    229   {
    230     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    231     return qs;
    232   }
    233   return qs;
    234 }
    235 
    236 
    237 /**
    238  * Obtain the purse fee for a purse created at @a time.
    239  *
    240  * @param atime when was the purse created
    241  * @param[out] fee set to the purse fee
    242  * @return #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT on success
    243  */
    244 static enum GNUNET_DB_QueryStatus
    245 get_purse_fee (struct GNUNET_TIME_Timestamp atime,
    246                struct TALER_Amount *fee)
    247 {
    248   enum GNUNET_DB_QueryStatus qs;
    249   struct TALER_MasterSignatureP master_sig;
    250   struct GNUNET_TIME_Timestamp start_date;
    251   struct GNUNET_TIME_Timestamp end_date;
    252   struct TALER_GlobalFeeSet fees;
    253   struct GNUNET_TIME_Relative ptimeout;
    254   struct GNUNET_TIME_Relative hexp;
    255   uint32_t pacl;
    256 
    257   qs = TALER_EXCHANGEDB_get_global_fee (TALER_ARL_edb,
    258                                         atime,
    259                                         &start_date,
    260                                         &end_date,
    261                                         &fees,
    262                                         &ptimeout,
    263                                         &hexp,
    264                                         &pacl,
    265                                         &master_sig);
    266   if (0 > qs)
    267   {
    268     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    269     return qs;
    270   }
    271   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    272   {
    273     char *diag;
    274 
    275     GNUNET_asprintf (&diag,
    276                      "purse fee unavailable at %s\n",
    277                      GNUNET_TIME_timestamp2s (atime));
    278     qs = report_row_inconsistency ("purse-fee",
    279                                    atime.abs_time.abs_value_us,
    280                                    diag);
    281     GNUNET_free (diag);
    282     if (0 > qs)
    283     {
    284       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    285       return qs;
    286     }
    287     return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
    288   }
    289   *fee = fees.purse;
    290   return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
    291 }
    292 
    293 
    294 /* ***************************** Analyze purses ************************ */
    295 /* This logic checks the purses_requests, purse_deposits,
    296    purse_refunds, purse_merges and account_merges */
    297 
    298 /**
    299  * Summary data we keep per purse.
    300  */
    301 struct PurseSummary
    302 {
    303   /**
    304    * Public key of the purse.
    305    * Always set when the struct is first initialized.
    306    */
    307   struct TALER_PurseContractPublicKeyP purse_pub;
    308 
    309   /**
    310    * Balance of the purse from deposits (includes purse fee, excludes deposit
    311    * fees), as calculated by auditor.
    312    */
    313   struct TALER_Amount balance;
    314 
    315   /**
    316    * Expected value of the purse, excludes purse fee.
    317    */
    318   struct TALER_Amount total_value;
    319 
    320   /**
    321    * Purse balance according to exchange DB.
    322    */
    323   struct TALER_Amount exchange_balance;
    324 
    325   /**
    326    * Contract terms of the purse.
    327    */
    328   struct TALER_PrivateContractHashP h_contract_terms;
    329 
    330   /**
    331    * Merge timestamp (as per exchange DB).
    332    */
    333   struct GNUNET_TIME_Timestamp merge_timestamp;
    334 
    335   /**
    336    * Purse creation date.  This is when the merge
    337    * fee is applied.
    338    */
    339   struct GNUNET_TIME_Timestamp creation_date;
    340 
    341   /**
    342    * Purse expiration date.
    343    */
    344   struct GNUNET_TIME_Timestamp expiration_date;
    345 
    346   /**
    347    * Did we have a previous purse info?  Used to decide between UPDATE and
    348    * INSERT later.  Initialized in #load_auditor_purse_summary().
    349    */
    350   bool had_pi;
    351 
    352   /**
    353    * Was the purse deleted? Note: as this is set via an UPDATE, it
    354    * may be false at the auditor even if the purse was deleted. Thus,
    355    * this value is only meaningful for *internal* checks.
    356    */
    357   bool purse_deleted;
    358 
    359   /**
    360    * Was the purse refunded? Note: as this is set via an UPDATE, it
    361    * may be false at the auditor even if the purse was deleted. Thus,
    362    * this value is only meaningful for *internal* checks.
    363    */
    364   bool purse_refunded;
    365 
    366 };
    367 
    368 
    369 /**
    370  * Load the auditor's remembered state about the purse into @a ps.
    371  *
    372  * @param[in,out] ps purse summary to (fully) initialize
    373  * @return transaction status code
    374  */
    375 static enum GNUNET_DB_QueryStatus
    376 load_auditor_purse_summary (struct PurseSummary *ps)
    377 {
    378   enum GNUNET_DB_QueryStatus qs;
    379   uint64_t rowid;
    380 
    381   qs = TALER_AUDITORDB_get_purse_info (TALER_ARL_adb,
    382                                        &ps->purse_pub,
    383                                        &rowid,
    384                                        &ps->balance,
    385                                        &ps->expiration_date);
    386   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    387               "Loaded purse `%s' info (%d)\n",
    388               TALER_B2S (&ps->purse_pub),
    389               (int) qs);
    390   if (0 > qs)
    391   {
    392     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    393     return qs;
    394   }
    395   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    396   {
    397     ps->had_pi = false;
    398     GNUNET_assert (GNUNET_OK ==
    399                    TALER_amount_set_zero (TALER_ARL_currency,
    400                                           &ps->balance));
    401     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    402                 "Creating fresh purse `%s'\n",
    403                 TALER_B2S (&ps->purse_pub));
    404     return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
    405   }
    406   ps->had_pi = true;
    407   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    408               "Auditor remembers purse `%s' has balance %s\n",
    409               TALER_B2S (&ps->purse_pub),
    410               TALER_amount2s (&ps->balance));
    411   return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
    412 }
    413 
    414 
    415 /**
    416  * Closure to the various callbacks we make while checking a purse.
    417  */
    418 struct PurseContext
    419 {
    420   /**
    421    * Map from hash of purse's public key to a `struct PurseSummary`.
    422    */
    423   struct GNUNET_CONTAINER_MultiHashMap *purses;
    424 
    425   /**
    426    * Transaction status code, set to error codes if applicable.
    427    */
    428   enum GNUNET_DB_QueryStatus qs;
    429 
    430 };
    431 
    432 
    433 /**
    434  * Create a new purse for @a purse_pub in @a pc.
    435  *
    436  * @param[in,out] pc context to update
    437  * @param purse_pub key for which to create a purse
    438  * @return NULL on error
    439  */
    440 static struct PurseSummary *
    441 setup_purse (struct PurseContext *pc,
    442              const struct TALER_PurseContractPublicKeyP *purse_pub)
    443 {
    444   struct PurseSummary *ps;
    445   struct GNUNET_HashCode key;
    446   enum GNUNET_DB_QueryStatus qs;
    447 
    448   GNUNET_CRYPTO_hash (purse_pub,
    449                       sizeof (*purse_pub),
    450                       &key);
    451   ps = GNUNET_CONTAINER_multihashmap_get (pc->purses,
    452                                           &key);
    453   if (NULL != ps)
    454   {
    455     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    456                 "Found purse `%s' summary in cache\n",
    457                 TALER_B2S (&ps->purse_pub));
    458     return ps;
    459   }
    460   ps = GNUNET_new (struct PurseSummary);
    461   ps->purse_pub = *purse_pub;
    462   GNUNET_assert (GNUNET_OK ==
    463                  TALER_amount_set_zero (TALER_ARL_currency,
    464                                         &ps->balance));
    465   /* get purse meta-data from exchange DB */
    466   qs = TALER_EXCHANGEDB_select_purse (TALER_ARL_edb,
    467                                       purse_pub,
    468                                       &ps->creation_date,
    469                                       &ps->expiration_date,
    470                                       &ps->total_value,
    471                                       &ps->exchange_balance,
    472                                       &ps->h_contract_terms,
    473                                       &ps->merge_timestamp,
    474                                       &ps->purse_deleted,
    475                                       &ps->purse_refunded);
    476   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    477               "Loaded purse `%s' meta-data (%d)\n",
    478               TALER_B2S (purse_pub),
    479               (int) qs);
    480   if (0 >= qs)
    481   {
    482     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    483                 "Failed to load meta-data of purse `%s'\n",
    484                 TALER_B2S (&ps->purse_pub));
    485     GNUNET_free (ps);
    486     pc->qs = qs;
    487     return NULL;
    488   }
    489   GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
    490   qs = load_auditor_purse_summary (ps);
    491   if (0 > qs)
    492   {
    493     GNUNET_free (ps);
    494     pc->qs = qs;
    495     return NULL;
    496   }
    497   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    498               "Starting purse `%s' analysis\n",
    499               TALER_B2S (purse_pub));
    500   GNUNET_assert (GNUNET_OK ==
    501                  GNUNET_CONTAINER_multihashmap_put (pc->purses,
    502                                                     &key,
    503                                                     ps,
    504                                                     GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)
    505                  );
    506   return ps;
    507 }
    508 
    509 
    510 /**
    511  * Function called on purse requests.
    512  *
    513  * @param cls closure
    514  * @param rowid which row in the database was the request stored in
    515  * @param purse_pub public key of the purse
    516  * @param merge_pub public key representing the merge capability
    517  * @param purse_creation when was the purse created
    518  * @param purse_expiration when would an unmerged purse expire
    519  * @param h_contract_terms contract associated with the purse
    520  * @param age_limit the age limit for deposits into the purse
    521  * @param target_amount amount to be put into the purse
    522  * @param purse_sig signature of the purse over the initialization data
    523  * @return #GNUNET_OK to continue to iterate
    524    */
    525 static enum GNUNET_GenericReturnValue
    526 handle_purse_requested (
    527   void *cls,
    528   uint64_t rowid,
    529   const struct TALER_PurseContractPublicKeyP *purse_pub,
    530   const struct TALER_PurseMergePublicKeyP *merge_pub,
    531   struct GNUNET_TIME_Timestamp purse_creation,
    532   struct GNUNET_TIME_Timestamp purse_expiration,
    533   const struct TALER_PrivateContractHashP *h_contract_terms,
    534   uint32_t age_limit,
    535   const struct TALER_Amount *target_amount,
    536   const struct TALER_PurseContractSignatureP *purse_sig)
    537 {
    538   struct PurseContext *pc = cls;
    539   struct PurseSummary *ps;
    540   struct GNUNET_HashCode key;
    541 
    542   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    543               "Handling purse request `%s'\n",
    544               TALER_B2S (purse_pub));
    545   GNUNET_assert (rowid >= TALER_ARL_USE_PP (purse_request_serial_id));
    546   TALER_ARL_USE_PP (purse_request_serial_id) = rowid + 1;
    547   if (GNUNET_OK !=
    548       TALER_wallet_purse_create_verify (purse_expiration,
    549                                         h_contract_terms,
    550                                         merge_pub,
    551                                         age_limit,
    552                                         target_amount,
    553                                         purse_pub,
    554                                         purse_sig))
    555   {
    556     struct TALER_AUDITORDB_BadSigLosses bsl = {
    557       .problem_row_id = rowid,
    558       .operation = (char *) "purse-request",
    559       .loss = *target_amount,
    560       .operation_specific_pub = purse_pub->eddsa_pub
    561     };
    562     enum GNUNET_DB_QueryStatus qs;
    563 
    564     qs = TALER_AUDITORDB_insert_bad_sig_losses (
    565       TALER_ARL_adb,
    566       &bsl);
    567     if (qs < 0)
    568     {
    569       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    570       pc->qs = qs;
    571       return GNUNET_SYSERR;
    572     }
    573     TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_total_bad_sig_loss),
    574                           &TALER_ARL_USE_AB (purse_total_bad_sig_loss),
    575                           target_amount);
    576   }
    577   GNUNET_CRYPTO_hash (purse_pub,
    578                       sizeof (*purse_pub),
    579                       &key);
    580   ps = GNUNET_new (struct PurseSummary);
    581   ps->purse_pub = *purse_pub;
    582   GNUNET_assert (GNUNET_OK ==
    583                  TALER_amount_set_zero (TALER_ARL_currency,
    584                                         &ps->balance));
    585   ps->creation_date = purse_creation;
    586   ps->expiration_date = purse_expiration;
    587   ps->total_value = *target_amount;
    588   ps->h_contract_terms = *h_contract_terms;
    589   {
    590     enum GNUNET_DB_QueryStatus qs;
    591 
    592     qs = load_auditor_purse_summary (ps);
    593     if (0 > qs)
    594     {
    595       GNUNET_free (ps);
    596       pc->qs = qs;
    597       return GNUNET_SYSERR;
    598     }
    599   }
    600   GNUNET_assert (GNUNET_OK ==
    601                  GNUNET_CONTAINER_multihashmap_put (pc->purses,
    602                                                     &key,
    603                                                     ps,
    604                                                     GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)
    605                  );
    606   return GNUNET_OK;
    607 }
    608 
    609 
    610 /**
    611  * Function called with details about purse deposits that have been made, with
    612  * the goal of auditing the deposit's execution.
    613  *
    614  * @param cls closure
    615  * @param rowid unique serial ID for the deposit in our DB
    616  * @param deposit deposit details
    617  * @param reserve_pub which reserve is the purse merged into, NULL if unknown
    618  * @param flags purse flags
    619  * @param auditor_balance purse balance (according to the
    620  *          auditor during auditing)
    621  * @param purse_total target amount the purse should reach
    622  * @param denom_pub denomination public key of @a coin_pub
    623  * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
    624  */
    625 static enum GNUNET_GenericReturnValue
    626 handle_purse_deposits (
    627   void *cls,
    628   uint64_t rowid,
    629   const struct TALER_EXCHANGEDB_PurseDeposit *deposit,
    630   const struct TALER_ReservePublicKeyP *reserve_pub,
    631   enum TALER_WalletAccountMergeFlags flags,
    632   const struct TALER_Amount *auditor_balance,
    633   const struct TALER_Amount *purse_total,
    634   const struct TALER_DenominationPublicKey *denom_pub)
    635 {
    636   struct PurseContext *pc = cls;
    637   struct TALER_Amount amount_minus_fee;
    638   const char *base_url
    639     = (NULL == deposit->exchange_base_url)
    640       ? TALER_ARL_exchange_url
    641       : deposit->exchange_base_url;
    642   struct TALER_DenominationHashP h_denom_pub;
    643 
    644   /* should be monotonically increasing */
    645   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    646               "Handling purse deposit `%s'\n",
    647               TALER_B2S (&deposit->purse_pub));
    648   GNUNET_assert (rowid >= TALER_ARL_USE_PP (purse_deposits_serial_id));
    649   TALER_ARL_USE_PP (purse_deposits_serial_id) = rowid + 1;
    650 
    651   {
    652     const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue;
    653     enum GNUNET_DB_QueryStatus qs;
    654 
    655     qs = TALER_ARL_get_denomination_info (denom_pub,
    656                                           &issue,
    657                                           &h_denom_pub);
    658     if (0 > qs)
    659     {
    660       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    661       if (GNUNET_DB_STATUS_HARD_ERROR == qs)
    662         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    663                     "Hard database error trying to get denomination %s from database!\n",
    664                     TALER_B2S (denom_pub));
    665       pc->qs = qs;
    666       return GNUNET_SYSERR;
    667     }
    668     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    669     {
    670       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    671                   "Failed to find denomination key for purse deposit `%s' in record %llu\n",
    672                   TALER_B2S (&deposit->purse_pub),
    673                   (unsigned long long) rowid);
    674       qs = report_row_inconsistency ("purse-deposit",
    675                                      rowid,
    676                                      "denomination key not found");
    677       if (0 > qs)
    678       {
    679         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    680         pc->qs = qs;
    681         return GNUNET_SYSERR;
    682       }
    683       return GNUNET_OK;
    684     }
    685     TALER_ARL_amount_subtract (&amount_minus_fee,
    686                                &deposit->amount,
    687                                &issue->fees.deposit);
    688   }
    689 
    690   if (GNUNET_OK !=
    691       TALER_wallet_purse_deposit_verify (base_url,
    692                                          &deposit->purse_pub,
    693                                          &deposit->amount,
    694                                          &h_denom_pub,
    695                                          &deposit->h_age_commitment,
    696                                          &deposit->coin_pub,
    697                                          &deposit->coin_sig))
    698   {
    699     struct TALER_AUDITORDB_BadSigLosses bsl = {
    700       .problem_row_id = rowid,
    701       .operation = (char *) "purse-deposit",
    702       .loss = deposit->amount,
    703       .operation_specific_pub = deposit->coin_pub.eddsa_pub
    704     };
    705     enum GNUNET_DB_QueryStatus qs;
    706 
    707     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    708                 "Failed to verify purse deposit signature on `%s' in record %llu\n",
    709                 TALER_B2S (&deposit->purse_pub),
    710                 (unsigned long long) rowid);
    711     qs = TALER_AUDITORDB_insert_bad_sig_losses (
    712       TALER_ARL_adb,
    713       &bsl);
    714     if (qs < 0)
    715     {
    716       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    717       pc->qs = qs;
    718       return GNUNET_SYSERR;
    719     }
    720     TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_total_bad_sig_loss),
    721                           &TALER_ARL_USE_AB (purse_total_bad_sig_loss),
    722                           &deposit->amount);
    723     return GNUNET_OK;
    724   }
    725 
    726   {
    727     struct PurseSummary *ps;
    728 
    729     ps = setup_purse (pc,
    730                       &deposit->purse_pub);
    731     if (NULL == ps)
    732     {
    733       enum GNUNET_DB_QueryStatus qs;
    734 
    735       if (0 > pc->qs)
    736       {
    737         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc->qs);
    738         return GNUNET_SYSERR;
    739       }
    740       GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == pc->qs);
    741       qs = report_row_inconsistency ("purse_deposit",
    742                                      rowid,
    743                                      "purse not found");
    744       if (0 > qs)
    745       {
    746         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    747         pc->qs = qs;
    748         return GNUNET_SYSERR;
    749       }
    750       return GNUNET_OK;
    751     }
    752     TALER_ARL_amount_add (&ps->balance,
    753                           &ps->balance,
    754                           &amount_minus_fee);
    755     TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_global_balance),
    756                           &TALER_ARL_USE_AB (purse_global_balance),
    757                           &amount_minus_fee);
    758   }
    759   return GNUNET_OK;
    760 }
    761 
    762 
    763 /**
    764  * Function called with details about purse merges that have been made, with
    765  * the goal of auditing the purse merge execution.
    766  *
    767  * @param cls closure
    768  * @param rowid unique serial ID for the deposit in our DB
    769  * @param partner_base_url where is the reserve, NULL for this exchange
    770  * @param amount total amount expected in the purse
    771  * @param balance current balance in the purse
    772  * @param flags purse flags
    773  * @param merge_pub merge capability key
    774  * @param reserve_pub reserve the merge affects
    775  * @param merge_sig signature affirming the merge
    776  * @param purse_pub purse key
    777  * @param merge_timestamp when did the merge happen
    778  * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
    779  */
    780 static enum GNUNET_GenericReturnValue
    781 handle_purse_merged (
    782   void *cls,
    783   uint64_t rowid,
    784   const char *partner_base_url,
    785   const struct TALER_Amount *amount,
    786   const struct TALER_Amount *balance,
    787   enum TALER_WalletAccountMergeFlags flags,
    788   const struct TALER_PurseMergePublicKeyP *merge_pub,
    789   const struct TALER_ReservePublicKeyP *reserve_pub,
    790   const struct TALER_PurseMergeSignatureP *merge_sig,
    791   const struct TALER_PurseContractPublicKeyP *purse_pub,
    792   struct GNUNET_TIME_Timestamp merge_timestamp)
    793 {
    794   struct PurseContext *pc = cls;
    795   struct PurseSummary *ps;
    796   enum GNUNET_DB_QueryStatus qs;
    797 
    798   /* should be monotonically increasing */
    799   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    800               "Handling purse merged `%s'\n",
    801               TALER_B2S (purse_pub));
    802   GNUNET_assert (rowid >= TALER_ARL_USE_PP (purse_merges_serial_id));
    803   TALER_ARL_USE_PP (purse_merges_serial_id) = rowid + 1;
    804 
    805   {
    806     struct TALER_NormalizedPayto reserve_url;
    807 
    808     reserve_url
    809       = TALER_reserve_make_payto (NULL == partner_base_url
    810                                   ? TALER_ARL_exchange_url
    811                                   : partner_base_url,
    812                                   reserve_pub);
    813     if (GNUNET_OK !=
    814         TALER_wallet_purse_merge_verify (reserve_url,
    815                                          merge_timestamp,
    816                                          purse_pub,
    817                                          merge_pub,
    818                                          merge_sig))
    819     {
    820       struct TALER_AUDITORDB_BadSigLosses bsl = {
    821         .problem_row_id = rowid,
    822         .operation = (char *) "merge-purse",
    823         .loss = *amount,
    824         .operation_specific_pub = merge_pub->eddsa_pub
    825       };
    826 
    827       GNUNET_free (reserve_url.normalized_payto);
    828       qs = TALER_AUDITORDB_insert_bad_sig_losses (
    829         TALER_ARL_adb,
    830         &bsl);
    831       if (qs < 0)
    832       {
    833         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    834         pc->qs = qs;
    835         return GNUNET_SYSERR;
    836       }
    837       TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_total_bad_sig_loss),
    838                             &TALER_ARL_USE_AB (purse_total_bad_sig_loss),
    839                             amount);
    840       return GNUNET_OK;
    841     }
    842     GNUNET_free (reserve_url.normalized_payto);
    843   }
    844 
    845   ps = setup_purse (pc,
    846                     purse_pub);
    847   if (NULL == ps)
    848   {
    849     if (pc->qs < 0)
    850     {
    851       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc->qs);
    852       return GNUNET_SYSERR;
    853     }
    854     GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == pc->qs);
    855     qs = report_row_inconsistency ("purse-merge",
    856                                    rowid,
    857                                    "purse not found");
    858     if (qs < 0)
    859     {
    860       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    861       pc->qs = qs;
    862       return GNUNET_SYSERR;
    863     }
    864     return GNUNET_OK;
    865   }
    866   GNUNET_break (0 ==
    867                 GNUNET_TIME_timestamp_cmp (merge_timestamp,
    868                                            ==,
    869                                            ps->merge_timestamp));
    870   TALER_ARL_amount_add (&ps->balance,
    871                         &ps->balance,
    872                         amount);
    873   return GNUNET_OK;
    874 }
    875 
    876 
    877 /**
    878  * Function called with details about account merge requests that have been
    879  * made, with the goal of auditing the account merge execution.
    880  *
    881  * @param cls closure
    882  * @param rowid unique serial ID for the deposit in our DB
    883  * @param reserve_pub reserve affected by the merge
    884  * @param purse_pub purse being merged
    885  * @param h_contract_terms hash over contract of the purse
    886  * @param purse_expiration when would the purse expire
    887  * @param amount total amount in the purse
    888  * @param min_age minimum age of all coins deposited into the purse
    889  * @param flags how was the purse created
    890  * @param purse_fee if a purse fee was paid, how high is it
    891  * @param merge_timestamp when was the merge approved
    892  * @param reserve_sig signature by reserve approving the merge
    893  * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
    894  */
    895 static enum GNUNET_GenericReturnValue
    896 handle_account_merged (
    897   void *cls,
    898   uint64_t rowid,
    899   const struct TALER_ReservePublicKeyP *reserve_pub,
    900   const struct TALER_PurseContractPublicKeyP *purse_pub,
    901   const struct TALER_PrivateContractHashP *h_contract_terms,
    902   struct GNUNET_TIME_Timestamp purse_expiration,
    903   const struct TALER_Amount *amount,
    904   uint32_t min_age,
    905   enum TALER_WalletAccountMergeFlags flags,
    906   const struct TALER_Amount *purse_fee,
    907   struct GNUNET_TIME_Timestamp merge_timestamp,
    908   const struct TALER_ReserveSignatureP *reserve_sig)
    909 {
    910   struct PurseContext *pc = cls;
    911   struct PurseSummary *ps;
    912   enum GNUNET_DB_QueryStatus qs;
    913 
    914   /* should be monotonically increasing */
    915   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    916               "Handling account merge on purse `%s'\n",
    917               TALER_B2S (purse_pub));
    918   GNUNET_assert (rowid >= TALER_ARL_USE_PP (purse_account_merge_serial_id));
    919   TALER_ARL_USE_PP (purse_account_merge_serial_id) = rowid + 1;
    920   if (GNUNET_OK !=
    921       TALER_wallet_account_merge_verify (merge_timestamp,
    922                                          purse_pub,
    923                                          purse_expiration,
    924                                          h_contract_terms,
    925                                          amount,
    926                                          purse_fee,
    927                                          min_age,
    928                                          flags,
    929                                          reserve_pub,
    930                                          reserve_sig))
    931   {
    932     struct TALER_AUDITORDB_BadSigLosses bsl = {
    933       .problem_row_id = rowid,
    934       .operation = (char *) "account-merge",
    935       .loss = *purse_fee,
    936       .operation_specific_pub = reserve_pub->eddsa_pub
    937     };
    938 
    939     qs = TALER_AUDITORDB_insert_bad_sig_losses (
    940       TALER_ARL_adb,
    941       &bsl);
    942     if (qs < 0)
    943     {
    944       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    945       pc->qs = qs;
    946       return GNUNET_SYSERR;
    947     }
    948     TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_total_bad_sig_loss),
    949                           &TALER_ARL_USE_AB (purse_total_bad_sig_loss),
    950                           purse_fee);
    951     return GNUNET_OK;
    952   }
    953   ps = setup_purse (pc,
    954                     purse_pub);
    955   if (NULL == ps)
    956   {
    957     if (0 > pc->qs)
    958     {
    959       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc->qs);
    960       return GNUNET_SYSERR;
    961     }
    962     GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == pc->qs);
    963     qs = report_row_inconsistency ("account-merge",
    964                                    rowid,
    965                                    "purse not found");
    966     if (0 > qs)
    967     {
    968       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    969       pc->qs = qs;
    970       return GNUNET_SYSERR;
    971     }
    972     return GNUNET_OK;
    973   }
    974   TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_global_balance),
    975                         &TALER_ARL_USE_AB (purse_global_balance),
    976                         purse_fee);
    977   TALER_ARL_amount_add (&ps->balance,
    978                         &ps->balance,
    979                         purse_fee);
    980   return GNUNET_OK;
    981 }
    982 
    983 
    984 /**
    985  * Function called with details about purse decisions that have been made.
    986  *
    987  * @param cls closure
    988  * @param rowid unique serial ID for the deposit in our DB
    989  * @param purse_pub which purse was the decision made on
    990  * @param refunded true if decision was to refund
    991  * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
    992  */
    993 static enum GNUNET_GenericReturnValue
    994 handle_purse_decision (
    995   void *cls,
    996   uint64_t rowid,
    997   const struct TALER_PurseContractPublicKeyP *purse_pub,
    998   bool refunded)
    999 {
   1000   struct PurseContext *pc = cls;
   1001   struct PurseSummary *ps;
   1002   struct GNUNET_HashCode key;
   1003   enum GNUNET_DB_QueryStatus qs;
   1004   struct TALER_Amount purse_fee;
   1005   struct TALER_Amount balance_without_purse_fee;
   1006 
   1007   GNUNET_assert (rowid >= TALER_ARL_USE_PP (purse_decision_serial_id));
   1008   TALER_ARL_USE_PP (purse_decision_serial_id) = rowid + 1;
   1009   ps = setup_purse (pc,
   1010                     purse_pub);
   1011   if (NULL == ps)
   1012   {
   1013     if (0 > pc->qs)
   1014     {
   1015       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc->qs);
   1016       return GNUNET_SYSERR;
   1017     }
   1018     qs = report_row_inconsistency ("purse-decision",
   1019                                    rowid,
   1020                                    "purse not found");
   1021     if (0 > qs)
   1022     {
   1023       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1024       pc->qs = qs;
   1025       return GNUNET_SYSERR;
   1026     }
   1027     return GNUNET_OK;
   1028   }
   1029   qs = get_purse_fee (ps->creation_date,
   1030                       &purse_fee);
   1031   if (0 > qs)
   1032   {
   1033     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1034     pc->qs = qs;
   1035     return GNUNET_SYSERR;
   1036   }
   1037   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
   1038     return GNUNET_OK; /* already reported */
   1039   if (0 >
   1040       TALER_amount_subtract (&balance_without_purse_fee,
   1041                              &ps->balance,
   1042                              &purse_fee))
   1043   {
   1044     qs = report_row_inconsistency ("purse-request",
   1045                                    rowid,
   1046                                    "purse fee higher than balance");
   1047     if (0 > qs)
   1048     {
   1049       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1050       pc->qs = qs;
   1051       return GNUNET_SYSERR;
   1052     }
   1053     GNUNET_assert (GNUNET_OK ==
   1054                    TALER_amount_set_zero (TALER_ARL_currency,
   1055                                           &balance_without_purse_fee));
   1056   }
   1057 
   1058   if (refunded)
   1059   {
   1060     if (-1 != TALER_amount_cmp (&balance_without_purse_fee,
   1061                                 &ps->total_value))
   1062     {
   1063       qs = report_amount_arithmetic_inconsistency ("purse-decision: refund",
   1064                                                    rowid,
   1065                                                    &balance_without_purse_fee,
   1066                                                    &ps->total_value,
   1067                                                    0);
   1068       if (0 > qs)
   1069       {
   1070         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1071         pc->qs = qs;
   1072         return GNUNET_SYSERR;
   1073       }
   1074     }
   1075     if ( (internal_checks) &&
   1076          (! ps->purse_refunded) )
   1077     {
   1078       qs = report_row_inconsistency (
   1079         "purse-decision",
   1080         rowid,
   1081         "purse not marked as refunded (internal check)");
   1082       if (qs < 0)
   1083       {
   1084         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1085         pc->qs = qs;
   1086         return GNUNET_SYSERR;
   1087       }
   1088     }
   1089   }
   1090   else
   1091   {
   1092     if (-1 == TALER_amount_cmp (&balance_without_purse_fee,
   1093                                 &ps->total_value))
   1094     {
   1095       qs = report_amount_arithmetic_inconsistency ("purse-decision: merge",
   1096                                                    rowid,
   1097                                                    &ps->total_value,
   1098                                                    &balance_without_purse_fee,
   1099                                                    0);
   1100       if (0 > qs)
   1101       {
   1102         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1103         pc->qs = qs;
   1104         return GNUNET_SYSERR;
   1105       }
   1106       TALER_ARL_amount_add (&TALER_ARL_USE_AB (
   1107                               purse_total_balance_insufficient_loss),
   1108                             &TALER_ARL_USE_AB (
   1109                               purse_total_balance_insufficient_loss),
   1110                             &ps->total_value);
   1111     }
   1112   }
   1113 
   1114   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1115               "Deleting purse with decision `%s'\n",
   1116               TALER_B2S (&ps->purse_pub));
   1117   qs = TALER_AUDITORDB_delete_purse_info (TALER_ARL_adb,
   1118                                           purse_pub);
   1119   if (qs < 0)
   1120   {
   1121     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1122     pc->qs = qs;
   1123     return GNUNET_SYSERR;
   1124   }
   1125   GNUNET_CRYPTO_hash (purse_pub,
   1126                       sizeof (*purse_pub),
   1127                       &key);
   1128   GNUNET_assert (GNUNET_YES ==
   1129                  GNUNET_CONTAINER_multihashmap_remove (pc->purses,
   1130                                                        &key,
   1131                                                        ps));
   1132   GNUNET_free (ps);
   1133   return GNUNET_OK;
   1134 }
   1135 
   1136 
   1137 /**
   1138  * Function called on explicitly deleted purses.
   1139  *
   1140  * @param cls closure
   1141  * @param deletion_serial_id row ID with the deletion data
   1142  * @param purse_pub public key of the purse
   1143  * @param purse_sig signature affirming deletion of the purse
   1144  * @return #GNUNET_OK to continue to iterate
   1145  */
   1146 static enum GNUNET_GenericReturnValue
   1147 handle_purse_deletion (
   1148   void *cls,
   1149   uint64_t deletion_serial_id,
   1150   const struct TALER_PurseContractPublicKeyP *purse_pub,
   1151   const struct TALER_PurseContractSignatureP *purse_sig)
   1152 {
   1153   struct PurseContext *pc = cls;
   1154   struct PurseSummary *ps;
   1155 
   1156   ps = setup_purse (pc,
   1157                     purse_pub);
   1158   if (NULL == ps)
   1159   {
   1160     GNUNET_break (0);
   1161     return GNUNET_SYSERR;
   1162   }
   1163   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1164               "Handling purse `%s' deletion\n",
   1165               TALER_B2S (purse_pub));
   1166   if (GNUNET_OK !=
   1167       TALER_wallet_purse_delete_verify (purse_pub,
   1168                                         purse_sig))
   1169   {
   1170     enum GNUNET_DB_QueryStatus qs;
   1171 
   1172     qs = report_row_inconsistency (
   1173       "purse-delete",
   1174       deletion_serial_id,
   1175       "purse deletion signature invalid");
   1176     if (qs < 0)
   1177     {
   1178       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1179       pc->qs = qs;
   1180       return GNUNET_SYSERR;
   1181     }
   1182   }
   1183   else
   1184   {
   1185     if ( (internal_checks) &&
   1186          (! ps->purse_deleted) )
   1187     {
   1188       enum GNUNET_DB_QueryStatus qs;
   1189 
   1190       qs = report_row_inconsistency (
   1191         "purse-delete",
   1192         deletion_serial_id,
   1193         "purse not marked as deleted (internal check)");
   1194       if (qs < 0)
   1195       {
   1196         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1197         pc->qs = qs;
   1198         return GNUNET_SYSERR;
   1199       }
   1200     }
   1201   }
   1202   return GNUNET_OK;
   1203 }
   1204 
   1205 
   1206 /**
   1207  * Function called on expired purses.
   1208  *
   1209  * @param cls closure
   1210  * @param purse_pub public key of the purse
   1211  * @param balance amount of money in the purse
   1212  * @param expiration_date when did the purse expire?
   1213  * @return #GNUNET_OK to continue to iterate
   1214  */
   1215 static enum GNUNET_GenericReturnValue
   1216 handle_purse_expired (
   1217   void *cls,
   1218   const struct TALER_PurseContractPublicKeyP *purse_pub,
   1219   const struct TALER_Amount *balance,
   1220   struct GNUNET_TIME_Timestamp expiration_date)
   1221 {
   1222   struct PurseContext *pc = cls;
   1223   enum GNUNET_DB_QueryStatus qs;
   1224   struct TALER_AUDITORDB_PurseNotClosedInconsistencies pnci = {
   1225     .amount = *balance,
   1226     .expiration_date = expiration_date.abs_time,
   1227     .purse_pub = purse_pub->eddsa_pub
   1228   };
   1229 
   1230   qs = TALER_AUDITORDB_insert_purse_not_closed_inconsistencies (
   1231     TALER_ARL_adb,
   1232     &pnci);
   1233   if (qs < 0)
   1234   {
   1235     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1236     pc->qs = qs;
   1237     return GNUNET_SYSERR;
   1238   }
   1239   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1240               "Handling purse expiration `%s'\n",
   1241               TALER_B2S (purse_pub));
   1242   TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_total_delayed_decisions),
   1243                         &TALER_ARL_USE_AB (purse_total_delayed_decisions),
   1244                         balance);
   1245   return GNUNET_OK;
   1246 }
   1247 
   1248 
   1249 /**
   1250  * Check that the purse summary matches what the exchange database
   1251  * thinks about the purse, and update our own state of the purse.
   1252  *
   1253  * Remove all purses that we are happy with from the DB.
   1254  *
   1255  * @param cls our `struct PurseContext`
   1256  * @param key hash of the purse public key
   1257  * @param value a `struct PurseSummary`
   1258  * @return #GNUNET_OK to process more entries
   1259  */
   1260 static enum GNUNET_GenericReturnValue
   1261 verify_purse_balance (void *cls,
   1262                       const struct GNUNET_HashCode *key,
   1263                       void *value)
   1264 {
   1265   struct PurseContext *pc = cls;
   1266   struct PurseSummary *ps = value;
   1267   enum GNUNET_DB_QueryStatus qs;
   1268 
   1269   if (internal_checks)
   1270   {
   1271     struct TALER_Amount pf;
   1272     struct TALER_Amount balance_without_purse_fee;
   1273 
   1274     /* subtract purse fee from ps->balance to get actual balance we expect, as
   1275        we track the balance including purse fee, while the exchange subtracts
   1276        the purse fee early on. */
   1277     qs = get_purse_fee (ps->creation_date,
   1278                         &pf);
   1279     if (qs < 0)
   1280     {
   1281       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1282       pc->qs = qs;
   1283       return GNUNET_SYSERR;
   1284     }
   1285     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
   1286       return GNUNET_OK; /* error already reported */
   1287     if (0 >
   1288         TALER_amount_subtract (&balance_without_purse_fee,
   1289                                &ps->balance,
   1290                                &pf))
   1291     {
   1292       qs = report_row_inconsistency ("purse",
   1293                                      0,
   1294                                      "purse fee higher than balance");
   1295       if (qs < 0)
   1296       {
   1297         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1298         pc->qs = qs;
   1299         return GNUNET_SYSERR;
   1300       }
   1301       GNUNET_assert (GNUNET_OK ==
   1302                      TALER_amount_set_zero (TALER_ARL_currency,
   1303                                             &balance_without_purse_fee));
   1304     }
   1305 
   1306     if (0 != TALER_amount_cmp (&ps->exchange_balance,
   1307                                &balance_without_purse_fee))
   1308     {
   1309       qs = report_amount_arithmetic_inconsistency ("purse-balance",
   1310                                                    0,
   1311                                                    &ps->exchange_balance,
   1312                                                    &balance_without_purse_fee,
   1313                                                    0);
   1314       if (qs < 0)
   1315       {
   1316         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1317         pc->qs = qs;
   1318         return GNUNET_SYSERR;
   1319       }
   1320     }
   1321   }
   1322 
   1323   if (ps->had_pi)
   1324   {
   1325     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1326                 "Updating purse `%s'\n",
   1327                 TALER_B2S (&ps->purse_pub));
   1328     qs = TALER_AUDITORDB_update_purse_info (TALER_ARL_adb,
   1329                                             &ps->purse_pub,
   1330                                             &ps->balance);
   1331   }
   1332   else
   1333   {
   1334     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1335                 "Inserting purse `%s'\n",
   1336                 TALER_B2S (&ps->purse_pub));
   1337     qs = TALER_AUDITORDB_insert_purse_info (TALER_ARL_adb,
   1338                                             &ps->purse_pub,
   1339                                             &ps->balance,
   1340                                             ps->expiration_date);
   1341     ps->had_pi = true;
   1342   }
   1343   if (qs < 0)
   1344   {
   1345     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1346     pc->qs = qs;
   1347     return GNUNET_SYSERR;
   1348   }
   1349   GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
   1350   return GNUNET_OK;
   1351 }
   1352 
   1353 
   1354 /**
   1355  * Clear memory from the purses hash map.
   1356  *
   1357  * @param cls our `struct PurseContext`
   1358  * @param key hash of the purse public key
   1359  * @param value a `struct PurseSummary`
   1360  * @return #GNUNET_OK to process more entries
   1361  */
   1362 static enum GNUNET_GenericReturnValue
   1363 release_purse_balance (void *cls,
   1364                        const struct GNUNET_HashCode *key,
   1365                        void *value)
   1366 {
   1367   struct PurseContext *pc = cls;
   1368   struct PurseSummary *ps = value;
   1369 
   1370   GNUNET_assert (GNUNET_YES ==
   1371                  GNUNET_CONTAINER_multihashmap_remove (pc->purses,
   1372                                                        key,
   1373                                                        ps));
   1374   GNUNET_free (ps);
   1375   return GNUNET_OK;
   1376 }
   1377 
   1378 
   1379 /**
   1380  * Analyze purses for being well-formed.
   1381  *
   1382  * @param cls NULL
   1383  * @return transaction status code
   1384  */
   1385 static enum GNUNET_DB_QueryStatus
   1386 analyze_purses (void *cls)
   1387 {
   1388   struct PurseContext pc;
   1389   enum GNUNET_DB_QueryStatus qs;
   1390   bool had_pp;
   1391   bool had_bal;
   1392 
   1393   (void) cls;
   1394   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1395               "Analyzing purses\n");
   1396   qs = TALER_AUDITORDB_get_auditor_progress (
   1397     TALER_ARL_adb,
   1398     TALER_ARL_GET_PP (purse_account_merge_serial_id),
   1399     TALER_ARL_GET_PP (purse_decision_serial_id),
   1400     TALER_ARL_GET_PP (purse_deletion_serial_id),
   1401     TALER_ARL_GET_PP (purse_deposits_serial_id),
   1402     TALER_ARL_GET_PP (purse_merges_serial_id),
   1403     TALER_ARL_GET_PP (purse_request_serial_id),
   1404     TALER_ARL_GET_PP (purse_open_counter),
   1405     NULL);
   1406   if (0 > qs)
   1407   {
   1408     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1409     return qs;
   1410   }
   1411   had_pp = (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs);
   1412   if (had_pp)
   1413   {
   1414     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1415                 "Resuming purse audit at %llu/%llu/%llu/%llu/%llu/%llu/%llu\n",
   1416                 (unsigned long long) TALER_ARL_USE_PP (
   1417                   purse_open_counter),
   1418                 (unsigned long long) TALER_ARL_USE_PP (
   1419                   purse_request_serial_id),
   1420                 (unsigned long long) TALER_ARL_USE_PP (
   1421                   purse_decision_serial_id),
   1422                 (unsigned long long) TALER_ARL_USE_PP (
   1423                   purse_deletion_serial_id),
   1424                 (unsigned long long) TALER_ARL_USE_PP (
   1425                   purse_merges_serial_id),
   1426                 (unsigned long long) TALER_ARL_USE_PP (
   1427                   purse_deposits_serial_id),
   1428                 (unsigned long long) TALER_ARL_USE_PP (
   1429                   purse_account_merge_serial_id));
   1430   }
   1431   else
   1432   {
   1433     GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
   1434                 "First analysis using this auditor, starting audit from scratch\n");
   1435   }
   1436   pc.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
   1437   qs = TALER_AUDITORDB_get_balance (
   1438     TALER_ARL_adb,
   1439     TALER_ARL_GET_AB (purse_global_balance),
   1440     TALER_ARL_GET_AB (purse_total_balance_insufficient_loss),
   1441     TALER_ARL_GET_AB (purse_total_delayed_decisions),
   1442     TALER_ARL_GET_AB (purse_total_balance_purse_not_closed),
   1443     TALER_ARL_GET_AB (purse_total_arithmetic_delta_plus),
   1444     TALER_ARL_GET_AB (purse_total_arithmetic_delta_minus),
   1445     TALER_ARL_GET_AB (purse_total_bad_sig_loss),
   1446     NULL);
   1447   if (qs < 0)
   1448   {
   1449     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1450     return qs;
   1451   }
   1452   had_bal = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
   1453   pc.purses = GNUNET_CONTAINER_multihashmap_create (512,
   1454                                                     GNUNET_NO);
   1455 
   1456   qs = TALER_TALER_EXCHANGEDB_select_purse_requests_above_serial_id (
   1457     TALER_ARL_edb,
   1458     TALER_ARL_USE_PP (purse_request_serial_id),
   1459     &handle_purse_requested,
   1460     &pc);
   1461   if (qs < 0)
   1462   {
   1463     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1464     return qs;
   1465   }
   1466   if (pc.qs < 0)
   1467   {
   1468     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
   1469     return pc.qs;
   1470   }
   1471   qs = TALER_TALER_TALER_EXCHANGEDB_select_purse_merges_above_serial_id (
   1472     TALER_ARL_edb,
   1473     TALER_ARL_USE_PP (purse_merges_serial_id),
   1474     &handle_purse_merged,
   1475     &pc);
   1476   if (qs < 0)
   1477   {
   1478     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1479     return qs;
   1480   }
   1481   if (pc.qs < 0)
   1482   {
   1483     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
   1484     return pc.qs;
   1485   }
   1486 
   1487   qs = TALER_TALER_EXCHANGEDB_select_purse_deposits_above_serial_id (
   1488     TALER_ARL_edb,
   1489     TALER_ARL_USE_PP (purse_deposits_serial_id),
   1490     &handle_purse_deposits,
   1491     &pc);
   1492   if (qs < 0)
   1493   {
   1494     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1495     return qs;
   1496   }
   1497   if (pc.qs < 0)
   1498   {
   1499     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
   1500     return pc.qs;
   1501   }
   1502 
   1503   /* Charge purse fee! */
   1504   qs = TALER_EXCHANGEDB_select_account_merges_above_serial_id (
   1505     TALER_ARL_edb,
   1506     TALER_ARL_USE_PP (purse_account_merge_serial_id),
   1507     &handle_account_merged,
   1508     &pc);
   1509   if (qs < 0)
   1510   {
   1511     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1512     return qs;
   1513   }
   1514   if (pc.qs < 0)
   1515   {
   1516     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
   1517     return pc.qs;
   1518   }
   1519 
   1520   qs = TALER_EXCHANGEDB_select_all_purse_decisions_above_serial_id (
   1521     TALER_ARL_edb,
   1522     TALER_ARL_USE_PP (purse_decision_serial_id),
   1523     &handle_purse_decision,
   1524     &pc);
   1525   if (qs < 0)
   1526   {
   1527     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1528     return qs;
   1529   }
   1530   if (pc.qs < 0)
   1531   {
   1532     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
   1533     return pc.qs;
   1534   }
   1535 
   1536   qs = TALER_EXCHANGEDB_select_all_purse_deletions_above_serial_id (
   1537     TALER_ARL_edb,
   1538     TALER_ARL_USE_PP (purse_deletion_serial_id),
   1539     &handle_purse_deletion,
   1540     &pc);
   1541   if (qs < 0)
   1542   {
   1543     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1544     return qs;
   1545   }
   1546   if (pc.qs < 0)
   1547   {
   1548     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
   1549     return pc.qs;
   1550   }
   1551 
   1552   qs = TALER_AUDITORDB_select_purse_expired (
   1553     TALER_ARL_adb,
   1554     &handle_purse_expired,
   1555     &pc);
   1556   if (qs < 0)
   1557   {
   1558     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1559     return qs;
   1560   }
   1561   if (pc.qs < 0)
   1562   {
   1563     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
   1564     return pc.qs;
   1565   }
   1566 
   1567   GNUNET_CONTAINER_multihashmap_iterate (pc.purses,
   1568                                          &verify_purse_balance,
   1569                                          &pc);
   1570   GNUNET_CONTAINER_multihashmap_iterate (pc.purses,
   1571                                          &release_purse_balance,
   1572                                          &pc);
   1573   GNUNET_break (0 ==
   1574                 GNUNET_CONTAINER_multihashmap_size (pc.purses));
   1575   GNUNET_CONTAINER_multihashmap_destroy (pc.purses);
   1576   if (pc.qs < 0)
   1577   {
   1578     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
   1579     return pc.qs;
   1580   }
   1581   if (had_bal)
   1582     qs = TALER_AUDITORDB_update_balance (
   1583       TALER_ARL_adb,
   1584       TALER_ARL_SET_AB (purse_global_balance),
   1585       TALER_ARL_SET_AB (purse_total_balance_insufficient_loss),
   1586       TALER_ARL_SET_AB (purse_total_delayed_decisions),
   1587       TALER_ARL_SET_AB (purse_total_balance_purse_not_closed),
   1588       TALER_ARL_SET_AB (purse_total_arithmetic_delta_plus),
   1589       TALER_ARL_SET_AB (purse_total_arithmetic_delta_minus),
   1590       TALER_ARL_SET_AB (purse_total_bad_sig_loss),
   1591       NULL);
   1592   else
   1593     qs = TALER_AUDITORDB_insert_balance (
   1594       TALER_ARL_adb,
   1595       TALER_ARL_SET_AB (purse_global_balance),
   1596       TALER_ARL_SET_AB (purse_total_balance_insufficient_loss),
   1597       TALER_ARL_SET_AB (purse_total_delayed_decisions),
   1598       TALER_ARL_SET_AB (purse_total_balance_purse_not_closed),
   1599       TALER_ARL_SET_AB (purse_total_arithmetic_delta_plus),
   1600       TALER_ARL_SET_AB (purse_total_arithmetic_delta_minus),
   1601       TALER_ARL_SET_AB (purse_total_bad_sig_loss),
   1602       NULL);
   1603   if (0 > qs)
   1604   {
   1605     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1606                 "Failed to update auditor DB, not recording progress\n");
   1607     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1608     return qs;
   1609   }
   1610   if (had_pp)
   1611     qs = TALER_AUDITORDB_update_auditor_progress (
   1612       TALER_ARL_adb,
   1613       TALER_ARL_SET_PP (purse_account_merge_serial_id),
   1614       TALER_ARL_SET_PP (purse_decision_serial_id),
   1615       TALER_ARL_SET_PP (purse_deletion_serial_id),
   1616       TALER_ARL_SET_PP (purse_deposits_serial_id),
   1617       TALER_ARL_SET_PP (purse_merges_serial_id),
   1618       TALER_ARL_SET_PP (purse_request_serial_id),
   1619       TALER_ARL_SET_PP (purse_open_counter),
   1620       NULL);
   1621   else
   1622     qs = TALER_AUDITORDB_insert_auditor_progress (
   1623       TALER_ARL_adb,
   1624       TALER_ARL_SET_PP (purse_account_merge_serial_id),
   1625       TALER_ARL_SET_PP (purse_decision_serial_id),
   1626       TALER_ARL_SET_PP (purse_deletion_serial_id),
   1627       TALER_ARL_SET_PP (purse_deposits_serial_id),
   1628       TALER_ARL_SET_PP (purse_merges_serial_id),
   1629       TALER_ARL_SET_PP (purse_request_serial_id),
   1630       TALER_ARL_SET_PP (purse_open_counter),
   1631       NULL);
   1632   if (0 > qs)
   1633   {
   1634     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1635                 "Failed to update auditor DB, not recording progress\n");
   1636     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1637     return qs;
   1638   }
   1639   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1640               "Concluded purse audit step at %llu/%llu/%llu/%llu/%llu/%llu\n",
   1641               (unsigned long long) TALER_ARL_USE_PP (
   1642                 purse_request_serial_id),
   1643               (unsigned long long) TALER_ARL_USE_PP (
   1644                 purse_decision_serial_id),
   1645               (unsigned long long) TALER_ARL_USE_PP (
   1646                 purse_deletion_serial_id),
   1647               (unsigned long long) TALER_ARL_USE_PP (
   1648                 purse_merges_serial_id),
   1649               (unsigned long long) TALER_ARL_USE_PP (
   1650                 purse_deposits_serial_id),
   1651               (unsigned long long) TALER_ARL_USE_PP (
   1652                 purse_account_merge_serial_id));
   1653   return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
   1654 }
   1655 
   1656 
   1657 /**
   1658  * Function called on events received from Postgres.
   1659  *
   1660  * @param cls closure, NULL
   1661  * @param extra additional event data provided
   1662  * @param extra_size number of bytes in @a extra
   1663  */
   1664 static void
   1665 db_notify (void *cls,
   1666            const void *extra,
   1667            size_t extra_size)
   1668 {
   1669   (void) cls;
   1670   (void) extra;
   1671   (void) extra_size;
   1672 
   1673   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1674               "Received notification to wake purses\n");
   1675   if (GNUNET_OK !=
   1676       TALER_ARL_setup_sessions_and_run (&analyze_purses,
   1677                                         NULL))
   1678   {
   1679     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1680                 "Audit failed\n");
   1681     GNUNET_SCHEDULER_shutdown ();
   1682     global_ret = EXIT_FAILURE;
   1683     return;
   1684   }
   1685 }
   1686 
   1687 
   1688 /**
   1689  * Function called on shutdown.
   1690  */
   1691 static void
   1692 do_shutdown (void *cls)
   1693 {
   1694   (void) cls;
   1695 
   1696   if (NULL != eh)
   1697   {
   1698     TALER_AUDITORDB_event_listen_cancel (eh);
   1699     eh = NULL;
   1700   }
   1701   TALER_ARL_done ();
   1702 }
   1703 
   1704 
   1705 /**
   1706  * Main function that will be run.
   1707  *
   1708  * @param cls closure
   1709  * @param args remaining command-line arguments
   1710  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
   1711  * @param c configuration
   1712  */
   1713 static void
   1714 run (void *cls,
   1715      char *const *args,
   1716      const char *cfgfile,
   1717      const struct GNUNET_CONFIGURATION_Handle *c)
   1718 {
   1719   (void) cls;
   1720   (void) args;
   1721   (void) cfgfile;
   1722 
   1723   cfg = c;
   1724   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
   1725                                  NULL);
   1726   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1727               "Launching purses auditor\n");
   1728   if (GNUNET_OK !=
   1729       TALER_ARL_init (c))
   1730   {
   1731     global_ret = EXIT_FAILURE;
   1732     return;
   1733   }
   1734   if (test_mode != 1)
   1735   {
   1736     struct GNUNET_DB_EventHeaderP es = {
   1737       .size = htons (sizeof (es)),
   1738       .type = htons (TALER_DBEVENT_EXCHANGE_AUDITOR_WAKE_HELPER_PURSES)
   1739     };
   1740 
   1741     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1742                 "Running helper indefinitely\n");
   1743     eh = TALER_AUDITORDB_event_listen (TALER_ARL_adb,
   1744                                        &es,
   1745                                        GNUNET_TIME_UNIT_FOREVER_REL,
   1746                                        &db_notify,
   1747                                        NULL);
   1748   }
   1749   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1750               "Starting audit\n");
   1751   if (GNUNET_OK !=
   1752       TALER_ARL_setup_sessions_and_run (&analyze_purses,
   1753                                         NULL))
   1754   {
   1755     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1756                 "Audit failed\n");
   1757     GNUNET_SCHEDULER_shutdown ();
   1758     global_ret = EXIT_FAILURE;
   1759     return;
   1760   }
   1761 }
   1762 
   1763 
   1764 /**
   1765  * The main function to check the database's handling of purses.
   1766  *
   1767  * @param argc number of arguments from the command line
   1768  * @param argv command line arguments
   1769  * @return 0 ok, 1 on error
   1770  */
   1771 int
   1772 main (int argc,
   1773       char *const *argv)
   1774 {
   1775   const struct GNUNET_GETOPT_CommandLineOption options[] = {
   1776     GNUNET_GETOPT_option_flag ('i',
   1777                                "internal",
   1778                                "perform checks only applicable for exchange-internal audits",
   1779                                &internal_checks),
   1780     GNUNET_GETOPT_option_flag ('t',
   1781                                "test",
   1782                                "run in test mode and exit when idle",
   1783                                &test_mode),
   1784     GNUNET_GETOPT_option_timetravel ('T',
   1785                                      "timetravel"),
   1786     GNUNET_GETOPT_OPTION_END
   1787   };
   1788   enum GNUNET_GenericReturnValue ret;
   1789 
   1790   ret = GNUNET_PROGRAM_run (
   1791     TALER_AUDITOR_project_data (),
   1792     argc,
   1793     argv,
   1794     "taler-helper-auditor-purses",
   1795     gettext_noop ("Audit Taler exchange purse handling"),
   1796     options,
   1797     &run,
   1798     NULL);
   1799   if (GNUNET_SYSERR == ret)
   1800     return EXIT_INVALIDARGUMENT;
   1801   if (GNUNET_NO == ret)
   1802     return EXIT_SUCCESS;
   1803   return global_ret;
   1804 }
   1805 
   1806 
   1807 /* end of taler-helper-auditor-purses.c */