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


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