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


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