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


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