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


      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   return ps;
    506 }
    507 
    508 
    509 /**
    510  * Function called on purse requests.
    511  *
    512  * @param cls closure
    513  * @param rowid which row in the database was the request stored in
    514  * @param purse_pub public key of the purse
    515  * @param merge_pub public key representing the merge capability
    516  * @param purse_creation when was the purse created
    517  * @param purse_expiration when would an unmerged purse expire
    518  * @param h_contract_terms contract associated with the purse
    519  * @param age_limit the age limit for deposits into the purse
    520  * @param target_amount amount to be put into the purse
    521  * @param purse_sig signature of the purse over the initialization data
    522  * @return #GNUNET_OK to continue to iterate
    523    */
    524 static enum GNUNET_GenericReturnValue
    525 handle_purse_requested (
    526   void *cls,
    527   uint64_t rowid,
    528   const struct TALER_PurseContractPublicKeyP *purse_pub,
    529   const struct TALER_PurseMergePublicKeyP *merge_pub,
    530   struct GNUNET_TIME_Timestamp purse_creation,
    531   struct GNUNET_TIME_Timestamp purse_expiration,
    532   const struct TALER_PrivateContractHashP *h_contract_terms,
    533   uint32_t age_limit,
    534   const struct TALER_Amount *target_amount,
    535   const struct TALER_PurseContractSignatureP *purse_sig)
    536 {
    537   struct PurseContext *pc = cls;
    538   struct PurseSummary *ps;
    539   struct GNUNET_HashCode key;
    540 
    541   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    542               "Handling purse request `%s'\n",
    543               TALER_B2S (purse_pub));
    544   TALER_ARL_USE_PP (purse_request_serial_id) = rowid;
    545   if (GNUNET_OK !=
    546       TALER_wallet_purse_create_verify (purse_expiration,
    547                                         h_contract_terms,
    548                                         merge_pub,
    549                                         age_limit,
    550                                         target_amount,
    551                                         purse_pub,
    552                                         purse_sig))
    553   {
    554     struct TALER_AUDITORDB_BadSigLosses bsl = {
    555       .problem_row_id = rowid,
    556       .operation = (char *) "purse-request",
    557       .loss = *target_amount,
    558       .operation_specific_pub = purse_pub->eddsa_pub
    559     };
    560     enum GNUNET_DB_QueryStatus qs;
    561 
    562     qs = TALER_AUDITORDB_insert_bad_sig_losses (
    563       TALER_ARL_adb,
    564       &bsl);
    565     if (qs < 0)
    566     {
    567       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    568       pc->qs = qs;
    569       return GNUNET_SYSERR;
    570     }
    571     TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_total_bad_sig_loss),
    572                           &TALER_ARL_USE_AB (purse_total_bad_sig_loss),
    573                           target_amount);
    574   }
    575   GNUNET_CRYPTO_hash (purse_pub,
    576                       sizeof (*purse_pub),
    577                       &key);
    578   ps = GNUNET_new (struct PurseSummary);
    579   ps->purse_pub = *purse_pub;
    580   GNUNET_assert (GNUNET_OK ==
    581                  TALER_amount_set_zero (TALER_ARL_currency,
    582                                         &ps->balance));
    583   ps->creation_date = purse_creation;
    584   ps->expiration_date = purse_expiration;
    585   ps->total_value = *target_amount;
    586   ps->h_contract_terms = *h_contract_terms;
    587   {
    588     enum GNUNET_DB_QueryStatus qs;
    589 
    590     qs = load_auditor_purse_summary (ps);
    591     if (0 > qs)
    592     {
    593       GNUNET_free (ps);
    594       pc->qs = qs;
    595       return GNUNET_SYSERR;
    596     }
    597   }
    598   GNUNET_assert (GNUNET_OK ==
    599                  GNUNET_CONTAINER_multihashmap_put (pc->purses,
    600                                                     &key,
    601                                                     ps,
    602                                                     GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
    603   return GNUNET_OK;
    604 }
    605 
    606 
    607 /**
    608  * Function called with details about purse deposits that have been made, with
    609  * the goal of auditing the deposit's execution.
    610  *
    611  * @param cls closure
    612  * @param rowid unique serial ID for the deposit in our DB
    613  * @param deposit deposit details
    614  * @param reserve_pub which reserve is the purse merged into, NULL if unknown
    615  * @param flags purse flags
    616  * @param auditor_balance purse balance (according to the
    617  *          auditor during auditing)
    618  * @param purse_total target amount the purse should reach
    619  * @param denom_pub denomination public key of @a coin_pub
    620  * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
    621  */
    622 static enum GNUNET_GenericReturnValue
    623 handle_purse_deposits (
    624   void *cls,
    625   uint64_t rowid,
    626   const struct TALER_EXCHANGEDB_PurseDeposit *deposit,
    627   const struct TALER_ReservePublicKeyP *reserve_pub,
    628   enum TALER_WalletAccountMergeFlags flags,
    629   const struct TALER_Amount *auditor_balance,
    630   const struct TALER_Amount *purse_total,
    631   const struct TALER_DenominationPublicKey *denom_pub)
    632 {
    633   struct PurseContext *pc = cls;
    634   struct TALER_Amount amount_minus_fee;
    635   const char *base_url
    636     = (NULL == deposit->exchange_base_url)
    637       ? TALER_ARL_exchange_url
    638       : deposit->exchange_base_url;
    639   struct TALER_DenominationHashP h_denom_pub;
    640 
    641   /* should be monotonically increasing */
    642   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    643               "Handling purse deposit `%s'\n",
    644               TALER_B2S (&deposit->purse_pub));
    645   GNUNET_assert (rowid >= TALER_ARL_USE_PP (purse_deposits_serial_id));
    646   TALER_ARL_USE_PP (purse_deposits_serial_id) = rowid + 1;
    647 
    648   {
    649     const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue;
    650     enum GNUNET_DB_QueryStatus qs;
    651 
    652     qs = TALER_ARL_get_denomination_info (denom_pub,
    653                                           &issue,
    654                                           &h_denom_pub);
    655     if (0 > qs)
    656     {
    657       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    658       if (GNUNET_DB_STATUS_HARD_ERROR == qs)
    659         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    660                     "Hard database error trying to get denomination %s from database!\n",
    661                     TALER_B2S (denom_pub));
    662       pc->qs = qs;
    663       return GNUNET_SYSERR;
    664     }
    665     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    666     {
    667       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    668                   "Failed to find denomination key for purse deposit `%s' in record %llu\n",
    669                   TALER_B2S (&deposit->purse_pub),
    670                   (unsigned long long) rowid);
    671       qs = report_row_inconsistency ("purse-deposit",
    672                                      rowid,
    673                                      "denomination key not found");
    674       if (0 > qs)
    675       {
    676         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    677         pc->qs = qs;
    678         return GNUNET_SYSERR;
    679       }
    680       return GNUNET_OK;
    681     }
    682     TALER_ARL_amount_subtract (&amount_minus_fee,
    683                                &deposit->amount,
    684                                &issue->fees.deposit);
    685   }
    686 
    687   if (GNUNET_OK !=
    688       TALER_wallet_purse_deposit_verify (base_url,
    689                                          &deposit->purse_pub,
    690                                          &deposit->amount,
    691                                          &h_denom_pub,
    692                                          &deposit->h_age_commitment,
    693                                          &deposit->coin_pub,
    694                                          &deposit->coin_sig))
    695   {
    696     struct TALER_AUDITORDB_BadSigLosses bsl = {
    697       .problem_row_id = rowid,
    698       .operation = (char *) "purse-deposit",
    699       .loss = deposit->amount,
    700       .operation_specific_pub = deposit->coin_pub.eddsa_pub
    701     };
    702     enum GNUNET_DB_QueryStatus qs;
    703 
    704     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    705                 "Failed to verify purse deposit signature on `%s' in record %llu\n",
    706                 TALER_B2S (&deposit->purse_pub),
    707                 (unsigned long long) rowid);
    708     qs = TALER_AUDITORDB_insert_bad_sig_losses (
    709       TALER_ARL_adb,
    710       &bsl);
    711     if (qs < 0)
    712     {
    713       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    714       pc->qs = qs;
    715       return GNUNET_SYSERR;
    716     }
    717     TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_total_bad_sig_loss),
    718                           &TALER_ARL_USE_AB (purse_total_bad_sig_loss),
    719                           &deposit->amount);
    720     return GNUNET_OK;
    721   }
    722 
    723   {
    724     struct PurseSummary *ps;
    725 
    726     ps = setup_purse (pc,
    727                       &deposit->purse_pub);
    728     if (NULL == ps)
    729     {
    730       enum GNUNET_DB_QueryStatus qs;
    731 
    732       if (0 > pc->qs)
    733       {
    734         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc->qs);
    735         return GNUNET_SYSERR;
    736       }
    737       GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == pc->qs);
    738       qs = report_row_inconsistency ("purse_deposit",
    739                                      rowid,
    740                                      "purse not found");
    741       if (0 > qs)
    742       {
    743         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    744         pc->qs = qs;
    745         return GNUNET_SYSERR;
    746       }
    747       return GNUNET_OK;
    748     }
    749     TALER_ARL_amount_add (&ps->balance,
    750                           &ps->balance,
    751                           &amount_minus_fee);
    752     TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_global_balance),
    753                           &TALER_ARL_USE_AB (purse_global_balance),
    754                           &amount_minus_fee);
    755   }
    756   return GNUNET_OK;
    757 }
    758 
    759 
    760 /**
    761  * Function called with details about purse merges that have been made, with
    762  * the goal of auditing the purse merge execution.
    763  *
    764  * @param cls closure
    765  * @param rowid unique serial ID for the deposit in our DB
    766  * @param partner_base_url where is the reserve, NULL for this exchange
    767  * @param amount total amount expected in the purse
    768  * @param balance current balance in the purse
    769  * @param flags purse flags
    770  * @param merge_pub merge capability key
    771  * @param reserve_pub reserve the merge affects
    772  * @param merge_sig signature affirming the merge
    773  * @param purse_pub purse key
    774  * @param merge_timestamp when did the merge happen
    775  * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
    776  */
    777 static enum GNUNET_GenericReturnValue
    778 handle_purse_merged (
    779   void *cls,
    780   uint64_t rowid,
    781   const char *partner_base_url,
    782   const struct TALER_Amount *amount,
    783   const struct TALER_Amount *balance,
    784   enum TALER_WalletAccountMergeFlags flags,
    785   const struct TALER_PurseMergePublicKeyP *merge_pub,
    786   const struct TALER_ReservePublicKeyP *reserve_pub,
    787   const struct TALER_PurseMergeSignatureP *merge_sig,
    788   const struct TALER_PurseContractPublicKeyP *purse_pub,
    789   struct GNUNET_TIME_Timestamp merge_timestamp)
    790 {
    791   struct PurseContext *pc = cls;
    792   struct PurseSummary *ps;
    793   enum GNUNET_DB_QueryStatus qs;
    794 
    795   /* should be monotonically increasing */
    796   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    797               "Handling purse merged `%s'\n",
    798               TALER_B2S (purse_pub));
    799   GNUNET_assert (rowid >= TALER_ARL_USE_PP (purse_merges_serial_id));
    800   TALER_ARL_USE_PP (purse_merges_serial_id) = rowid + 1;
    801 
    802   {
    803     struct TALER_NormalizedPayto reserve_url;
    804 
    805     reserve_url
    806       = TALER_reserve_make_payto (NULL == partner_base_url
    807                                   ? TALER_ARL_exchange_url
    808                                   : partner_base_url,
    809                                   reserve_pub);
    810     if (GNUNET_OK !=
    811         TALER_wallet_purse_merge_verify (reserve_url,
    812                                          merge_timestamp,
    813                                          purse_pub,
    814                                          merge_pub,
    815                                          merge_sig))
    816     {
    817       struct TALER_AUDITORDB_BadSigLosses bsl = {
    818         .problem_row_id = rowid,
    819         .operation = (char *) "merge-purse",
    820         .loss = *amount,
    821         .operation_specific_pub = merge_pub->eddsa_pub
    822       };
    823 
    824       GNUNET_free (reserve_url.normalized_payto);
    825       qs = TALER_AUDITORDB_insert_bad_sig_losses (
    826         TALER_ARL_adb,
    827         &bsl);
    828       if (qs < 0)
    829       {
    830         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    831         pc->qs = qs;
    832         return GNUNET_SYSERR;
    833       }
    834       TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_total_bad_sig_loss),
    835                             &TALER_ARL_USE_AB (purse_total_bad_sig_loss),
    836                             amount);
    837       return GNUNET_OK;
    838     }
    839     GNUNET_free (reserve_url.normalized_payto);
    840   }
    841 
    842   ps = setup_purse (pc,
    843                     purse_pub);
    844   if (NULL == ps)
    845   {
    846     if (0 < pc->qs)
    847     {
    848       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc->qs);
    849       return GNUNET_SYSERR;
    850     }
    851     GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == pc->qs);
    852     qs = report_row_inconsistency ("purse-merge",
    853                                    rowid,
    854                                    "purse not found");
    855     if (qs < 0)
    856     {
    857       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    858       pc->qs = qs;
    859       return GNUNET_SYSERR;
    860     }
    861     return GNUNET_OK;
    862   }
    863   GNUNET_break (0 ==
    864                 GNUNET_TIME_timestamp_cmp (merge_timestamp,
    865                                            ==,
    866                                            ps->merge_timestamp));
    867   TALER_ARL_amount_add (&ps->balance,
    868                         &ps->balance,
    869                         amount);
    870   return GNUNET_OK;
    871 }
    872 
    873 
    874 /**
    875  * Function called with details about account merge requests that have been
    876  * made, with the goal of auditing the account merge execution.
    877  *
    878  * @param cls closure
    879  * @param rowid unique serial ID for the deposit in our DB
    880  * @param reserve_pub reserve affected by the merge
    881  * @param purse_pub purse being merged
    882  * @param h_contract_terms hash over contract of the purse
    883  * @param purse_expiration when would the purse expire
    884  * @param amount total amount in the purse
    885  * @param min_age minimum age of all coins deposited into the purse
    886  * @param flags how was the purse created
    887  * @param purse_fee if a purse fee was paid, how high is it
    888  * @param merge_timestamp when was the merge approved
    889  * @param reserve_sig signature by reserve approving the merge
    890  * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
    891  */
    892 static enum GNUNET_GenericReturnValue
    893 handle_account_merged (
    894   void *cls,
    895   uint64_t rowid,
    896   const struct TALER_ReservePublicKeyP *reserve_pub,
    897   const struct TALER_PurseContractPublicKeyP *purse_pub,
    898   const struct TALER_PrivateContractHashP *h_contract_terms,
    899   struct GNUNET_TIME_Timestamp purse_expiration,
    900   const struct TALER_Amount *amount,
    901   uint32_t min_age,
    902   enum TALER_WalletAccountMergeFlags flags,
    903   const struct TALER_Amount *purse_fee,
    904   struct GNUNET_TIME_Timestamp merge_timestamp,
    905   const struct TALER_ReserveSignatureP *reserve_sig)
    906 {
    907   struct PurseContext *pc = cls;
    908   struct PurseSummary *ps;
    909   enum GNUNET_DB_QueryStatus qs;
    910 
    911   /* should be monotonically increasing */
    912   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    913               "Handling account merge on purse `%s'\n",
    914               TALER_B2S (purse_pub));
    915   GNUNET_assert (rowid >= TALER_ARL_USE_PP (purse_account_merge_serial_id));
    916   TALER_ARL_USE_PP (purse_account_merge_serial_id) = rowid + 1;
    917   if (GNUNET_OK !=
    918       TALER_wallet_account_merge_verify (merge_timestamp,
    919                                          purse_pub,
    920                                          purse_expiration,
    921                                          h_contract_terms,
    922                                          amount,
    923                                          purse_fee,
    924                                          min_age,
    925                                          flags,
    926                                          reserve_pub,
    927                                          reserve_sig))
    928   {
    929     struct TALER_AUDITORDB_BadSigLosses bsl = {
    930       .problem_row_id = rowid,
    931       .operation = (char *) "account-merge",
    932       .loss = *purse_fee,
    933       .operation_specific_pub = reserve_pub->eddsa_pub
    934     };
    935 
    936     qs = TALER_AUDITORDB_insert_bad_sig_losses (
    937       TALER_ARL_adb,
    938       &bsl);
    939     if (qs < 0)
    940     {
    941       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    942       pc->qs = qs;
    943       return GNUNET_SYSERR;
    944     }
    945     TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_total_bad_sig_loss),
    946                           &TALER_ARL_USE_AB (purse_total_bad_sig_loss),
    947                           purse_fee);
    948     return GNUNET_OK;
    949   }
    950   ps = setup_purse (pc,
    951                     purse_pub);
    952   if (NULL == ps)
    953   {
    954     if (0 > pc->qs)
    955     {
    956       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc->qs);
    957       return GNUNET_SYSERR;
    958     }
    959     GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == pc->qs);
    960     qs = report_row_inconsistency ("account-merge",
    961                                    rowid,
    962                                    "purse not found");
    963     if (0 > qs)
    964     {
    965       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    966       pc->qs = qs;
    967       return GNUNET_SYSERR;
    968     }
    969     return GNUNET_OK;
    970   }
    971   TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_global_balance),
    972                         &TALER_ARL_USE_AB (purse_global_balance),
    973                         purse_fee);
    974   TALER_ARL_amount_add (&ps->balance,
    975                         &ps->balance,
    976                         purse_fee);
    977   return GNUNET_OK;
    978 }
    979 
    980 
    981 /**
    982  * Function called with details about purse decisions that have been made.
    983  *
    984  * @param cls closure
    985  * @param rowid unique serial ID for the deposit in our DB
    986  * @param purse_pub which purse was the decision made on
    987  * @param refunded true if decision was to refund
    988  * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
    989  */
    990 static enum GNUNET_GenericReturnValue
    991 handle_purse_decision (
    992   void *cls,
    993   uint64_t rowid,
    994   const struct TALER_PurseContractPublicKeyP *purse_pub,
    995   bool refunded)
    996 {
    997   struct PurseContext *pc = cls;
    998   struct PurseSummary *ps;
    999   struct GNUNET_HashCode key;
   1000   enum GNUNET_DB_QueryStatus qs;
   1001   struct TALER_Amount purse_fee;
   1002   struct TALER_Amount balance_without_purse_fee;
   1003 
   1004   TALER_ARL_USE_PP (purse_decision_serial_id) = rowid;
   1005   ps = setup_purse (pc,
   1006                     purse_pub);
   1007   if (NULL == ps)
   1008   {
   1009     if (0 > pc->qs)
   1010     {
   1011       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc->qs);
   1012       return GNUNET_SYSERR;
   1013     }
   1014     qs = report_row_inconsistency ("purse-decision",
   1015                                    rowid,
   1016                                    "purse not found");
   1017     if (0 > qs)
   1018     {
   1019       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1020       pc->qs = qs;
   1021       return GNUNET_SYSERR;
   1022     }
   1023     return GNUNET_OK;
   1024   }
   1025   qs = get_purse_fee (ps->creation_date,
   1026                       &purse_fee);
   1027   if (0 > qs)
   1028   {
   1029     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1030     pc->qs = qs;
   1031     return GNUNET_SYSERR;
   1032   }
   1033   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
   1034     return GNUNET_OK; /* already reported */
   1035   if (0 >
   1036       TALER_amount_subtract (&balance_without_purse_fee,
   1037                              &ps->balance,
   1038                              &purse_fee))
   1039   {
   1040     qs = report_row_inconsistency ("purse-request",
   1041                                    rowid,
   1042                                    "purse fee higher than balance");
   1043     if (0 > qs)
   1044     {
   1045       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1046       pc->qs = qs;
   1047       return GNUNET_SYSERR;
   1048     }
   1049     GNUNET_assert (GNUNET_OK ==
   1050                    TALER_amount_set_zero (TALER_ARL_currency,
   1051                                           &balance_without_purse_fee));
   1052   }
   1053 
   1054   if (refunded)
   1055   {
   1056     if (-1 != TALER_amount_cmp (&balance_without_purse_fee,
   1057                                 &ps->total_value))
   1058     {
   1059       qs = report_amount_arithmetic_inconsistency ("purse-decision: refund",
   1060                                                    rowid,
   1061                                                    &balance_without_purse_fee,
   1062                                                    &ps->total_value,
   1063                                                    0);
   1064       if (0 > qs)
   1065       {
   1066         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1067         pc->qs = qs;
   1068         return GNUNET_SYSERR;
   1069       }
   1070     }
   1071     if ( (internal_checks) &&
   1072          (! ps->purse_refunded) )
   1073     {
   1074       qs = report_row_inconsistency (
   1075         "purse-decision",
   1076         rowid,
   1077         "purse not marked as refunded (internal check)");
   1078       if (qs < 0)
   1079       {
   1080         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1081         pc->qs = qs;
   1082         return GNUNET_SYSERR;
   1083       }
   1084     }
   1085   }
   1086   else
   1087   {
   1088     if (-1 == TALER_amount_cmp (&balance_without_purse_fee,
   1089                                 &ps->total_value))
   1090     {
   1091       qs = report_amount_arithmetic_inconsistency ("purse-decision: merge",
   1092                                                    rowid,
   1093                                                    &ps->total_value,
   1094                                                    &balance_without_purse_fee,
   1095                                                    0);
   1096       if (0 > qs)
   1097       {
   1098         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1099         pc->qs = qs;
   1100         return GNUNET_SYSERR;
   1101       }
   1102       TALER_ARL_amount_add (&TALER_ARL_USE_AB (
   1103                               purse_total_balance_insufficient_loss),
   1104                             &TALER_ARL_USE_AB (
   1105                               purse_total_balance_insufficient_loss),
   1106                             &ps->total_value);
   1107     }
   1108   }
   1109 
   1110   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1111               "Deleting purse with decision `%s'\n",
   1112               TALER_B2S (&ps->purse_pub));
   1113   qs = TALER_AUDITORDB_delete_purse_info (TALER_ARL_adb,
   1114                                           purse_pub);
   1115   if (qs < 0)
   1116   {
   1117     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1118     pc->qs = qs;
   1119     return GNUNET_SYSERR;
   1120   }
   1121   GNUNET_CRYPTO_hash (purse_pub,
   1122                       sizeof (*purse_pub),
   1123                       &key);
   1124   GNUNET_assert (GNUNET_YES ==
   1125                  GNUNET_CONTAINER_multihashmap_remove (pc->purses,
   1126                                                        &key,
   1127                                                        ps));
   1128   GNUNET_free (ps);
   1129   return GNUNET_OK;
   1130 }
   1131 
   1132 
   1133 /**
   1134  * Function called on explicitly deleted purses.
   1135  *
   1136  * @param cls closure
   1137  * @param deletion_serial_id row ID with the deletion data
   1138  * @param purse_pub public key of the purse
   1139  * @param purse_sig signature affirming deletion of the purse
   1140  * @return #GNUNET_OK to continue to iterate
   1141  */
   1142 static enum GNUNET_GenericReturnValue
   1143 handle_purse_deletion (
   1144   void *cls,
   1145   uint64_t deletion_serial_id,
   1146   const struct TALER_PurseContractPublicKeyP *purse_pub,
   1147   const struct TALER_PurseContractSignatureP *purse_sig)
   1148 {
   1149   struct PurseContext *pc = cls;
   1150   struct PurseSummary *ps;
   1151 
   1152   ps = setup_purse (pc,
   1153                     purse_pub);
   1154   if (NULL == ps)
   1155   {
   1156     GNUNET_break (0);
   1157     return GNUNET_SYSERR;
   1158   }
   1159   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1160               "Handling purse `%s' deletion\n",
   1161               TALER_B2S (purse_pub));
   1162   if (GNUNET_OK !=
   1163       TALER_wallet_purse_delete_verify (purse_pub,
   1164                                         purse_sig))
   1165   {
   1166     enum GNUNET_DB_QueryStatus qs;
   1167 
   1168     qs = report_row_inconsistency (
   1169       "purse-delete",
   1170       deletion_serial_id,
   1171       "purse deletion signature invalid");
   1172     if (qs < 0)
   1173     {
   1174       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1175       pc->qs = qs;
   1176       return GNUNET_SYSERR;
   1177     }
   1178   }
   1179   else
   1180   {
   1181     if ( (internal_checks) &&
   1182          (! ps->purse_deleted) )
   1183     {
   1184       enum GNUNET_DB_QueryStatus qs;
   1185 
   1186       qs = report_row_inconsistency (
   1187         "purse-delete",
   1188         deletion_serial_id,
   1189         "purse not marked as deleted (internal check)");
   1190       if (qs < 0)
   1191       {
   1192         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1193         pc->qs = qs;
   1194         return GNUNET_SYSERR;
   1195       }
   1196     }
   1197   }
   1198   return GNUNET_OK;
   1199 }
   1200 
   1201 
   1202 /**
   1203  * Function called on expired purses.
   1204  *
   1205  * @param cls closure
   1206  * @param purse_pub public key of the purse
   1207  * @param balance amount of money in the purse
   1208  * @param expiration_date when did the purse expire?
   1209  * @return #GNUNET_OK to continue to iterate
   1210  */
   1211 static enum GNUNET_GenericReturnValue
   1212 handle_purse_expired (
   1213   void *cls,
   1214   const struct TALER_PurseContractPublicKeyP *purse_pub,
   1215   const struct TALER_Amount *balance,
   1216   struct GNUNET_TIME_Timestamp expiration_date)
   1217 {
   1218   struct PurseContext *pc = cls;
   1219   enum GNUNET_DB_QueryStatus qs;
   1220   struct TALER_AUDITORDB_PurseNotClosedInconsistencies pnci = {
   1221     .amount = *balance,
   1222     .expiration_date = expiration_date.abs_time,
   1223     .purse_pub = purse_pub->eddsa_pub
   1224   };
   1225 
   1226   qs = TALER_AUDITORDB_insert_purse_not_closed_inconsistencies (
   1227     TALER_ARL_adb,
   1228     &pnci);
   1229   if (qs < 0)
   1230   {
   1231     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1232     pc->qs = qs;
   1233     return GNUNET_SYSERR;
   1234   }
   1235   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1236               "Handling purse expiration `%s'\n",
   1237               TALER_B2S (purse_pub));
   1238   TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_total_delayed_decisions),
   1239                         &TALER_ARL_USE_AB (purse_total_delayed_decisions),
   1240                         balance);
   1241   return GNUNET_OK;
   1242 }
   1243 
   1244 
   1245 /**
   1246  * Check that the purse summary matches what the exchange database
   1247  * thinks about the purse, and update our own state of the purse.
   1248  *
   1249  * Remove all purses that we are happy with from the DB.
   1250  *
   1251  * @param cls our `struct PurseContext`
   1252  * @param key hash of the purse public key
   1253  * @param value a `struct PurseSummary`
   1254  * @return #GNUNET_OK to process more entries
   1255  */
   1256 static enum GNUNET_GenericReturnValue
   1257 verify_purse_balance (void *cls,
   1258                       const struct GNUNET_HashCode *key,
   1259                       void *value)
   1260 {
   1261   struct PurseContext *pc = cls;
   1262   struct PurseSummary *ps = value;
   1263   enum GNUNET_DB_QueryStatus qs;
   1264 
   1265   if (internal_checks)
   1266   {
   1267     struct TALER_Amount pf;
   1268     struct TALER_Amount balance_without_purse_fee;
   1269 
   1270     /* subtract purse fee from ps->balance to get actual balance we expect, as
   1271        we track the balance including purse fee, while the exchange subtracts
   1272        the purse fee early on. */
   1273     qs = get_purse_fee (ps->creation_date,
   1274                         &pf);
   1275     if (qs < 0)
   1276     {
   1277       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1278       pc->qs = qs;
   1279       return GNUNET_SYSERR;
   1280     }
   1281     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
   1282       return GNUNET_OK; /* error already reported */
   1283     if (0 >
   1284         TALER_amount_subtract (&balance_without_purse_fee,
   1285                                &ps->balance,
   1286                                &pf))
   1287     {
   1288       qs = report_row_inconsistency ("purse",
   1289                                      0,
   1290                                      "purse fee higher than balance");
   1291       if (qs < 0)
   1292       {
   1293         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1294         pc->qs = qs;
   1295         return GNUNET_SYSERR;
   1296       }
   1297       GNUNET_assert (GNUNET_OK ==
   1298                      TALER_amount_set_zero (TALER_ARL_currency,
   1299                                             &balance_without_purse_fee));
   1300     }
   1301 
   1302     if (0 != TALER_amount_cmp (&ps->exchange_balance,
   1303                                &balance_without_purse_fee))
   1304     {
   1305       qs = report_amount_arithmetic_inconsistency ("purse-balance",
   1306                                                    0,
   1307                                                    &ps->exchange_balance,
   1308                                                    &balance_without_purse_fee,
   1309                                                    0);
   1310       if (qs < 0)
   1311       {
   1312         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1313         pc->qs = qs;
   1314         return GNUNET_SYSERR;
   1315       }
   1316     }
   1317   }
   1318 
   1319   if (ps->had_pi)
   1320   {
   1321     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1322                 "Updating purse `%s'\n",
   1323                 TALER_B2S (&ps->purse_pub));
   1324     qs = TALER_AUDITORDB_update_purse_info (TALER_ARL_adb,
   1325                                             &ps->purse_pub,
   1326                                             &ps->balance);
   1327   }
   1328   else
   1329   {
   1330     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1331                 "Inserting purse `%s'\n",
   1332                 TALER_B2S (&ps->purse_pub));
   1333     qs = TALER_AUDITORDB_insert_purse_info (TALER_ARL_adb,
   1334                                             &ps->purse_pub,
   1335                                             &ps->balance,
   1336                                             ps->expiration_date);
   1337     ps->had_pi = true;
   1338   }
   1339   if (qs < 0)
   1340   {
   1341     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1342     pc->qs = qs;
   1343     return GNUNET_SYSERR;
   1344   }
   1345   GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
   1346   return GNUNET_OK;
   1347 }
   1348 
   1349 
   1350 /**
   1351  * Clear memory from the purses hash map.
   1352  *
   1353  * @param cls our `struct PurseContext`
   1354  * @param key hash of the purse public key
   1355  * @param value a `struct PurseSummary`
   1356  * @return #GNUNET_OK to process more entries
   1357  */
   1358 static enum GNUNET_GenericReturnValue
   1359 release_purse_balance (void *cls,
   1360                        const struct GNUNET_HashCode *key,
   1361                        void *value)
   1362 {
   1363   struct PurseContext *pc = cls;
   1364   struct PurseSummary *ps = value;
   1365 
   1366   GNUNET_assert (GNUNET_YES ==
   1367                  GNUNET_CONTAINER_multihashmap_remove (pc->purses,
   1368                                                        key,
   1369                                                        ps));
   1370   GNUNET_free (ps);
   1371   return GNUNET_OK;
   1372 }
   1373 
   1374 
   1375 /**
   1376  * Analyze purses for being well-formed.
   1377  *
   1378  * @param cls NULL
   1379  * @return transaction status code
   1380  */
   1381 static enum GNUNET_DB_QueryStatus
   1382 analyze_purses (void *cls)
   1383 {
   1384   struct PurseContext pc;
   1385   enum GNUNET_DB_QueryStatus qs;
   1386   bool had_pp;
   1387   bool had_bal;
   1388 
   1389   (void) cls;
   1390   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1391               "Analyzing purses\n");
   1392   qs = TALER_AUDITORDB_get_auditor_progress (
   1393     TALER_ARL_adb,
   1394     TALER_ARL_GET_PP (purse_account_merge_serial_id),
   1395     TALER_ARL_GET_PP (purse_decision_serial_id),
   1396     TALER_ARL_GET_PP (purse_deletion_serial_id),
   1397     TALER_ARL_GET_PP (purse_deposits_serial_id),
   1398     TALER_ARL_GET_PP (purse_merges_serial_id),
   1399     TALER_ARL_GET_PP (purse_request_serial_id),
   1400     TALER_ARL_GET_PP (purse_open_counter),
   1401     NULL);
   1402   if (0 > qs)
   1403   {
   1404     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1405     return qs;
   1406   }
   1407   had_pp = (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs);
   1408   if (had_pp)
   1409   {
   1410     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1411                 "Resuming purse audit at %llu/%llu/%llu/%llu/%llu/%llu/%llu\n",
   1412                 (unsigned long long) TALER_ARL_USE_PP (
   1413                   purse_open_counter),
   1414                 (unsigned long long) TALER_ARL_USE_PP (
   1415                   purse_request_serial_id),
   1416                 (unsigned long long) TALER_ARL_USE_PP (
   1417                   purse_decision_serial_id),
   1418                 (unsigned long long) TALER_ARL_USE_PP (
   1419                   purse_deletion_serial_id),
   1420                 (unsigned long long) TALER_ARL_USE_PP (
   1421                   purse_merges_serial_id),
   1422                 (unsigned long long) TALER_ARL_USE_PP (
   1423                   purse_deposits_serial_id),
   1424                 (unsigned long long) TALER_ARL_USE_PP (
   1425                   purse_account_merge_serial_id));
   1426   }
   1427   else
   1428   {
   1429     GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
   1430                 "First analysis using this auditor, starting audit from scratch\n");
   1431   }
   1432   pc.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
   1433   qs = TALER_AUDITORDB_get_balance (
   1434     TALER_ARL_adb,
   1435     TALER_ARL_GET_AB (purse_global_balance),
   1436     TALER_ARL_GET_AB (purse_total_balance_insufficient_loss),
   1437     TALER_ARL_GET_AB (purse_total_delayed_decisions),
   1438     TALER_ARL_GET_AB (purse_total_balance_purse_not_closed),
   1439     TALER_ARL_GET_AB (purse_total_arithmetic_delta_plus),
   1440     TALER_ARL_GET_AB (purse_total_arithmetic_delta_minus),
   1441     TALER_ARL_GET_AB (purse_total_bad_sig_loss),
   1442     NULL);
   1443   if (qs < 0)
   1444   {
   1445     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1446     return qs;
   1447   }
   1448   had_bal = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
   1449   pc.purses = GNUNET_CONTAINER_multihashmap_create (512,
   1450                                                     GNUNET_NO);
   1451 
   1452   qs = TALER_TALER_EXCHANGEDB_select_purse_requests_above_serial_id (
   1453     TALER_ARL_edb,
   1454     TALER_ARL_USE_PP (purse_request_serial_id),
   1455     &handle_purse_requested,
   1456     &pc);
   1457   if (qs < 0)
   1458   {
   1459     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1460     return qs;
   1461   }
   1462   if (pc.qs < 0)
   1463   {
   1464     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
   1465     return pc.qs;
   1466   }
   1467   qs = TALER_TALER_TALER_EXCHANGEDB_select_purse_merges_above_serial_id (
   1468     TALER_ARL_edb,
   1469     TALER_ARL_USE_PP (purse_merges_serial_id),
   1470     &handle_purse_merged,
   1471     &pc);
   1472   if (qs < 0)
   1473   {
   1474     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1475     return qs;
   1476   }
   1477   if (pc.qs < 0)
   1478   {
   1479     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
   1480     return pc.qs;
   1481   }
   1482 
   1483   qs = TALER_TALER_EXCHANGEDB_select_purse_deposits_above_serial_id (
   1484     TALER_ARL_edb,
   1485     TALER_ARL_USE_PP (purse_deposits_serial_id),
   1486     &handle_purse_deposits,
   1487     &pc);
   1488   if (qs < 0)
   1489   {
   1490     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1491     return qs;
   1492   }
   1493   if (pc.qs < 0)
   1494   {
   1495     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
   1496     return pc.qs;
   1497   }
   1498 
   1499   /* Charge purse fee! */
   1500   qs = TALER_EXCHANGEDB_select_account_merges_above_serial_id (
   1501     TALER_ARL_edb,
   1502     TALER_ARL_USE_PP (purse_account_merge_serial_id),
   1503     &handle_account_merged,
   1504     &pc);
   1505   if (qs < 0)
   1506   {
   1507     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1508     return qs;
   1509   }
   1510   if (pc.qs < 0)
   1511   {
   1512     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
   1513     return pc.qs;
   1514   }
   1515 
   1516   qs = TALER_EXCHANGEDB_select_all_purse_decisions_above_serial_id (
   1517     TALER_ARL_edb,
   1518     TALER_ARL_USE_PP (purse_decision_serial_id),
   1519     &handle_purse_decision,
   1520     &pc);
   1521   if (qs < 0)
   1522   {
   1523     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1524     return qs;
   1525   }
   1526   if (pc.qs < 0)
   1527   {
   1528     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
   1529     return pc.qs;
   1530   }
   1531 
   1532   qs = TALER_EXCHANGEDB_select_all_purse_deletions_above_serial_id (
   1533     TALER_ARL_edb,
   1534     TALER_ARL_USE_PP (purse_deletion_serial_id),
   1535     &handle_purse_deletion,
   1536     &pc);
   1537   if (qs < 0)
   1538   {
   1539     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1540     return qs;
   1541   }
   1542   if (pc.qs < 0)
   1543   {
   1544     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
   1545     return pc.qs;
   1546   }
   1547 
   1548   qs = TALER_AUDITORDB_select_purse_expired (
   1549     TALER_ARL_adb,
   1550     &handle_purse_expired,
   1551     &pc);
   1552   if (qs < 0)
   1553   {
   1554     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1555     return qs;
   1556   }
   1557   if (pc.qs < 0)
   1558   {
   1559     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
   1560     return pc.qs;
   1561   }
   1562 
   1563   GNUNET_CONTAINER_multihashmap_iterate (pc.purses,
   1564                                          &verify_purse_balance,
   1565                                          &pc);
   1566   GNUNET_CONTAINER_multihashmap_iterate (pc.purses,
   1567                                          &release_purse_balance,
   1568                                          &pc);
   1569   GNUNET_break (0 ==
   1570                 GNUNET_CONTAINER_multihashmap_size (pc.purses));
   1571   GNUNET_CONTAINER_multihashmap_destroy (pc.purses);
   1572   if (pc.qs < 0)
   1573   {
   1574     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
   1575     return pc.qs;
   1576   }
   1577   if (had_bal)
   1578     qs = TALER_AUDITORDB_update_balance (
   1579       TALER_ARL_adb,
   1580       TALER_ARL_SET_AB (purse_global_balance),
   1581       TALER_ARL_SET_AB (purse_total_balance_insufficient_loss),
   1582       TALER_ARL_SET_AB (purse_total_delayed_decisions),
   1583       TALER_ARL_SET_AB (purse_total_balance_purse_not_closed),
   1584       TALER_ARL_SET_AB (purse_total_arithmetic_delta_plus),
   1585       TALER_ARL_SET_AB (purse_total_arithmetic_delta_minus),
   1586       TALER_ARL_SET_AB (purse_total_bad_sig_loss),
   1587       NULL);
   1588   else
   1589     qs = TALER_AUDITORDB_insert_balance (
   1590       TALER_ARL_adb,
   1591       TALER_ARL_SET_AB (purse_global_balance),
   1592       TALER_ARL_SET_AB (purse_total_balance_insufficient_loss),
   1593       TALER_ARL_SET_AB (purse_total_delayed_decisions),
   1594       TALER_ARL_SET_AB (purse_total_balance_purse_not_closed),
   1595       TALER_ARL_SET_AB (purse_total_arithmetic_delta_plus),
   1596       TALER_ARL_SET_AB (purse_total_arithmetic_delta_minus),
   1597       TALER_ARL_SET_AB (purse_total_bad_sig_loss),
   1598       NULL);
   1599   if (0 > qs)
   1600   {
   1601     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1602                 "Failed to update auditor DB, not recording progress\n");
   1603     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1604     return qs;
   1605   }
   1606   if (had_pp)
   1607     qs = TALER_AUDITORDB_update_auditor_progress (
   1608       TALER_ARL_adb,
   1609       TALER_ARL_SET_PP (purse_account_merge_serial_id),
   1610       TALER_ARL_SET_PP (purse_decision_serial_id),
   1611       TALER_ARL_SET_PP (purse_deletion_serial_id),
   1612       TALER_ARL_SET_PP (purse_deposits_serial_id),
   1613       TALER_ARL_SET_PP (purse_merges_serial_id),
   1614       TALER_ARL_SET_PP (purse_request_serial_id),
   1615       TALER_ARL_SET_PP (purse_open_counter),
   1616       NULL);
   1617   else
   1618     qs = TALER_AUDITORDB_insert_auditor_progress (
   1619       TALER_ARL_adb,
   1620       TALER_ARL_SET_PP (purse_account_merge_serial_id),
   1621       TALER_ARL_SET_PP (purse_decision_serial_id),
   1622       TALER_ARL_SET_PP (purse_deletion_serial_id),
   1623       TALER_ARL_SET_PP (purse_deposits_serial_id),
   1624       TALER_ARL_SET_PP (purse_merges_serial_id),
   1625       TALER_ARL_SET_PP (purse_request_serial_id),
   1626       TALER_ARL_SET_PP (purse_open_counter),
   1627       NULL);
   1628   if (0 > qs)
   1629   {
   1630     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1631                 "Failed to update auditor DB, not recording progress\n");
   1632     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1633     return qs;
   1634   }
   1635   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1636               "Concluded purse audit step at %llu/%llu/%llu/%llu/%llu/%llu\n",
   1637               (unsigned long long) TALER_ARL_USE_PP (
   1638                 purse_request_serial_id),
   1639               (unsigned long long) TALER_ARL_USE_PP (
   1640                 purse_decision_serial_id),
   1641               (unsigned long long) TALER_ARL_USE_PP (
   1642                 purse_deletion_serial_id),
   1643               (unsigned long long) TALER_ARL_USE_PP (
   1644                 purse_merges_serial_id),
   1645               (unsigned long long) TALER_ARL_USE_PP (
   1646                 purse_deposits_serial_id),
   1647               (unsigned long long) TALER_ARL_USE_PP (
   1648                 purse_account_merge_serial_id));
   1649   return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
   1650 }
   1651 
   1652 
   1653 /**
   1654  * Function called on events received from Postgres.
   1655  *
   1656  * @param cls closure, NULL
   1657  * @param extra additional event data provided
   1658  * @param extra_size number of bytes in @a extra
   1659  */
   1660 static void
   1661 db_notify (void *cls,
   1662            const void *extra,
   1663            size_t extra_size)
   1664 {
   1665   (void) cls;
   1666   (void) extra;
   1667   (void) extra_size;
   1668 
   1669   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1670               "Received notification to wake purses\n");
   1671   if (GNUNET_OK !=
   1672       TALER_ARL_setup_sessions_and_run (&analyze_purses,
   1673                                         NULL))
   1674   {
   1675     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1676                 "Audit failed\n");
   1677     GNUNET_SCHEDULER_shutdown ();
   1678     global_ret = EXIT_FAILURE;
   1679     return;
   1680   }
   1681 }
   1682 
   1683 
   1684 /**
   1685  * Function called on shutdown.
   1686  */
   1687 static void
   1688 do_shutdown (void *cls)
   1689 {
   1690   (void) cls;
   1691 
   1692   if (NULL != eh)
   1693   {
   1694     TALER_AUDITORDB_event_listen_cancel (eh);
   1695     eh = NULL;
   1696   }
   1697   TALER_ARL_done ();
   1698 }
   1699 
   1700 
   1701 /**
   1702  * Main function that will be run.
   1703  *
   1704  * @param cls closure
   1705  * @param args remaining command-line arguments
   1706  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
   1707  * @param c configuration
   1708  */
   1709 static void
   1710 run (void *cls,
   1711      char *const *args,
   1712      const char *cfgfile,
   1713      const struct GNUNET_CONFIGURATION_Handle *c)
   1714 {
   1715   (void) cls;
   1716   (void) args;
   1717   (void) cfgfile;
   1718 
   1719   cfg = c;
   1720   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
   1721                                  NULL);
   1722   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1723               "Launching purses auditor\n");
   1724   if (GNUNET_OK !=
   1725       TALER_ARL_init (c))
   1726   {
   1727     global_ret = EXIT_FAILURE;
   1728     return;
   1729   }
   1730   if (test_mode != 1)
   1731   {
   1732     struct GNUNET_DB_EventHeaderP es = {
   1733       .size = htons (sizeof (es)),
   1734       .type = htons (TALER_DBEVENT_EXCHANGE_AUDITOR_WAKE_HELPER_PURSES)
   1735     };
   1736 
   1737     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1738                 "Running helper indefinitely\n");
   1739     eh = TALER_AUDITORDB_event_listen (TALER_ARL_adb,
   1740                                        &es,
   1741                                        GNUNET_TIME_UNIT_FOREVER_REL,
   1742                                        &db_notify,
   1743                                        NULL);
   1744   }
   1745   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1746               "Starting audit\n");
   1747   if (GNUNET_OK !=
   1748       TALER_ARL_setup_sessions_and_run (&analyze_purses,
   1749                                         NULL))
   1750   {
   1751     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1752                 "Audit failed\n");
   1753     GNUNET_SCHEDULER_shutdown ();
   1754     global_ret = EXIT_FAILURE;
   1755     return;
   1756   }
   1757 }
   1758 
   1759 
   1760 /**
   1761  * The main function to check the database's handling of purses.
   1762  *
   1763  * @param argc number of arguments from the command line
   1764  * @param argv command line arguments
   1765  * @return 0 ok, 1 on error
   1766  */
   1767 int
   1768 main (int argc,
   1769       char *const *argv)
   1770 {
   1771   const struct GNUNET_GETOPT_CommandLineOption options[] = {
   1772     GNUNET_GETOPT_option_flag ('i',
   1773                                "internal",
   1774                                "perform checks only applicable for exchange-internal audits",
   1775                                &internal_checks),
   1776     GNUNET_GETOPT_option_flag ('t',
   1777                                "test",
   1778                                "run in test mode and exit when idle",
   1779                                &test_mode),
   1780     GNUNET_GETOPT_option_timetravel ('T',
   1781                                      "timetravel"),
   1782     GNUNET_GETOPT_OPTION_END
   1783   };
   1784   enum GNUNET_GenericReturnValue ret;
   1785 
   1786   ret = GNUNET_PROGRAM_run (
   1787     TALER_AUDITOR_project_data (),
   1788     argc,
   1789     argv,
   1790     "taler-helper-auditor-purses",
   1791     gettext_noop ("Audit Taler exchange purse handling"),
   1792     options,
   1793     &run,
   1794     NULL);
   1795   if (GNUNET_SYSERR == ret)
   1796     return EXIT_INVALIDARGUMENT;
   1797   if (GNUNET_NO == ret)
   1798     return EXIT_SUCCESS;
   1799   return global_ret;
   1800 }
   1801 
   1802 
   1803 /* end of taler-helper-auditor-purses.c */