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


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