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-aggregation.c (55250B)


      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-aggregation.c
     18  * @brief audits an exchange's aggregations.
     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 "taler/taler_dbevents.h"
     28 #include "report-lib.h"
     29 #include "auditor-database/event_listen.h"
     30 #include "auditor-database/get_auditor_progress.h"
     31 #include "auditor-database/get_balance.h"
     32 #include "auditor-database/insert_amount_arithmetic_inconsistency.h"
     33 #include "auditor-database/insert_auditor_progress.h"
     34 #include "auditor-database/insert_bad_sig_losses.h"
     35 #include "auditor-database/insert_balance.h"
     36 #include "auditor-database/insert_coin_inconsistency.h"
     37 #include "auditor-database/insert_fee_time_inconsistency.h"
     38 #include "auditor-database/insert_row_inconsistency.h"
     39 #include "auditor-database/insert_wire_out_inconsistency.h"
     40 #include "auditor-database/update_auditor_progress.h"
     41 #include "auditor-database/update_balance.h"
     42 #include "exchange-database/get_coin_transactions.h"
     43 #include "exchange-database/get_known_coin.h"
     44 #include "exchange-database/get_wire_fee.h"
     45 #include "exchange-database/lookup_wire_transfer.h"
     46 #include "exchange-database/select_wire_out_above_serial_id.h"
     47 
     48 /**
     49  * Return value from main().
     50  */
     51 static int global_ret;
     52 
     53 /**
     54  * Run in test mode. Exit when idle instead of
     55  * going to sleep and waiting for more work.
     56  */
     57 static int test_mode;
     58 
     59 /**
     60  * Checkpointing our progress for aggregations.
     61  */
     62 static TALER_ARL_DEF_PP (aggregation_last_wire_out_serial_id);
     63 
     64 /**
     65  * Total aggregation fees (wire fees) earned.
     66  */
     67 static TALER_ARL_DEF_AB (aggregation_total_wire_fee_revenue);
     68 
     69 /**
     70  * Total delta between calculated and stored wire out transfers,
     71  * for positive deltas.
     72  */
     73 static TALER_ARL_DEF_AB (aggregation_total_wire_out_delta_plus);
     74 
     75 /**
     76  * Total delta between calculated and stored wire out transfers
     77  * for negative deltas.
     78  */
     79 static TALER_ARL_DEF_AB (aggregation_total_wire_out_delta_minus);
     80 
     81 /**
     82  * Profits the exchange made by bad amount calculations on coins.
     83  */
     84 static TALER_ARL_DEF_AB (aggregation_total_coin_delta_plus);
     85 
     86 /**
     87  * Losses the exchange made by bad amount calculations on coins.
     88  */
     89 static TALER_ARL_DEF_AB (aggregation_total_coin_delta_minus);
     90 
     91 /**
     92  * Profits the exchange made by bad amount calculations.
     93  */
     94 static TALER_ARL_DEF_AB (aggregation_total_arithmetic_delta_plus);
     95 
     96 /**
     97  * Losses the exchange made by bad amount calculations.
     98  */
     99 static TALER_ARL_DEF_AB (aggregation_total_arithmetic_delta_minus);
    100 
    101 /**
    102  * Total amount lost by operations for which signatures were invalid.
    103  */
    104 static TALER_ARL_DEF_AB (aggregation_total_bad_sig_loss);
    105 
    106 /**
    107  * Should we run checks that only work for exchange-internal audits?
    108  */
    109 static int internal_checks;
    110 
    111 static struct GNUNET_DB_EventHandler *eh;
    112 
    113 /**
    114  * The auditors's configuration.
    115  */
    116 static const struct GNUNET_CONFIGURATION_Handle *cfg;
    117 
    118 /**
    119  * Report a (serious) inconsistency in the exchange's database with
    120  * respect to calculations involving amounts.
    121  *
    122  * @param operation what operation had the inconsistency
    123  * @param rowid affected row, 0 if row is missing
    124  * @param exchange amount calculated by exchange
    125  * @param auditor amount calculated by auditor
    126  * @param profitable 1 if @a exchange being larger than @a auditor is
    127  *           profitable for the exchange for this operation,
    128  *           -1 if @a exchange being smaller than @a auditor is
    129  *           profitable for the exchange, and 0 if it is unclear
    130  * @return transaction status
    131  */
    132 static enum GNUNET_DB_QueryStatus
    133 report_amount_arithmetic_inconsistency (
    134   const char *operation,
    135   uint64_t rowid,
    136   const struct TALER_Amount *exchange,
    137   const struct TALER_Amount *auditor,
    138   int profitable)
    139 {
    140   struct TALER_Amount delta;
    141   struct TALER_Amount *target;
    142 
    143   if (0 < TALER_amount_cmp (exchange,
    144                             auditor))
    145   {
    146     /* exchange > auditor */
    147     TALER_ARL_amount_subtract (&delta,
    148                                exchange,
    149                                auditor);
    150   }
    151   else
    152   {
    153     /* auditor < exchange */
    154     profitable = -profitable;
    155     TALER_ARL_amount_subtract (&delta,
    156                                auditor,
    157                                exchange);
    158   }
    159 
    160   {
    161     struct TALER_AUDITORDB_AmountArithmeticInconsistency aai = {
    162       .problem_row_id = rowid,
    163       .profitable = profitable,
    164       .operation = (char *) operation,
    165       .exchange_amount = *exchange,
    166       .auditor_amount = *auditor
    167     };
    168     enum GNUNET_DB_QueryStatus qs;
    169 
    170     qs = TALER_AUDITORDB_insert_amount_arithmetic_inconsistency (
    171       TALER_ARL_adb,
    172       &aai);
    173 
    174     if (qs < 0)
    175     {
    176       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    177       return qs;
    178     }
    179   }
    180   if (0 != profitable)
    181   {
    182     target = (1 == profitable)
    183       ? &TALER_ARL_USE_AB (aggregation_total_arithmetic_delta_plus)
    184       : &TALER_ARL_USE_AB (aggregation_total_arithmetic_delta_minus);
    185     TALER_ARL_amount_add (target,
    186                           target,
    187                           &delta);
    188   }
    189   return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
    190 }
    191 
    192 
    193 /**
    194  * Report a (serious) inconsistency in the exchange's database with
    195  * respect to calculations involving amounts of a coin.
    196  *
    197  * @param operation what operation had the inconsistency
    198  * @param coin_pub affected coin
    199  * @param exchange amount calculated by exchange
    200  * @param auditor amount calculated by auditor
    201  * @param profitable 1 if @a exchange being larger than @a auditor is
    202  *           profitable for the exchange for this operation,
    203  *           -1 if @a exchange being smaller than @a auditor is
    204  *           profitable for the exchange, and 0 if it is unclear
    205  * @return transaction status
    206  */
    207 static enum GNUNET_DB_QueryStatus
    208 report_coin_arithmetic_inconsistency (
    209   const char *operation,
    210   const struct TALER_CoinSpendPublicKeyP *coin_pub,
    211   const struct TALER_Amount *exchange,
    212   const struct TALER_Amount *auditor,
    213   int profitable)
    214 {
    215   struct TALER_Amount delta;
    216   struct TALER_Amount *target;
    217 
    218   if (0 < TALER_amount_cmp (exchange,
    219                             auditor))
    220   {
    221     /* exchange > auditor */
    222     TALER_ARL_amount_subtract (&delta,
    223                                exchange,
    224                                auditor);
    225   }
    226   else
    227   {
    228     /* auditor < exchange */
    229     profitable = -profitable;
    230     TALER_ARL_amount_subtract (&delta,
    231                                auditor,
    232                                exchange);
    233   }
    234 
    235   {
    236     enum GNUNET_DB_QueryStatus qs;
    237     struct TALER_AUDITORDB_CoinInconsistency ci = {
    238       .operation = (char *) operation,
    239       .auditor_amount = *auditor,
    240       .exchange_amount = *exchange,
    241       .profitable = profitable,
    242       .coin_pub = coin_pub->eddsa_pub
    243     };
    244 
    245     qs = TALER_AUDITORDB_insert_coin_inconsistency (
    246       TALER_ARL_adb,
    247       &ci);
    248 
    249     if (qs < 0)
    250     {
    251       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    252       return qs;
    253     }
    254   }
    255   if (0 != profitable)
    256   {
    257     target = (1 == profitable)
    258       ? &TALER_ARL_USE_AB (aggregation_total_coin_delta_plus)
    259       : &TALER_ARL_USE_AB (aggregation_total_coin_delta_minus);
    260     TALER_ARL_amount_add (target,
    261                           target,
    262                           &delta);
    263   }
    264   return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
    265 }
    266 
    267 
    268 /**
    269  * Report a (serious) inconsistency in the exchange's database.
    270  *
    271  * @param table affected table
    272  * @param rowid affected row, 0 if row is missing
    273  * @param diagnostic message explaining the problem
    274  * @return transaction status
    275  */
    276 static enum GNUNET_DB_QueryStatus
    277 report_row_inconsistency (const char *table,
    278                           uint64_t rowid,
    279                           const char *diagnostic)
    280 {
    281   enum GNUNET_DB_QueryStatus qs;
    282   struct TALER_AUDITORDB_RowInconsistency ri = {
    283     .diagnostic = (char *) diagnostic,
    284     .row_table = (char *) table,
    285     .row_id = rowid
    286   };
    287 
    288   qs = TALER_AUDITORDB_insert_row_inconsistency (
    289     TALER_ARL_adb,
    290     &ri);
    291 
    292   if (qs < 0)
    293   {
    294     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    295     return qs;
    296   }
    297   return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
    298 }
    299 
    300 
    301 /* *********************** Analyze aggregations ******************** */
    302 /* This logic checks that the aggregator did the right thing
    303    paying each merchant what they were due (and on time). */
    304 
    305 
    306 /**
    307  * Information about wire fees charged by the exchange.
    308  */
    309 struct WireFeeInfo
    310 {
    311 
    312   /**
    313    * Kept in a DLL.
    314    */
    315   struct WireFeeInfo *next;
    316 
    317   /**
    318    * Kept in a DLL.
    319    */
    320   struct WireFeeInfo *prev;
    321 
    322   /**
    323    * When does the fee go into effect (inclusive).
    324    */
    325   struct GNUNET_TIME_Timestamp start_date;
    326 
    327   /**
    328    * When does the fee stop being in effect (exclusive).
    329    */
    330   struct GNUNET_TIME_Timestamp end_date;
    331 
    332   /**
    333    * How high are the wire fees.
    334    */
    335   struct TALER_WireFeeSet fees;
    336 
    337 };
    338 
    339 
    340 /**
    341  * Closure for callbacks during #analyze_merchants().
    342  */
    343 struct AggregationContext
    344 {
    345 
    346   /**
    347    * DLL of wire fees charged by the exchange.
    348    */
    349   struct WireFeeInfo *fee_head;
    350 
    351   /**
    352    * DLL of wire fees charged by the exchange.
    353    */
    354   struct WireFeeInfo *fee_tail;
    355 
    356   /**
    357    * Final result status.
    358    */
    359   enum GNUNET_DB_QueryStatus qs;
    360 };
    361 
    362 
    363 /**
    364  * Closure for #wire_transfer_information_cb.
    365  */
    366 struct WireCheckContext
    367 {
    368 
    369   /**
    370    * Corresponding merchant context.
    371    */
    372   struct AggregationContext *ac;
    373 
    374   /**
    375    * Total deposits claimed by all transactions that were aggregated
    376    * under the given @e wtid.
    377    */
    378   struct TALER_Amount total_deposits;
    379 
    380   /**
    381    * Target account details of the receiver.
    382    */
    383   struct TALER_FullPayto payto_uri;
    384 
    385   /**
    386    * Execution time of the wire transfer.
    387    */
    388   struct GNUNET_TIME_Timestamp date;
    389 
    390   /**
    391    * Database transaction status.
    392    */
    393   enum GNUNET_DB_QueryStatus qs;
    394 
    395 };
    396 
    397 
    398 /**
    399  * Check coin's transaction history for plausibility.  Does NOT check
    400  * the signatures (those are checked independently), but does calculate
    401  * the amounts for the aggregation table and checks that the total
    402  * claimed coin value is within the value of the coin's denomination.
    403  *
    404  * @param coin_pub public key of the coin (for reporting)
    405  * @param h_contract_terms hash of the proposal for which we calculate the amount
    406  * @param merchant_pub public key of the merchant (who is allowed to issue refunds)
    407  * @param issue denomination information about the coin
    408  * @param tl_head head of transaction history to verify
    409  * @param[out] merchant_gain amount the coin contributes to the wire transfer to the merchant
    410  * @param[out] deposit_gain amount the coin contributes excluding refunds
    411  * @return database transaction status
    412  */
    413 static enum GNUNET_DB_QueryStatus
    414 check_transaction_history_for_deposit (
    415   const struct TALER_CoinSpendPublicKeyP *coin_pub,
    416   const struct TALER_PrivateContractHashP *h_contract_terms,
    417   const struct TALER_MerchantPublicKeyP *merchant_pub,
    418   const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue,
    419   const struct TALER_EXCHANGEDB_TransactionList *tl_head,
    420   struct TALER_Amount *merchant_gain,
    421   struct TALER_Amount *deposit_gain)
    422 {
    423   struct TALER_Amount expenditures;
    424   struct TALER_Amount refunds;
    425   struct TALER_Amount spent;
    426   struct TALER_Amount *deposited = NULL;
    427   struct TALER_Amount merchant_loss;
    428   const struct TALER_Amount *deposit_fee;
    429   enum GNUNET_DB_QueryStatus qs;
    430 
    431   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    432               "Checking transaction history of coin %s\n",
    433               TALER_B2S (coin_pub));
    434   GNUNET_assert (GNUNET_OK ==
    435                  TALER_amount_set_zero (TALER_ARL_currency,
    436                                         &expenditures));
    437   GNUNET_assert (GNUNET_OK ==
    438                  TALER_amount_set_zero (TALER_ARL_currency,
    439                                         &refunds));
    440   GNUNET_assert (GNUNET_OK ==
    441                  TALER_amount_set_zero (TALER_ARL_currency,
    442                                         merchant_gain));
    443   GNUNET_assert (GNUNET_OK ==
    444                  TALER_amount_set_zero (TALER_ARL_currency,
    445                                         &merchant_loss));
    446   /* Go over transaction history to compute totals; note that we do not bother
    447      to reconstruct the order of the events, so instead of subtracting we
    448      compute positive (deposit, melt) and negative (refund) values separately
    449      here, and then subtract the negative from the positive at the end (after
    450      the loops). */
    451   deposit_fee = NULL;
    452   for (const struct TALER_EXCHANGEDB_TransactionList *tl = tl_head;
    453        NULL != tl;
    454        tl = tl->next)
    455   {
    456     const struct TALER_Amount *fee_claimed;
    457 
    458     switch (tl->type)
    459     {
    460     case TALER_EXCHANGEDB_TT_DEPOSIT:
    461       /* check wire and h_wire are consistent */
    462       if (NULL != deposited)
    463       {
    464         struct TALER_AUDITORDB_RowInconsistency ri = {
    465           .row_id = tl->serial_id,
    466           .diagnostic = (char *)
    467                         "multiple deposits of the same coin into the same contract detected",
    468           .row_table = (char *) "deposits"
    469         };
    470 
    471         qs = TALER_AUDITORDB_insert_row_inconsistency (
    472           TALER_ARL_adb,
    473           &ri);
    474 
    475         if (qs < 0)
    476         {
    477           GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    478           return qs;
    479         }
    480       }
    481       deposited = &tl->details.deposit->amount_with_fee;       /* according to exchange*/
    482       fee_claimed = &tl->details.deposit->deposit_fee;       /* Fee according to exchange DB */
    483       TALER_ARL_amount_add (&expenditures,
    484                             &expenditures,
    485                             deposited);
    486       /* Check if this deposit is within the remit of the aggregation
    487          we are investigating, if so, include it in the totals. */
    488       if ((0 == GNUNET_memcmp (merchant_pub,
    489                                &tl->details.deposit->merchant_pub)) &&
    490           (0 == GNUNET_memcmp (h_contract_terms,
    491                                &tl->details.deposit->h_contract_terms)))
    492       {
    493         struct TALER_Amount amount_without_fee;
    494 
    495         TALER_ARL_amount_subtract (&amount_without_fee,
    496                                    deposited,
    497                                    fee_claimed);
    498         TALER_ARL_amount_add (merchant_gain,
    499                               merchant_gain,
    500                               &amount_without_fee);
    501         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    502                     "Detected applicable deposit of %s\n",
    503                     TALER_amount2s (&amount_without_fee));
    504         deposit_fee = fee_claimed;       /* We had a deposit, remember the fee, we may need it */
    505       }
    506       /* Check that the fees given in the transaction list and in dki match */
    507       if (0 !=
    508           TALER_amount_cmp (&issue->fees.deposit,
    509                             fee_claimed))
    510       {
    511         /* Disagreement in fee structure between auditor and exchange DB! */
    512         qs = report_amount_arithmetic_inconsistency ("deposit fee",
    513                                                      0,
    514                                                      fee_claimed,
    515                                                      &issue->fees.deposit,
    516                                                      1);
    517         if (0 > qs)
    518           return qs;
    519       }
    520       break;
    521     case TALER_EXCHANGEDB_TT_MELT:
    522       {
    523         const struct TALER_Amount *amount_with_fee;
    524 
    525         amount_with_fee = &tl->details.melt->amount_with_fee;
    526         fee_claimed = &tl->details.melt->melt_fee;
    527         TALER_ARL_amount_add (&expenditures,
    528                               &expenditures,
    529                               amount_with_fee);
    530         /* Check that the fees given in the transaction list and in dki match */
    531         if (0 !=
    532             TALER_amount_cmp (&issue->fees.refresh,
    533                               fee_claimed))
    534         {
    535           /* Disagreement in fee structure between exchange and auditor */
    536           qs = report_amount_arithmetic_inconsistency ("melt fee",
    537                                                        0,
    538                                                        fee_claimed,
    539                                                        &issue->fees.refresh,
    540                                                        1);
    541           if (0 > qs)
    542             return qs;
    543         }
    544         break;
    545       }
    546     case TALER_EXCHANGEDB_TT_REFUND:
    547       {
    548         const struct TALER_Amount *amount_with_fee;
    549 
    550         amount_with_fee = &tl->details.refund->refund_amount;
    551         fee_claimed = &tl->details.refund->refund_fee;
    552         TALER_ARL_amount_add (&refunds,
    553                               &refunds,
    554                               amount_with_fee);
    555         TALER_ARL_amount_add (&expenditures,
    556                               &expenditures,
    557                               fee_claimed);
    558         /* Check if this refund is within the remit of the aggregation
    559            we are investigating, if so, include it in the totals. */
    560         if ((0 == GNUNET_memcmp (merchant_pub,
    561                                  &tl->details.refund->merchant_pub)) &&
    562             (0 == GNUNET_memcmp (h_contract_terms,
    563                                  &tl->details.refund->h_contract_terms)))
    564         {
    565           GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    566                       "Detected applicable refund of %s\n",
    567                       TALER_amount2s (amount_with_fee));
    568           TALER_ARL_amount_add (&merchant_loss,
    569                                 &merchant_loss,
    570                                 amount_with_fee);
    571         }
    572         /* Check that the fees given in the transaction list and in dki match */
    573         if (0 !=
    574             TALER_amount_cmp (&issue->fees.refund,
    575                               fee_claimed))
    576         {
    577           /* Disagreement in fee structure between exchange and auditor! */
    578           qs = report_amount_arithmetic_inconsistency ("refund fee",
    579                                                        0,
    580                                                        fee_claimed,
    581                                                        &issue->fees.refund,
    582                                                        1);
    583           if (0 > qs)
    584             return qs;
    585         }
    586         break;
    587       }
    588     case TALER_EXCHANGEDB_TT_RECOUP_REFRESH_RECEIVER:
    589       {
    590         const struct TALER_Amount *amount_with_fee;
    591 
    592         amount_with_fee = &tl->details.old_coin_recoup->value;
    593         /* We count recoups of refreshed coins like refunds for the dirty old
    594            coin, as they equivalently _increase_ the remaining value on the
    595            _old_ coin */
    596         TALER_ARL_amount_add (&refunds,
    597                               &refunds,
    598                               amount_with_fee);
    599         break;
    600       }
    601     case TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW:
    602       {
    603         const struct TALER_Amount *amount_with_fee;
    604 
    605         /* We count recoups of the coin as expenditures, as it
    606            equivalently decreases the remaining value of the recouped coin. */
    607         amount_with_fee = &tl->details.recoup->value;
    608         TALER_ARL_amount_add (&expenditures,
    609                               &expenditures,
    610                               amount_with_fee);
    611         break;
    612       }
    613     case TALER_EXCHANGEDB_TT_RECOUP_REFRESH:
    614       {
    615         const struct TALER_Amount *amount_with_fee;
    616 
    617         /* We count recoups of the coin as expenditures, as it
    618            equivalently decreases the remaining value of the recouped coin. */
    619         amount_with_fee = &tl->details.recoup_refresh->value;
    620         TALER_ARL_amount_add (&expenditures,
    621                               &expenditures,
    622                               amount_with_fee);
    623         break;
    624       }
    625     case TALER_EXCHANGEDB_TT_PURSE_DEPOSIT:
    626       {
    627         const struct TALER_Amount *amount_with_fee;
    628 
    629         amount_with_fee = &tl->details.purse_deposit->amount;
    630         if (! tl->details.purse_deposit->refunded)
    631           TALER_ARL_amount_add (&expenditures,
    632                                 &expenditures,
    633                                 amount_with_fee);
    634         break;
    635       }
    636 
    637     case TALER_EXCHANGEDB_TT_PURSE_REFUND:
    638       {
    639         const struct TALER_Amount *amount_with_fee;
    640 
    641         amount_with_fee = &tl->details.purse_refund->refund_amount;
    642         fee_claimed = &tl->details.purse_refund->refund_fee;
    643         TALER_ARL_amount_add (&refunds,
    644                               &refunds,
    645                               amount_with_fee);
    646         TALER_ARL_amount_add (&expenditures,
    647                               &expenditures,
    648                               fee_claimed);
    649         /* Check that the fees given in the transaction list and in dki match */
    650         if (0 !=
    651             TALER_amount_cmp (&issue->fees.refund,
    652                               fee_claimed))
    653         {
    654           /* Disagreement in fee structure between exchange and auditor! */
    655           qs = report_amount_arithmetic_inconsistency ("refund fee",
    656                                                        0,
    657                                                        fee_claimed,
    658                                                        &issue->fees.refund,
    659                                                        1);
    660           if (0 > qs)
    661             return qs;
    662         }
    663         break;
    664       }
    665 
    666     case TALER_EXCHANGEDB_TT_RESERVE_OPEN:
    667       {
    668         const struct TALER_Amount *amount_with_fee;
    669 
    670         amount_with_fee = &tl->details.reserve_open->coin_contribution;
    671         TALER_ARL_amount_add (&expenditures,
    672                               &expenditures,
    673                               amount_with_fee);
    674         break;
    675       }
    676     } /* switch (tl->type) */
    677   } /* for 'tl' */
    678 
    679   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    680               "Deposits for this aggregation (after fees) are %s\n",
    681               TALER_amount2s (merchant_gain));
    682   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    683               "Aggregation loss due to refunds is %s\n",
    684               TALER_amount2s (&merchant_loss));
    685   *deposit_gain = *merchant_gain;
    686   if ((NULL != deposited) &&
    687       (NULL != deposit_fee) &&
    688       (0 == TALER_amount_cmp (&refunds,
    689                               deposited)))
    690   {
    691     /* We had a /deposit operation AND /refund operations adding up to the
    692        total deposited value including deposit fee. Thus, we should not
    693        subtract the /deposit fee from the merchant gain (as it was also
    694        refunded). */
    695     TALER_ARL_amount_add (merchant_gain,
    696                           merchant_gain,
    697                           deposit_fee);
    698   }
    699   {
    700     struct TALER_Amount final_gain;
    701 
    702     if (TALER_ARL_SR_INVALID_NEGATIVE ==
    703         TALER_ARL_amount_subtract_neg (&final_gain,
    704                                        merchant_gain,
    705                                        &merchant_loss))
    706     {
    707       /* refunds above deposits? Bad! */
    708       qs = report_coin_arithmetic_inconsistency ("refund (merchant)",
    709                                                  coin_pub,
    710                                                  merchant_gain,
    711                                                  &merchant_loss,
    712                                                  1);
    713       if (0 > qs)
    714         return qs;
    715       /* For the overall aggregation, we should not count this
    716          as a NEGATIVE contribution as that is not allowed; so
    717          let's count it as zero as that's the best we can do. */
    718       GNUNET_assert (GNUNET_OK ==
    719                      TALER_amount_set_zero (TALER_ARL_currency,
    720                                             merchant_gain));
    721     }
    722     else
    723     {
    724       *merchant_gain = final_gain;
    725     }
    726   }
    727 
    728 
    729   /* Calculate total balance change, i.e. expenditures (recoup, deposit, refresh)
    730      minus refunds (refunds, recoup-to-old) */
    731   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    732               "Subtracting refunds of %s from coin value loss\n",
    733               TALER_amount2s (&refunds));
    734   if (TALER_ARL_SR_INVALID_NEGATIVE ==
    735       TALER_ARL_amount_subtract_neg (&spent,
    736                                      &expenditures,
    737                                      &refunds))
    738   {
    739     /* refunds above expenditures? Bad! */
    740     qs = report_coin_arithmetic_inconsistency ("refund (balance)",
    741                                                coin_pub,
    742                                                &expenditures,
    743                                                &refunds,
    744                                                1);
    745     if (0 > qs)
    746       return qs;
    747   }
    748   else
    749   {
    750     /* Now check that 'spent' is less or equal than the total coin value */
    751     if (1 == TALER_amount_cmp (&spent,
    752                                &issue->value))
    753     {
    754       /* spent > value */
    755       qs = report_coin_arithmetic_inconsistency ("spend",
    756                                                  coin_pub,
    757                                                  &spent,
    758                                                  &issue->value,
    759                                                  -1);
    760       if (0 > qs)
    761         return qs;
    762     }
    763   }
    764   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    765               "Final merchant gain after refunds is %s\n",
    766               TALER_amount2s (deposit_gain));
    767   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    768               "Coin %s contributes %s to contract %s\n",
    769               TALER_B2S (coin_pub),
    770               TALER_amount2s (merchant_gain),
    771               GNUNET_h2s (&h_contract_terms->hash));
    772   return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
    773 }
    774 
    775 
    776 /**
    777  * Function called with the results of the lookup of the
    778  * transaction data associated with a wire transfer identifier.
    779  *
    780  * @param[in,out] cls a `struct WireCheckContext`
    781  * @param rowid which row in the table is the information from (for diagnostics)
    782  * @param merchant_pub public key of the merchant (should be same for all callbacks with the same @e cls)
    783  * @param account_pay_uri where did we transfer the funds?
    784  * @param h_payto hash over @a account_payto_uri as it is in the DB
    785  * @param exec_time execution time of the wire transfer (should be same for all callbacks with the same @e cls)
    786  * @param h_contract_terms which proposal was this payment about
    787  * @param denom_pub denomination of @a coin_pub
    788  * @param coin_pub which public key was this payment about
    789  * @param coin_value amount contributed by this coin in total (with fee),
    790  *                   but excluding refunds by this coin
    791  * @param deposit_fee applicable deposit fee for this coin, actual
    792  *        fees charged may differ if coin was refunded
    793  */
    794 static void
    795 wire_transfer_information_cb (
    796   void *cls,
    797   uint64_t rowid,
    798   const struct TALER_MerchantPublicKeyP *merchant_pub,
    799   const struct TALER_FullPayto account_pay_uri,
    800   const struct TALER_FullPaytoHashP *h_payto,
    801   struct GNUNET_TIME_Timestamp exec_time,
    802   const struct TALER_PrivateContractHashP *h_contract_terms,
    803   const struct TALER_DenominationPublicKey *denom_pub,
    804   const struct TALER_CoinSpendPublicKeyP *coin_pub,
    805   const struct TALER_Amount *coin_value,
    806   const struct TALER_Amount *deposit_fee)
    807 {
    808   struct WireCheckContext *wcc = cls;
    809   const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue;
    810   struct TALER_Amount computed_value;
    811   struct TALER_Amount total_deposit_without_refunds;
    812   struct TALER_EXCHANGEDB_TransactionList *tl;
    813   struct TALER_CoinPublicInfo coin;
    814   enum GNUNET_DB_QueryStatus qs;
    815   struct TALER_FullPaytoHashP hpt;
    816   uint64_t etag_out;
    817 
    818   if (0 > wcc->qs)
    819     return;
    820   TALER_full_payto_hash (account_pay_uri,
    821                          &hpt);
    822   if (0 !=
    823       GNUNET_memcmp (&hpt,
    824                      h_payto))
    825   {
    826     qs = report_row_inconsistency ("wire_targets",
    827                                    rowid,
    828                                    "h-payto does not match payto URI");
    829     if (0 > qs)
    830     {
    831       wcc->qs = qs;
    832       return;
    833     }
    834   }
    835   /* Obtain coin's transaction history */
    836   /* FIXME-Optimization: could use 'start' mechanism to only fetch
    837      transactions we did not yet process, instead of going over them again and
    838      again.*/
    839 
    840   {
    841     struct TALER_Amount balance;
    842     struct TALER_DenominationHashP h_denom_pub;
    843 
    844     qs = TALER_EXCHANGEDB_get_coin_transactions (TALER_ARL_edb,
    845                                                  false,
    846                                                  coin_pub,
    847                                                  0,
    848                                                  0,
    849                                                  &etag_out,
    850                                                  &balance,
    851                                                  &h_denom_pub,
    852                                                  &tl);
    853   }
    854   if (0 > qs)
    855   {
    856     wcc->qs = qs;
    857     TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    858     return;
    859   }
    860   if (NULL == tl)
    861   {
    862     qs = report_row_inconsistency ("aggregation",
    863                                    rowid,
    864                                    "no transaction history for coin claimed in aggregation");
    865     if (0 > qs)
    866       wcc->qs = qs;
    867     return;
    868   }
    869   qs = TALER_EXCHANGEDB_get_known_coin (TALER_ARL_edb,
    870                                         coin_pub,
    871                                         &coin);
    872   if (0 > qs)
    873   {
    874     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    875     wcc->qs = qs;
    876     return;
    877   }
    878   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    879   {
    880     /* this should be a foreign key violation at this point! */
    881     qs = report_row_inconsistency ("aggregation",
    882                                    rowid,
    883                                    "could not get coin details for coin claimed in aggregation");
    884     if (0 > qs)
    885       wcc->qs = qs;
    886     TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    887     return;
    888   }
    889   qs = TALER_ARL_get_denomination_info_by_hash (&coin.denom_pub_hash,
    890                                                 &issue);
    891   if (0 > qs)
    892   {
    893     wcc->qs = qs;
    894     TALER_denom_sig_free (&coin.denom_sig);
    895     TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    896     return;
    897   }
    898   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    899   {
    900     TALER_denom_sig_free (&coin.denom_sig);
    901     TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    902     qs = report_row_inconsistency ("aggregation",
    903                                    rowid,
    904                                    "could not find denomination key for coin claimed in aggregation");
    905     if (0 > qs)
    906       wcc->qs = qs;
    907     return;
    908   }
    909   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    910               "Testing coin `%s' for validity\n",
    911               TALER_B2S (&coin.coin_pub));
    912   if (GNUNET_OK !=
    913       TALER_test_coin_valid (&coin,
    914                              denom_pub))
    915   {
    916     struct TALER_AUDITORDB_BadSigLosses bsl = {
    917       .problem_row_id = rowid,
    918       .operation = (char *) "wire",
    919       .loss = *coin_value,
    920       .operation_specific_pub = coin.coin_pub.eddsa_pub
    921     };
    922 
    923     qs = TALER_AUDITORDB_insert_bad_sig_losses (
    924       TALER_ARL_adb,
    925       &bsl);
    926     if (qs < 0)
    927     {
    928       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    929       wcc->qs = qs;
    930       TALER_denom_sig_free (&coin.denom_sig);
    931       TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    932       return;
    933     }
    934     TALER_ARL_amount_add (&TALER_ARL_USE_AB (aggregation_total_bad_sig_loss),
    935                           &TALER_ARL_USE_AB (aggregation_total_bad_sig_loss),
    936                           coin_value);
    937     qs = report_row_inconsistency ("deposit",
    938                                    rowid,
    939                                    "coin denomination signature invalid");
    940     if (0 > qs)
    941     {
    942       wcc->qs = qs;
    943       TALER_denom_sig_free (&coin.denom_sig);
    944       TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    945       return;
    946     }
    947   }
    948   TALER_denom_sig_free (&coin.denom_sig);
    949   GNUNET_assert (NULL != issue); /* mostly to help static analysis */
    950   /* Check transaction history to see if it supports aggregate
    951      valuation */
    952   qs = check_transaction_history_for_deposit (
    953     coin_pub,
    954     h_contract_terms,
    955     merchant_pub,
    956     issue,
    957     tl,
    958     &computed_value,
    959     &total_deposit_without_refunds);
    960   if (0 > qs)
    961   {
    962     TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    963     wcc->qs = qs;
    964     return;
    965   }
    966   TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    967   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    968               "Coin contributes %s to aggregate (deposits after fees and refunds)\n",
    969               TALER_amount2s (&computed_value));
    970   {
    971     struct TALER_Amount coin_value_without_fee;
    972 
    973     if (TALER_ARL_SR_INVALID_NEGATIVE ==
    974         TALER_ARL_amount_subtract_neg (&coin_value_without_fee,
    975                                        coin_value,
    976                                        deposit_fee))
    977     {
    978       qs = report_amount_arithmetic_inconsistency (
    979         "aggregation (fee structure)",
    980         rowid,
    981         coin_value,
    982         deposit_fee,
    983         -1);
    984       if (0 > qs)
    985       {
    986         wcc->qs = qs;
    987         return;
    988       }
    989     }
    990     if (0 !=
    991         TALER_amount_cmp (&total_deposit_without_refunds,
    992                           &coin_value_without_fee))
    993     {
    994       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    995                   "Expected coin contribution of %s to aggregate\n",
    996                   TALER_amount2s (&coin_value_without_fee));
    997       qs = report_amount_arithmetic_inconsistency (
    998         "aggregation (contribution)",
    999         rowid,
   1000         &coin_value_without_fee,
   1001         &total_deposit_without_refunds,
   1002         -1);
   1003       if (0 > qs)
   1004       {
   1005         wcc->qs = qs;
   1006         return;
   1007       }
   1008     }
   1009   }
   1010   /* Check other details of wire transfer match */
   1011   if (0 != TALER_full_payto_cmp (account_pay_uri,
   1012                                  wcc->payto_uri))
   1013   {
   1014     qs = report_row_inconsistency ("aggregation",
   1015                                    rowid,
   1016                                    "target of outgoing wire transfer do not match hash of wire from deposit");
   1017     if (0 > qs)
   1018     {
   1019       wcc->qs = qs;
   1020       return;
   1021     }
   1022   }
   1023   if (GNUNET_TIME_timestamp_cmp (exec_time,
   1024                                  !=,
   1025                                  wcc->date))
   1026   {
   1027     /* This should be impossible from database constraints */
   1028     GNUNET_break (0);
   1029     qs = report_row_inconsistency ("aggregation",
   1030                                    rowid,
   1031                                    "date given in aggregate does not match wire transfer date");
   1032     if (0 > qs)
   1033     {
   1034       wcc->qs = qs;
   1035       return;
   1036     }
   1037   }
   1038 
   1039   /* Add coin's contribution to total aggregate value */
   1040   {
   1041     struct TALER_Amount res;
   1042 
   1043     TALER_ARL_amount_add (&res,
   1044                           &wcc->total_deposits,
   1045                           &computed_value);
   1046     wcc->total_deposits = res;
   1047   }
   1048 }
   1049 
   1050 
   1051 /**
   1052  * Lookup the wire fee that the exchange charges at @a timestamp.
   1053  *
   1054  * @param ac context for caching the result
   1055  * @param method method of the wire plugin
   1056  * @param timestamp time for which we need the fee
   1057  * @return NULL on error (fee unknown)
   1058  */
   1059 static const struct TALER_Amount *
   1060 get_wire_fee (struct AggregationContext *ac,
   1061               const char *method,
   1062               struct GNUNET_TIME_Timestamp timestamp)
   1063 {
   1064   struct WireFeeInfo *wfi;
   1065   struct WireFeeInfo *pos;
   1066   struct TALER_MasterSignatureP master_sig;
   1067   enum GNUNET_DB_QueryStatus qs;
   1068   uint64_t rowid;
   1069 
   1070   /* Check if fee is already loaded in cache */
   1071   for (pos = ac->fee_head; NULL != pos; pos = pos->next)
   1072   {
   1073     if (GNUNET_TIME_timestamp_cmp (pos->start_date,
   1074                                    <=,
   1075                                    timestamp) &&
   1076         GNUNET_TIME_timestamp_cmp (pos->end_date,
   1077                                    >,
   1078                                    timestamp))
   1079       return &pos->fees.wire;
   1080     if (GNUNET_TIME_timestamp_cmp (pos->start_date,
   1081                                    >,
   1082                                    timestamp))
   1083       break;
   1084   }
   1085 
   1086   /* Lookup fee in exchange database */
   1087   wfi = GNUNET_new (struct WireFeeInfo);
   1088   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
   1089       TALER_EXCHANGEDB_get_wire_fee (TALER_ARL_edb,
   1090                                      method,
   1091                                      timestamp,
   1092                                      &rowid,
   1093                                      &wfi->start_date,
   1094                                      &wfi->end_date,
   1095                                      &wfi->fees,
   1096                                      &master_sig))
   1097   {
   1098     GNUNET_break (0);
   1099     GNUNET_free (wfi);
   1100     return NULL;
   1101   }
   1102 
   1103   /* Check signature. (This is not terribly meaningful as the exchange can
   1104      easily make this one up, but it means that we have proof that the master
   1105      key was used for inconsistent wire fees if a merchant complains.) */
   1106   if (GNUNET_OK !=
   1107       TALER_exchange_offline_wire_fee_verify (
   1108         method,
   1109         wfi->start_date,
   1110         wfi->end_date,
   1111         &wfi->fees,
   1112         &TALER_ARL_master_pub,
   1113         &master_sig))
   1114   {
   1115     ac->qs = report_row_inconsistency ("wire-fee",
   1116                                        timestamp.abs_time.abs_value_us,
   1117                                        "wire fee signature invalid at given time");
   1118     /* Note: continue with the fee despite the signature
   1119        being invalid here; hopefully it is really only the
   1120        signature that is bad ... */
   1121   }
   1122 
   1123   /* Established fee, keep in sorted list */
   1124   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1125               "Wire fee is %s starting at %s\n",
   1126               TALER_amount2s (&wfi->fees.wire),
   1127               GNUNET_TIME_timestamp2s (wfi->start_date));
   1128   if ((NULL == pos) ||
   1129       (NULL == pos->prev))
   1130     GNUNET_CONTAINER_DLL_insert (ac->fee_head,
   1131                                  ac->fee_tail,
   1132                                  wfi);
   1133   else
   1134     GNUNET_CONTAINER_DLL_insert_after (ac->fee_head,
   1135                                        ac->fee_tail,
   1136                                        pos->prev,
   1137                                        wfi);
   1138   /* Check non-overlaping fee invariant */
   1139   if ((NULL != wfi->prev) &&
   1140       GNUNET_TIME_timestamp_cmp (wfi->prev->end_date,
   1141                                  >,
   1142                                  wfi->start_date))
   1143   {
   1144     struct TALER_AUDITORDB_FeeTimeInconsistency ftib = {
   1145       .problem_row_id = rowid,
   1146       .diagnostic = (char *) "start date before previous end date",
   1147       .time = wfi->start_date.abs_time,
   1148       .type = (char *) method
   1149     };
   1150 
   1151     qs = TALER_AUDITORDB_insert_fee_time_inconsistency (
   1152       TALER_ARL_adb,
   1153       &ftib);
   1154     if (qs < 0)
   1155     {
   1156       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1157       ac->qs = qs;
   1158       return NULL;
   1159     }
   1160   }
   1161   if ((NULL != wfi->next) &&
   1162       GNUNET_TIME_timestamp_cmp (wfi->next->start_date,
   1163                                  >=,
   1164                                  wfi->end_date))
   1165   {
   1166     struct TALER_AUDITORDB_FeeTimeInconsistency ftia = {
   1167       .problem_row_id = rowid,
   1168       .diagnostic = (char *) "end date date after next start date",
   1169       .time = wfi->end_date.abs_time,
   1170       .type = (char *) method
   1171     };
   1172 
   1173     qs = TALER_AUDITORDB_insert_fee_time_inconsistency (
   1174       TALER_ARL_adb,
   1175       &ftia);
   1176 
   1177     if (qs < 0)
   1178     {
   1179       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1180       ac->qs = qs;
   1181       return NULL;
   1182     }
   1183   }
   1184   return &wfi->fees.wire;
   1185 }
   1186 
   1187 
   1188 /**
   1189  * Check that a wire transfer made by the exchange is valid
   1190  * (has matching deposits).
   1191  *
   1192  * @param cls a `struct AggregationContext`
   1193  * @param rowid identifier of the respective row in the database
   1194  * @param date timestamp of the wire transfer (roughly)
   1195  * @param wtid wire transfer subject
   1196  * @param payto_uri bank account details of the receiver
   1197  * @param amount amount that was wired
   1198  * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop iteration
   1199  */
   1200 static enum GNUNET_GenericReturnValue
   1201 check_wire_out_cb (void *cls,
   1202                    uint64_t rowid,
   1203                    struct GNUNET_TIME_Timestamp date,
   1204                    const struct TALER_WireTransferIdentifierRawP *wtid,
   1205                    const struct TALER_FullPayto payto_uri,
   1206                    const struct TALER_Amount *amount)
   1207 {
   1208   struct AggregationContext *ac = cls;
   1209   struct WireCheckContext wcc;
   1210   struct TALER_Amount final_amount;
   1211   struct TALER_Amount exchange_gain;
   1212   enum GNUNET_DB_QueryStatus qs;
   1213   char *method;
   1214 
   1215   /* should be monotonically increasing */
   1216   GNUNET_assert (rowid >=
   1217                  TALER_ARL_USE_PP (aggregation_last_wire_out_serial_id));
   1218   TALER_ARL_USE_PP (aggregation_last_wire_out_serial_id) = rowid + 1;
   1219 
   1220   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1221               "Checking wire transfer %s over %s performed on %s\n",
   1222               TALER_B2S (wtid),
   1223               TALER_amount2s (amount),
   1224               GNUNET_TIME_timestamp2s (date));
   1225   if (NULL == (method = TALER_payto_get_method (payto_uri.full_payto)))
   1226   {
   1227     qs = report_row_inconsistency ("wire_out",
   1228                                    rowid,
   1229                                    "specified wire address lacks method");
   1230     if (0 > qs)
   1231       ac->qs = qs;
   1232     return GNUNET_OK;
   1233   }
   1234 
   1235   wcc.ac = ac;
   1236   wcc.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
   1237   wcc.date = date;
   1238   GNUNET_assert (GNUNET_OK ==
   1239                  TALER_amount_set_zero (amount->currency,
   1240                                         &wcc.total_deposits));
   1241   wcc.payto_uri = payto_uri;
   1242   qs = TALER_EXCHANGEDB_lookup_wire_transfer (TALER_ARL_edb,
   1243                                               wtid,
   1244                                               &wire_transfer_information_cb,
   1245                                               &wcc);
   1246   if (0 > qs)
   1247   {
   1248     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1249     ac->qs = qs;
   1250     GNUNET_free (method);
   1251     return GNUNET_SYSERR;
   1252   }
   1253   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != wcc.qs)
   1254   {
   1255     /* Note: detailed information was already logged
   1256        in #wire_transfer_information_cb, so here we
   1257        only log for debugging */
   1258     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1259                 "Inconsistency for wire_out %llu (WTID %s) detected\n",
   1260                 (unsigned long long) rowid,
   1261                 TALER_B2S (wtid));
   1262   }
   1263 
   1264 
   1265   /* Subtract aggregation fee from total (if possible) */
   1266   {
   1267     const struct TALER_Amount *wire_fee;
   1268 
   1269     wire_fee = get_wire_fee (ac,
   1270                              method,
   1271                              date);
   1272     if (0 > ac->qs)
   1273     {
   1274       GNUNET_free (method);
   1275       return GNUNET_SYSERR;
   1276     }
   1277     if (NULL == wire_fee)
   1278     {
   1279       qs = report_row_inconsistency ("wire-fee",
   1280                                      date.abs_time.abs_value_us,
   1281                                      "wire fee unavailable for given time");
   1282       if (qs < 0)
   1283       {
   1284         ac->qs = qs;
   1285         GNUNET_free (method);
   1286         return GNUNET_SYSERR;
   1287       }
   1288       /* If fee is unknown, we just assume the fee is zero */
   1289       final_amount = wcc.total_deposits;
   1290     }
   1291     else if (TALER_ARL_SR_INVALID_NEGATIVE ==
   1292              TALER_ARL_amount_subtract_neg (&final_amount,
   1293                                             &wcc.total_deposits,
   1294                                             wire_fee))
   1295     {
   1296       qs = report_amount_arithmetic_inconsistency (
   1297         "wire out (fee structure)",
   1298         rowid,
   1299         &wcc.total_deposits,
   1300         wire_fee,
   1301         -1);
   1302       /* If fee arithmetic fails, we just assume the fee is zero */
   1303       if (0 > qs)
   1304       {
   1305         ac->qs = qs;
   1306         GNUNET_free (method);
   1307         return GNUNET_SYSERR;
   1308       }
   1309       final_amount = wcc.total_deposits;
   1310     }
   1311   }
   1312   GNUNET_free (method);
   1313 
   1314   /* Round down to amount supported by wire method */
   1315   GNUNET_break (GNUNET_SYSERR !=
   1316                 TALER_amount_round_down (&final_amount,
   1317                                          &TALER_ARL_currency_round_unit));
   1318 
   1319   /* Calculate the exchange's gain as the fees plus rounding differences! */
   1320   TALER_ARL_amount_subtract (&exchange_gain,
   1321                              &wcc.total_deposits,
   1322                              &final_amount);
   1323 
   1324   /* Sum up aggregation fees (we simply include the rounding gains) */
   1325   TALER_ARL_amount_add (&TALER_ARL_USE_AB (aggregation_total_wire_fee_revenue),
   1326                         &TALER_ARL_USE_AB (aggregation_total_wire_fee_revenue),
   1327                         &exchange_gain);
   1328 
   1329   /* Check that calculated amount matches actual amount */
   1330   if (0 != TALER_amount_cmp (amount,
   1331                              &final_amount))
   1332   {
   1333     struct TALER_Amount delta;
   1334 
   1335     if (0 < TALER_amount_cmp (amount,
   1336                               &final_amount))
   1337     {
   1338       /* amount > final_amount */
   1339       TALER_ARL_amount_subtract (&delta,
   1340                                  amount,
   1341                                  &final_amount);
   1342       TALER_ARL_amount_add (&TALER_ARL_USE_AB (
   1343                               aggregation_total_wire_out_delta_plus),
   1344                             &TALER_ARL_USE_AB (
   1345                               aggregation_total_wire_out_delta_plus),
   1346                             &delta);
   1347     }
   1348     else
   1349     {
   1350       /* amount < final_amount */
   1351       TALER_ARL_amount_subtract (&delta,
   1352                                  &final_amount,
   1353                                  amount);
   1354       TALER_ARL_amount_add (&TALER_ARL_USE_AB (
   1355                               aggregation_total_wire_out_delta_minus),
   1356                             &TALER_ARL_USE_AB (
   1357                               aggregation_total_wire_out_delta_minus),
   1358                             &delta);
   1359     }
   1360 
   1361     {
   1362       struct TALER_AUDITORDB_WireOutInconsistency woi = {
   1363         .destination_account = payto_uri,
   1364         .diagnostic = (char *) "aggregated amount does not match expectations",
   1365         .wire_out_row_id = rowid,
   1366         .expected = final_amount,
   1367         .claimed = *amount
   1368       };
   1369 
   1370       qs = TALER_AUDITORDB_insert_wire_out_inconsistency (
   1371         TALER_ARL_adb,
   1372         &woi);
   1373 
   1374       if (qs < 0)
   1375       {
   1376         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1377         ac->qs = qs;
   1378         return GNUNET_SYSERR;
   1379       }
   1380     }
   1381     return GNUNET_OK;
   1382   }
   1383   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1384               "Aggregation unit %s is OK\n",
   1385               TALER_B2S (wtid));
   1386   return GNUNET_OK;
   1387 }
   1388 
   1389 
   1390 /**
   1391  * Analyze the exchange aggregator's payment processing.
   1392  *
   1393  * @param cls closure
   1394  * @return transaction status code
   1395  */
   1396 static enum GNUNET_DB_QueryStatus
   1397 analyze_aggregations (void *cls)
   1398 {
   1399   struct AggregationContext ac;
   1400   struct WireFeeInfo *wfi;
   1401   enum GNUNET_DB_QueryStatus qs;
   1402 
   1403   (void) cls;
   1404   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1405               "Analyzing aggregations\n");
   1406   qs = TALER_AUDITORDB_get_auditor_progress (
   1407     TALER_ARL_adb,
   1408     TALER_ARL_GET_PP (aggregation_last_wire_out_serial_id),
   1409     NULL);
   1410   if (0 > qs)
   1411   {
   1412     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1413     return qs;
   1414   }
   1415   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
   1416   {
   1417     GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
   1418                 "First analysis using this auditor, starting audit from scratch\n");
   1419   }
   1420   else
   1421   {
   1422     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1423                 "Resuming aggregation audit at %llu\n",
   1424                 (unsigned long long) TALER_ARL_USE_PP (
   1425                   aggregation_last_wire_out_serial_id));
   1426   }
   1427 
   1428   memset (&ac,
   1429           0,
   1430           sizeof (ac));
   1431   qs = TALER_AUDITORDB_get_balance (
   1432     TALER_ARL_adb,
   1433     TALER_ARL_GET_AB (aggregation_total_wire_fee_revenue),
   1434     TALER_ARL_GET_AB (aggregation_total_arithmetic_delta_plus),
   1435     TALER_ARL_GET_AB (aggregation_total_arithmetic_delta_minus),
   1436     TALER_ARL_GET_AB (aggregation_total_bad_sig_loss),
   1437     TALER_ARL_GET_AB (aggregation_total_wire_out_delta_plus),
   1438     TALER_ARL_GET_AB (aggregation_total_wire_out_delta_minus),
   1439     TALER_ARL_GET_AB (aggregation_total_coin_delta_plus),
   1440     TALER_ARL_GET_AB (aggregation_total_coin_delta_minus),
   1441     NULL);
   1442   if (0 > qs)
   1443   {
   1444     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1445     return qs;
   1446   }
   1447 
   1448   ac.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
   1449   qs = TALER_EXCHANGEDB_select_wire_out_above_serial_id (
   1450     TALER_ARL_edb,
   1451     TALER_ARL_USE_PP (aggregation_last_wire_out_serial_id),
   1452     &check_wire_out_cb,
   1453     &ac);
   1454   if (0 > qs)
   1455   {
   1456     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1457     ac.qs = qs;
   1458   }
   1459   while (NULL != (wfi = ac.fee_head))
   1460   {
   1461     GNUNET_CONTAINER_DLL_remove (ac.fee_head,
   1462                                  ac.fee_tail,
   1463                                  wfi);
   1464     GNUNET_free (wfi);
   1465   }
   1466   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
   1467   {
   1468     /* there were no wire out entries to be looked at, we are done */
   1469     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1470                 "No wire out entries found\n");
   1471     return qs;
   1472   }
   1473   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != ac.qs)
   1474   {
   1475     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == ac.qs);
   1476     return ac.qs;
   1477   }
   1478   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1479               "Finished aggregation audit at %llu\n",
   1480               (unsigned long long) TALER_ARL_USE_PP (
   1481                 aggregation_last_wire_out_serial_id));
   1482   qs = TALER_AUDITORDB_insert_balance (
   1483     TALER_ARL_adb,
   1484     TALER_ARL_SET_AB (aggregation_total_wire_fee_revenue),
   1485     TALER_ARL_SET_AB (aggregation_total_arithmetic_delta_plus),
   1486     TALER_ARL_SET_AB (aggregation_total_arithmetic_delta_minus),
   1487     TALER_ARL_SET_AB (aggregation_total_bad_sig_loss),
   1488     TALER_ARL_SET_AB (aggregation_total_wire_out_delta_plus),
   1489     TALER_ARL_SET_AB (aggregation_total_wire_out_delta_minus),
   1490     TALER_ARL_SET_AB (aggregation_total_coin_delta_plus),
   1491     TALER_ARL_SET_AB (aggregation_total_coin_delta_minus),
   1492     NULL);
   1493   if (0 > qs)
   1494   {
   1495     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1496                 "Failed to update auditor DB, not recording progress\n");
   1497     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1498     return qs;
   1499   }
   1500   qs = TALER_AUDITORDB_update_balance (
   1501     TALER_ARL_adb,
   1502     TALER_ARL_SET_AB (aggregation_total_wire_fee_revenue),
   1503     TALER_ARL_SET_AB (aggregation_total_arithmetic_delta_plus),
   1504     TALER_ARL_SET_AB (aggregation_total_arithmetic_delta_minus),
   1505     TALER_ARL_SET_AB (aggregation_total_bad_sig_loss),
   1506     TALER_ARL_SET_AB (aggregation_total_wire_out_delta_plus),
   1507     TALER_ARL_SET_AB (aggregation_total_wire_out_delta_minus),
   1508     TALER_ARL_SET_AB (aggregation_total_coin_delta_plus),
   1509     TALER_ARL_SET_AB (aggregation_total_coin_delta_minus),
   1510     NULL);
   1511   if (0 > qs)
   1512   {
   1513     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1514                 "Failed to update auditor DB, not recording progress\n");
   1515     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1516     return qs;
   1517   }
   1518 
   1519   qs = TALER_AUDITORDB_insert_auditor_progress (
   1520     TALER_ARL_adb,
   1521     TALER_ARL_SET_PP (aggregation_last_wire_out_serial_id),
   1522     NULL);
   1523   if (0 > qs)
   1524   {
   1525     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1526                 "Failed to update auditor DB, not recording progress\n");
   1527     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1528     return qs;
   1529   }
   1530   qs = TALER_AUDITORDB_update_auditor_progress (
   1531     TALER_ARL_adb,
   1532     TALER_ARL_SET_PP (aggregation_last_wire_out_serial_id),
   1533     NULL);
   1534   if (0 > qs)
   1535   {
   1536     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1537                 "Failed to update auditor DB, not recording progress\n");
   1538     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1539     return qs;
   1540   }
   1541   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1542               "Concluded aggregation audit step at %llu\n",
   1543               (unsigned long long) TALER_ARL_USE_PP (
   1544                 aggregation_last_wire_out_serial_id));
   1545 
   1546   return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
   1547 }
   1548 
   1549 
   1550 /**
   1551  * Function called on events received from Postgres.
   1552  *
   1553  * @param cls closure, NULL
   1554  * @param extra additional event data provided
   1555  * @param extra_size number of bytes in @a extra
   1556  */
   1557 static void
   1558 db_notify (void *cls,
   1559            const void *extra,
   1560            size_t extra_size)
   1561 {
   1562   (void) cls;
   1563   (void) extra;
   1564   (void) extra_size;
   1565   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1566               "Received notification to wake aggregation helper\n");
   1567   if (GNUNET_OK !=
   1568       TALER_ARL_setup_sessions_and_run (&analyze_aggregations,
   1569                                         NULL))
   1570   {
   1571     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1572                 "Audit failed\n");
   1573     GNUNET_SCHEDULER_shutdown ();
   1574     global_ret = EXIT_FAILURE;
   1575     return;
   1576   }
   1577 }
   1578 
   1579 
   1580 /**
   1581  * Function called on shutdown.
   1582  */
   1583 static void
   1584 do_shutdown (void *cls)
   1585 {
   1586   (void) cls;
   1587   if (NULL != eh)
   1588   {
   1589     TALER_AUDITORDB_event_listen_cancel (eh);
   1590     eh = NULL;
   1591   }
   1592   TALER_ARL_done ();
   1593 }
   1594 
   1595 
   1596 /**
   1597  * Main function that will be run.
   1598  *
   1599  * @param cls closure
   1600  * @param args remaining command-line arguments
   1601  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
   1602  * @param c configuration
   1603  */
   1604 static void
   1605 run (void *cls,
   1606      char *const *args,
   1607      const char *cfgfile,
   1608      const struct GNUNET_CONFIGURATION_Handle *c)
   1609 {
   1610   (void) cls;
   1611   (void) args;
   1612   (void) cfgfile;
   1613 
   1614   cfg = c;
   1615   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
   1616                                  NULL);
   1617   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1618               "Launching aggregation auditor\n");
   1619   if (GNUNET_OK !=
   1620       TALER_ARL_init (c))
   1621   {
   1622     global_ret = EXIT_FAILURE;
   1623     return;
   1624   }
   1625 
   1626   if (test_mode != 1)
   1627   {
   1628     struct GNUNET_DB_EventHeaderP es = {
   1629       .size = htons (sizeof (es)),
   1630       .type = htons (TALER_DBEVENT_EXCHANGE_AUDITOR_WAKE_HELPER_AGGREGATION)
   1631     };
   1632 
   1633     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1634                 "Running helper indefinitely\n");
   1635     eh = TALER_AUDITORDB_event_listen (TALER_ARL_adb,
   1636                                        &es,
   1637                                        GNUNET_TIME_UNIT_FOREVER_REL,
   1638                                        &db_notify,
   1639                                        NULL);
   1640   }
   1641   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1642               "Starting audit\n");
   1643   if (GNUNET_OK !=
   1644       TALER_ARL_setup_sessions_and_run (&analyze_aggregations,
   1645                                         NULL))
   1646   {
   1647     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1648                 "Audit failed\n");
   1649     GNUNET_SCHEDULER_shutdown ();
   1650     global_ret = EXIT_FAILURE;
   1651     return;
   1652   }
   1653 }
   1654 
   1655 
   1656 /**
   1657  * The main function to audit the exchange's aggregation processing.
   1658  *
   1659  * @param argc number of arguments from the command line
   1660  * @param argv command line arguments
   1661  * @return 0 ok, 1 on error
   1662  */
   1663 int
   1664 main (int argc,
   1665       char *const *argv)
   1666 {
   1667   const struct GNUNET_GETOPT_CommandLineOption options[] = {
   1668     GNUNET_GETOPT_option_flag ('i',
   1669                                "internal",
   1670                                "perform checks only applicable for exchange-internal audits",
   1671                                &internal_checks),
   1672     GNUNET_GETOPT_option_flag ('t',
   1673                                "test",
   1674                                "run in test mode and exit when idle",
   1675                                &test_mode),
   1676     GNUNET_GETOPT_option_timetravel ('T',
   1677                                      "timetravel"),
   1678     GNUNET_GETOPT_OPTION_END
   1679   };
   1680   enum GNUNET_GenericReturnValue ret;
   1681 
   1682   ret = GNUNET_PROGRAM_run (
   1683     TALER_AUDITOR_project_data (),
   1684     argc,
   1685     argv,
   1686     "taler-helper-auditor-aggregation",
   1687     gettext_noop ("Audit Taler exchange aggregation activity"),
   1688     options,
   1689     &run,
   1690     NULL);
   1691   if (GNUNET_SYSERR == ret)
   1692     return EXIT_INVALIDARGUMENT;
   1693   if (GNUNET_NO == ret)
   1694     return EXIT_SUCCESS;
   1695   return global_ret;
   1696 }
   1697 
   1698 
   1699 /* end of taler-helper-auditor-aggregation.c */