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


      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   qs = TALER_AUDITORDB_delete_purse_info (TALER_ARL_adb,
   1120                                           purse_pub);
   1121   if (qs < 0)
   1122   {
   1123     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1124     pc->qs = qs;
   1125     return GNUNET_SYSERR;
   1126   }
   1127   GNUNET_CRYPTO_hash (purse_pub,
   1128                       sizeof (*purse_pub),
   1129                       &key);
   1130   GNUNET_assert (GNUNET_YES ==
   1131                  GNUNET_CONTAINER_multihashmap_remove (pc->purses,
   1132                                                        &key,
   1133                                                        ps));
   1134   GNUNET_free (ps);
   1135   return GNUNET_OK;
   1136 }
   1137 
   1138 
   1139 /**
   1140  * Function called on explicitly deleted purses.
   1141  *
   1142  * @param pc closure
   1143  * @param deletion_serial_id row ID with the deletion data
   1144  * @param purse_pub public key of the purse
   1145  * @param purse_sig signature affirming deletion of the purse
   1146  * @return #GNUNET_OK to continue to iterate
   1147  */
   1148 static enum GNUNET_GenericReturnValue
   1149 handle_purse_deletion (
   1150   struct PurseContext *pc,
   1151   uint64_t deletion_serial_id,
   1152   const struct TALER_PurseContractPublicKeyP *purse_pub,
   1153   const struct TALER_PurseContractSignatureP *purse_sig)
   1154 {
   1155   struct PurseSummary *ps;
   1156 
   1157   ps = setup_purse (pc,
   1158                     purse_pub);
   1159   if (NULL == ps)
   1160   {
   1161     GNUNET_break (0);
   1162     return GNUNET_SYSERR;
   1163   }
   1164   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1165               "Handling purse `%s' deletion\n",
   1166               TALER_B2S (purse_pub));
   1167   if (GNUNET_OK !=
   1168       TALER_wallet_purse_delete_verify (purse_pub,
   1169                                         purse_sig))
   1170   {
   1171     enum GNUNET_DB_QueryStatus qs;
   1172 
   1173     qs = report_row_inconsistency (
   1174       "purse-delete",
   1175       deletion_serial_id,
   1176       "purse deletion signature invalid");
   1177     if (qs < 0)
   1178     {
   1179       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1180       pc->qs = qs;
   1181       return GNUNET_SYSERR;
   1182     }
   1183   }
   1184   else
   1185   {
   1186     if ( (internal_checks) &&
   1187          (! ps->purse_deleted) )
   1188     {
   1189       enum GNUNET_DB_QueryStatus qs;
   1190 
   1191       qs = report_row_inconsistency (
   1192         "purse-delete",
   1193         deletion_serial_id,
   1194         "purse not marked as deleted (internal check)");
   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   }
   1203   return GNUNET_OK;
   1204 }
   1205 
   1206 
   1207 /**
   1208  * Function called on expired purses.
   1209  *
   1210  * @param pc closure
   1211  * @param purse_pub public key of the purse
   1212  * @param balance amount of money in the purse
   1213  * @param expiration_date when did the purse expire?
   1214  * @return #GNUNET_OK to continue to iterate
   1215  */
   1216 static enum GNUNET_GenericReturnValue
   1217 handle_purse_expired (
   1218   struct PurseContext *pc,
   1219   const struct TALER_PurseContractPublicKeyP *purse_pub,
   1220   const struct TALER_Amount *balance,
   1221   struct GNUNET_TIME_Timestamp expiration_date)
   1222 {
   1223   enum GNUNET_DB_QueryStatus qs;
   1224   struct TALER_AUDITORDB_PurseNotClosedInconsistencies pnci = {
   1225     .amount = *balance,
   1226     .expiration_date = expiration_date.abs_time,
   1227     .purse_pub = purse_pub->eddsa_pub
   1228   };
   1229 
   1230   qs = TALER_AUDITORDB_insert_purse_not_closed_inconsistencies (
   1231     TALER_ARL_adb,
   1232     &pnci);
   1233   if (qs < 0)
   1234   {
   1235     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1236     pc->qs = qs;
   1237     return GNUNET_SYSERR;
   1238   }
   1239   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1240               "Handling purse expiration `%s'\n",
   1241               TALER_B2S (purse_pub));
   1242   TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_total_delayed_decisions),
   1243                         &TALER_ARL_USE_AB (purse_total_delayed_decisions),
   1244                         balance);
   1245   return GNUNET_OK;
   1246 }
   1247 
   1248 
   1249 /**
   1250  * Check that the purse summary matches what the exchange database
   1251  * thinks about the purse, and update our own state of the purse.
   1252  *
   1253  * Remove all purses that we are happy with from the DB.
   1254  *
   1255  * @param cls our `struct PurseContext`
   1256  * @param key hash of the purse public key
   1257  * @param value a `struct PurseSummary`
   1258  * @return #GNUNET_OK to process more entries
   1259  */
   1260 static enum GNUNET_GenericReturnValue
   1261 verify_purse_balance (void *cls,
   1262                       const struct GNUNET_HashCode *key,
   1263                       void *value)
   1264 {
   1265   struct PurseContext *pc = cls;
   1266   struct PurseSummary *ps = value;
   1267   enum GNUNET_DB_QueryStatus qs;
   1268 
   1269   if (internal_checks)
   1270   {
   1271     struct TALER_Amount pf;
   1272     struct TALER_Amount balance_without_purse_fee;
   1273 
   1274     /* subtract purse fee from ps->balance to get actual balance we expect, as
   1275        we track the balance including purse fee, while the exchange subtracts
   1276        the purse fee early on. */
   1277     qs = get_purse_fee (ps->creation_date,
   1278                         &pf);
   1279     if (qs < 0)
   1280     {
   1281       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1282       pc->qs = qs;
   1283       return GNUNET_SYSERR;
   1284     }
   1285     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
   1286       return GNUNET_OK; /* error already reported */
   1287     if (0 >
   1288         TALER_amount_subtract (&balance_without_purse_fee,
   1289                                &ps->balance,
   1290                                &pf))
   1291     {
   1292       qs = report_row_inconsistency ("purse",
   1293                                      0,
   1294                                      "purse fee higher than balance");
   1295       if (qs < 0)
   1296       {
   1297         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1298         pc->qs = qs;
   1299         return GNUNET_SYSERR;
   1300       }
   1301       GNUNET_assert (GNUNET_OK ==
   1302                      TALER_amount_set_zero (TALER_ARL_currency,
   1303                                             &balance_without_purse_fee));
   1304     }
   1305 
   1306     if (0 != TALER_amount_cmp (&ps->exchange_balance,
   1307                                &balance_without_purse_fee))
   1308     {
   1309       qs = report_amount_arithmetic_inconsistency ("purse-balance",
   1310                                                    0,
   1311                                                    &ps->exchange_balance,
   1312                                                    &balance_without_purse_fee,
   1313                                                    0);
   1314       if (qs < 0)
   1315       {
   1316         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1317         pc->qs = qs;
   1318         return GNUNET_SYSERR;
   1319       }
   1320     }
   1321   }
   1322 
   1323   if (ps->had_pi)
   1324   {
   1325     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1326                 "Updating purse `%s'\n",
   1327                 TALER_B2S (&ps->purse_pub));
   1328     qs = TALER_AUDITORDB_update_purse_info (TALER_ARL_adb,
   1329                                             &ps->purse_pub,
   1330                                             &ps->balance);
   1331   }
   1332   else
   1333   {
   1334     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1335                 "Inserting purse `%s'\n",
   1336                 TALER_B2S (&ps->purse_pub));
   1337     qs = TALER_AUDITORDB_insert_purse_info (TALER_ARL_adb,
   1338                                             &ps->purse_pub,
   1339                                             &ps->balance,
   1340                                             &ps->total_value,
   1341                                             ps->expiration_date);
   1342     ps->had_pi = true;
   1343   }
   1344   if (qs < 0)
   1345   {
   1346     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1347     pc->qs = qs;
   1348     return GNUNET_SYSERR;
   1349   }
   1350   GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
   1351   return GNUNET_OK;
   1352 }
   1353 
   1354 
   1355 /**
   1356  * Clear memory from the purses hash map.
   1357  *
   1358  * @param cls our `struct PurseContext`
   1359  * @param key hash of the purse public key
   1360  * @param value a `struct PurseSummary`
   1361  * @return #GNUNET_OK to process more entries
   1362  */
   1363 static enum GNUNET_GenericReturnValue
   1364 release_purse_balance (void *cls,
   1365                        const struct GNUNET_HashCode *key,
   1366                        void *value)
   1367 {
   1368   struct PurseContext *pc = cls;
   1369   struct PurseSummary *ps = value;
   1370 
   1371   GNUNET_assert (GNUNET_YES ==
   1372                  GNUNET_CONTAINER_multihashmap_remove (pc->purses,
   1373                                                        key,
   1374                                                        ps));
   1375   GNUNET_free (ps);
   1376   return GNUNET_OK;
   1377 }
   1378 
   1379 
   1380 /**
   1381  * Analyze purses for being well-formed.
   1382  *
   1383  * @param cls NULL
   1384  * @return transaction status code
   1385  */
   1386 static enum GNUNET_DB_QueryStatus
   1387 analyze_purses (void *cls)
   1388 {
   1389   struct PurseContext pc;
   1390   enum GNUNET_DB_QueryStatus qs;
   1391 
   1392   (void) cls;
   1393   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1394               "Analyzing purses\n");
   1395   qs = TALER_AUDITORDB_get_auditor_progress (
   1396     TALER_ARL_adb,
   1397     TALER_ARL_GET_PP (purse_account_merge_serial_id),
   1398     TALER_ARL_GET_PP (purse_decision_serial_id),
   1399     TALER_ARL_GET_PP (purse_deletion_serial_id),
   1400     TALER_ARL_GET_PP (purse_deposits_serial_id),
   1401     TALER_ARL_GET_PP (purse_merges_serial_id),
   1402     TALER_ARL_GET_PP (purse_request_serial_id),
   1403     TALER_ARL_GET_PP (purse_open_counter),
   1404     NULL);
   1405   if (0 > qs)
   1406   {
   1407     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1408     return qs;
   1409   }
   1410   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1411               "Resuming purse audit at %llu/%llu/%llu/%llu/%llu/%llu/%llu\n",
   1412               (unsigned long long) TALER_ARL_USE_PP (
   1413                 purse_open_counter),
   1414               (unsigned long long) TALER_ARL_USE_PP (
   1415                 purse_request_serial_id),
   1416               (unsigned long long) TALER_ARL_USE_PP (
   1417                 purse_decision_serial_id),
   1418               (unsigned long long) TALER_ARL_USE_PP (
   1419                 purse_deletion_serial_id),
   1420               (unsigned long long) TALER_ARL_USE_PP (
   1421                 purse_merges_serial_id),
   1422               (unsigned long long) TALER_ARL_USE_PP (
   1423                 purse_deposits_serial_id),
   1424               (unsigned long long) TALER_ARL_USE_PP (
   1425                 purse_account_merge_serial_id));
   1426   pc.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
   1427   qs = TALER_AUDITORDB_get_balance (
   1428     TALER_ARL_adb,
   1429     TALER_ARL_GET_AB (purse_global_balance),
   1430     TALER_ARL_GET_AB (purse_total_balance_insufficient_loss),
   1431     TALER_ARL_GET_AB (purse_total_delayed_decisions),
   1432     TALER_ARL_GET_AB (purse_total_balance_purse_not_closed),
   1433     TALER_ARL_GET_AB (purse_total_arithmetic_delta_plus),
   1434     TALER_ARL_GET_AB (purse_total_arithmetic_delta_minus),
   1435     TALER_ARL_GET_AB (purse_total_bad_sig_loss),
   1436     NULL);
   1437   if (qs < 0)
   1438   {
   1439     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1440     return qs;
   1441   }
   1442   pc.purses = GNUNET_CONTAINER_multihashmap_create (512,
   1443                                                     GNUNET_NO);
   1444 
   1445   qs = TALER_EXCHANGEDB_iterate_purse_requests_above_serial_id (
   1446     TALER_ARL_edb,
   1447     TALER_ARL_USE_PP (purse_request_serial_id),
   1448     &handle_purse_requested,
   1449     &pc);
   1450   if (qs < 0)
   1451   {
   1452     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1453     return qs;
   1454   }
   1455   if (pc.qs < 0)
   1456   {
   1457     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
   1458     return pc.qs;
   1459   }
   1460   qs = TALER_EXCHANGEDB_iterate_purse_merges_above_serial_id (
   1461     TALER_ARL_edb,
   1462     TALER_ARL_USE_PP (purse_merges_serial_id),
   1463     &handle_purse_merged,
   1464     &pc);
   1465   if (qs < 0)
   1466   {
   1467     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1468     return qs;
   1469   }
   1470   if (pc.qs < 0)
   1471   {
   1472     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
   1473     return pc.qs;
   1474   }
   1475 
   1476   qs = TALER_EXCHANGEDB_iterate_purse_deposits_above_serial_id (
   1477     TALER_ARL_edb,
   1478     TALER_ARL_USE_PP (purse_deposits_serial_id),
   1479     &handle_purse_deposits,
   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   /* Charge purse fee! */
   1493   qs = TALER_EXCHANGEDB_iterate_account_merges_above_serial_id (
   1494     TALER_ARL_edb,
   1495     TALER_ARL_USE_PP (purse_account_merge_serial_id),
   1496     &handle_account_merged,
   1497     &pc);
   1498   if (qs < 0)
   1499   {
   1500     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1501     return qs;
   1502   }
   1503   if (pc.qs < 0)
   1504   {
   1505     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
   1506     return pc.qs;
   1507   }
   1508 
   1509   qs = TALER_EXCHANGEDB_iterate_all_purse_decisions_above_serial_id (
   1510     TALER_ARL_edb,
   1511     TALER_ARL_USE_PP (purse_decision_serial_id),
   1512     &handle_purse_decision,
   1513     &pc);
   1514   if (qs < 0)
   1515   {
   1516     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1517     return qs;
   1518   }
   1519   if (pc.qs < 0)
   1520   {
   1521     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
   1522     return pc.qs;
   1523   }
   1524 
   1525   qs = TALER_EXCHANGEDB_iterate_all_purse_deletions_above_serial_id (
   1526     TALER_ARL_edb,
   1527     TALER_ARL_USE_PP (purse_deletion_serial_id),
   1528     &handle_purse_deletion,
   1529     &pc);
   1530   if (qs < 0)
   1531   {
   1532     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1533     return qs;
   1534   }
   1535   if (pc.qs < 0)
   1536   {
   1537     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
   1538     return pc.qs;
   1539   }
   1540 
   1541   qs = TALER_AUDITORDB_iterate_purse_expired (
   1542     TALER_ARL_adb,
   1543     &handle_purse_expired,
   1544     &pc);
   1545   if (qs < 0)
   1546   {
   1547     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1548     return qs;
   1549   }
   1550   if (pc.qs < 0)
   1551   {
   1552     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
   1553     return pc.qs;
   1554   }
   1555 
   1556   GNUNET_CONTAINER_multihashmap_iterate (pc.purses,
   1557                                          &verify_purse_balance,
   1558                                          &pc);
   1559   GNUNET_CONTAINER_multihashmap_iterate (pc.purses,
   1560                                          &release_purse_balance,
   1561                                          &pc);
   1562   GNUNET_break (0 ==
   1563                 GNUNET_CONTAINER_multihashmap_size (pc.purses));
   1564   GNUNET_CONTAINER_multihashmap_destroy (pc.purses);
   1565   if (pc.qs < 0)
   1566   {
   1567     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs);
   1568     return pc.qs;
   1569   }
   1570   qs = TALER_AUDITORDB_insert_balance (
   1571     TALER_ARL_adb,
   1572     TALER_ARL_SET_AB (purse_global_balance),
   1573     TALER_ARL_SET_AB (purse_total_balance_insufficient_loss),
   1574     TALER_ARL_SET_AB (purse_total_delayed_decisions),
   1575     TALER_ARL_SET_AB (purse_total_balance_purse_not_closed),
   1576     TALER_ARL_SET_AB (purse_total_arithmetic_delta_plus),
   1577     TALER_ARL_SET_AB (purse_total_arithmetic_delta_minus),
   1578     TALER_ARL_SET_AB (purse_total_bad_sig_loss),
   1579     NULL);
   1580   if (0 > qs)
   1581   {
   1582     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1583                 "Failed to update auditor DB, not recording progress\n");
   1584     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1585     return qs;
   1586   }
   1587   qs = TALER_AUDITORDB_insert_auditor_progress (
   1588     TALER_ARL_adb,
   1589     TALER_ARL_SET_PP (purse_account_merge_serial_id),
   1590     TALER_ARL_SET_PP (purse_decision_serial_id),
   1591     TALER_ARL_SET_PP (purse_deletion_serial_id),
   1592     TALER_ARL_SET_PP (purse_deposits_serial_id),
   1593     TALER_ARL_SET_PP (purse_merges_serial_id),
   1594     TALER_ARL_SET_PP (purse_request_serial_id),
   1595     TALER_ARL_SET_PP (purse_open_counter),
   1596     NULL);
   1597   if (0 > qs)
   1598   {
   1599     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1600                 "Failed to update auditor DB, not recording progress\n");
   1601     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1602     return qs;
   1603   }
   1604   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1605               "Concluded purse audit step at %llu/%llu/%llu/%llu/%llu/%llu\n",
   1606               (unsigned long long) TALER_ARL_USE_PP (
   1607                 purse_request_serial_id),
   1608               (unsigned long long) TALER_ARL_USE_PP (
   1609                 purse_decision_serial_id),
   1610               (unsigned long long) TALER_ARL_USE_PP (
   1611                 purse_deletion_serial_id),
   1612               (unsigned long long) TALER_ARL_USE_PP (
   1613                 purse_merges_serial_id),
   1614               (unsigned long long) TALER_ARL_USE_PP (
   1615                 purse_deposits_serial_id),
   1616               (unsigned long long) TALER_ARL_USE_PP (
   1617                 purse_account_merge_serial_id));
   1618   return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
   1619 }
   1620 
   1621 
   1622 /**
   1623  * Function called on events received from Postgres.
   1624  *
   1625  * @param cls closure, NULL
   1626  * @param extra additional event data provided
   1627  * @param extra_size number of bytes in @a extra
   1628  */
   1629 static void
   1630 db_notify (void *cls,
   1631            const void *extra,
   1632            size_t extra_size)
   1633 {
   1634   (void) cls;
   1635   (void) extra;
   1636   (void) extra_size;
   1637 
   1638   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1639               "Received notification to wake purses\n");
   1640   if (GNUNET_OK !=
   1641       TALER_ARL_setup_sessions_and_run (&analyze_purses,
   1642                                         NULL))
   1643   {
   1644     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1645                 "Audit failed\n");
   1646     GNUNET_SCHEDULER_shutdown ();
   1647     global_ret = EXIT_FAILURE;
   1648     return;
   1649   }
   1650 }
   1651 
   1652 
   1653 /**
   1654  * Function called on shutdown.
   1655  */
   1656 static void
   1657 do_shutdown (void *cls)
   1658 {
   1659   (void) cls;
   1660 
   1661   if (NULL != eh)
   1662   {
   1663     TALER_AUDITORDB_event_listen_cancel (eh);
   1664     eh = NULL;
   1665   }
   1666   TALER_ARL_done ();
   1667 }
   1668 
   1669 
   1670 /**
   1671  * Main function that will be run.
   1672  *
   1673  * @param cls closure
   1674  * @param args remaining command-line arguments
   1675  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
   1676  * @param c configuration
   1677  */
   1678 static void
   1679 run (void *cls,
   1680      char *const *args,
   1681      const char *cfgfile,
   1682      const struct GNUNET_CONFIGURATION_Handle *c)
   1683 {
   1684   (void) cls;
   1685   (void) args;
   1686   (void) cfgfile;
   1687 
   1688   cfg = c;
   1689   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
   1690                                  NULL);
   1691   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1692               "Launching purses auditor\n");
   1693   if (GNUNET_OK !=
   1694       TALER_ARL_init (c))
   1695   {
   1696     global_ret = EXIT_FAILURE;
   1697     return;
   1698   }
   1699   if (test_mode != 1)
   1700   {
   1701     struct GNUNET_DB_EventHeaderP es = {
   1702       .size = htons (sizeof (es)),
   1703       .type = htons (TALER_DBEVENT_EXCHANGE_AUDITOR_WAKE_HELPER_PURSES)
   1704     };
   1705 
   1706     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1707                 "Running helper indefinitely\n");
   1708     eh = TALER_AUDITORDB_event_listen (TALER_ARL_adb,
   1709                                        &es,
   1710                                        GNUNET_TIME_UNIT_FOREVER_REL,
   1711                                        &db_notify,
   1712                                        NULL);
   1713   }
   1714   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1715               "Starting audit\n");
   1716   if (GNUNET_OK !=
   1717       TALER_ARL_setup_sessions_and_run (&analyze_purses,
   1718                                         NULL))
   1719   {
   1720     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1721                 "Audit failed\n");
   1722     GNUNET_SCHEDULER_shutdown ();
   1723     global_ret = EXIT_FAILURE;
   1724     return;
   1725   }
   1726 }
   1727 
   1728 
   1729 /**
   1730  * The main function to check the database's handling of purses.
   1731  *
   1732  * @param argc number of arguments from the command line
   1733  * @param argv command line arguments
   1734  * @return 0 ok, 1 on error
   1735  */
   1736 int
   1737 main (int argc,
   1738       char *const *argv)
   1739 {
   1740   const struct GNUNET_GETOPT_CommandLineOption options[] = {
   1741     GNUNET_GETOPT_option_flag ('i',
   1742                                "internal",
   1743                                "perform checks only applicable for exchange-internal audits",
   1744                                &internal_checks),
   1745     GNUNET_GETOPT_option_flag ('t',
   1746                                "test",
   1747                                "run in test mode and exit when idle",
   1748                                &test_mode),
   1749     GNUNET_GETOPT_option_timetravel ('T',
   1750                                      "timetravel"),
   1751     GNUNET_GETOPT_OPTION_END
   1752   };
   1753   enum GNUNET_GenericReturnValue ret;
   1754 
   1755   ret = GNUNET_PROGRAM_run (
   1756     TALER_AUDITOR_project_data (),
   1757     argc,
   1758     argv,
   1759     "taler-helper-auditor-purses",
   1760     gettext_noop ("Audit Taler exchange purse handling"),
   1761     options,
   1762     &run,
   1763     NULL);
   1764   if (GNUNET_SYSERR == ret)
   1765     return EXIT_INVALIDARGUMENT;
   1766   if (GNUNET_NO == ret)
   1767     return EXIT_SUCCESS;
   1768   return global_ret;
   1769 }
   1770 
   1771 
   1772 /* end of taler-helper-auditor-purses.c */