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


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