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


      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     .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   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       /* check wire and h_wire are consistent */
    467       if (NULL != deposited)
    468       {
    469         struct TALER_AUDITORDB_RowInconsistency ri = {
    470           .row_id = tl->serial_id,
    471           .diagnostic = (char *)
    472                         "multiple deposits of the same coin into the same contract detected",
    473           .row_table = (char *) "deposits"
    474         };
    475 
    476         qs = TALER_AUDITORDB_insert_row_inconsistency (
    477           TALER_ARL_adb,
    478           &ri);
    479 
    480         if (qs < 0)
    481         {
    482           GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    483           return qs;
    484         }
    485       }
    486       deposited = &tl->details.deposit->amount_with_fee;       /* according to exchange*/
    487       fee_claimed = &tl->details.deposit->deposit_fee;       /* Fee according to exchange DB */
    488       TALER_ARL_amount_add (&expenditures,
    489                             &expenditures,
    490                             deposited);
    491       /* Check if this deposit is within the remit of the aggregation
    492          we are investigating, if so, include it in the totals. */
    493       if ((0 == GNUNET_memcmp (merchant_pub,
    494                                &tl->details.deposit->merchant_pub)) &&
    495           (0 == GNUNET_memcmp (h_contract_terms,
    496                                &tl->details.deposit->h_contract_terms)))
    497       {
    498         struct TALER_Amount amount_without_fee;
    499 
    500         TALER_ARL_amount_subtract (&amount_without_fee,
    501                                    deposited,
    502                                    fee_claimed);
    503         TALER_ARL_amount_add (merchant_gain,
    504                               merchant_gain,
    505                               &amount_without_fee);
    506         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    507                     "Detected applicable deposit of %s\n",
    508                     TALER_amount2s (&amount_without_fee));
    509         deposit_fee = fee_claimed;       /* We had a deposit, remember the fee, we may need it */
    510       }
    511       /* Check that the fees given in the transaction list and in dki match */
    512       if (0 !=
    513           TALER_amount_cmp (&issue->fees.deposit,
    514                             fee_claimed))
    515       {
    516         /* Disagreement in fee structure between auditor and exchange DB! */
    517         qs = report_amount_arithmetic_inconsistency ("deposit fee",
    518                                                      0,
    519                                                      fee_claimed,
    520                                                      &issue->fees.deposit,
    521                                                      1);
    522         if (0 > qs)
    523           return qs;
    524       }
    525       break;
    526     case TALER_EXCHANGEDB_TT_MELT:
    527       {
    528         const struct TALER_Amount *amount_with_fee;
    529 
    530         amount_with_fee = &tl->details.melt->amount_with_fee;
    531         fee_claimed = &tl->details.melt->melt_fee;
    532         TALER_ARL_amount_add (&expenditures,
    533                               &expenditures,
    534                               amount_with_fee);
    535         /* Check that the fees given in the transaction list and in dki match */
    536         if (0 !=
    537             TALER_amount_cmp (&issue->fees.refresh,
    538                               fee_claimed))
    539         {
    540           /* Disagreement in fee structure between exchange and auditor */
    541           qs = report_amount_arithmetic_inconsistency ("melt fee",
    542                                                        0,
    543                                                        fee_claimed,
    544                                                        &issue->fees.refresh,
    545                                                        1);
    546           if (0 > qs)
    547             return qs;
    548         }
    549         break;
    550       }
    551     case TALER_EXCHANGEDB_TT_REFUND:
    552       {
    553         const struct TALER_Amount *amount_with_fee;
    554 
    555         amount_with_fee = &tl->details.refund->refund_amount;
    556         fee_claimed = &tl->details.refund->refund_fee;
    557         TALER_ARL_amount_add (&refunds,
    558                               &refunds,
    559                               amount_with_fee);
    560         TALER_ARL_amount_add (&expenditures,
    561                               &expenditures,
    562                               fee_claimed);
    563         /* Check if this refund is within the remit of the aggregation
    564            we are investigating, if so, include it in the totals. */
    565         if ((0 == GNUNET_memcmp (merchant_pub,
    566                                  &tl->details.refund->merchant_pub)) &&
    567             (0 == GNUNET_memcmp (h_contract_terms,
    568                                  &tl->details.refund->h_contract_terms)))
    569         {
    570           GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    571                       "Detected applicable refund of %s\n",
    572                       TALER_amount2s (amount_with_fee));
    573           TALER_ARL_amount_add (&merchant_loss,
    574                                 &merchant_loss,
    575                                 amount_with_fee);
    576         }
    577         /* Check that the fees given in the transaction list and in dki match */
    578         if (0 !=
    579             TALER_amount_cmp (&issue->fees.refund,
    580                               fee_claimed))
    581         {
    582           /* Disagreement in fee structure between exchange and auditor! */
    583           qs = report_amount_arithmetic_inconsistency ("refund fee",
    584                                                        0,
    585                                                        fee_claimed,
    586                                                        &issue->fees.refund,
    587                                                        1);
    588           if (0 > qs)
    589             return qs;
    590         }
    591         break;
    592       }
    593     case TALER_EXCHANGEDB_TT_RECOUP_REFRESH_RECEIVER:
    594       {
    595         const struct TALER_Amount *amount_with_fee;
    596 
    597         amount_with_fee = &tl->details.old_coin_recoup->value;
    598         /* We count recoups of refreshed coins like refunds for the dirty old
    599            coin, as they equivalently _increase_ the remaining value on the
    600            _old_ coin */
    601         TALER_ARL_amount_add (&refunds,
    602                               &refunds,
    603                               amount_with_fee);
    604         break;
    605       }
    606     case TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW:
    607       {
    608         const struct TALER_Amount *amount_with_fee;
    609 
    610         /* We count recoups of the coin as expenditures, as it
    611            equivalently decreases the remaining value of the recouped coin. */
    612         amount_with_fee = &tl->details.recoup->value;
    613         TALER_ARL_amount_add (&expenditures,
    614                               &expenditures,
    615                               amount_with_fee);
    616         break;
    617       }
    618     case TALER_EXCHANGEDB_TT_RECOUP_REFRESH:
    619       {
    620         const struct TALER_Amount *amount_with_fee;
    621 
    622         /* We count recoups of the coin as expenditures, as it
    623            equivalently decreases the remaining value of the recouped coin. */
    624         amount_with_fee = &tl->details.recoup_refresh->value;
    625         TALER_ARL_amount_add (&expenditures,
    626                               &expenditures,
    627                               amount_with_fee);
    628         break;
    629       }
    630     case TALER_EXCHANGEDB_TT_PURSE_DEPOSIT:
    631       {
    632         const struct TALER_Amount *amount_with_fee;
    633 
    634         amount_with_fee = &tl->details.purse_deposit->amount;
    635         if (! tl->details.purse_deposit->refunded)
    636           TALER_ARL_amount_add (&expenditures,
    637                                 &expenditures,
    638                                 amount_with_fee);
    639         break;
    640       }
    641 
    642     case TALER_EXCHANGEDB_TT_PURSE_REFUND:
    643       {
    644         const struct TALER_Amount *amount_with_fee;
    645 
    646         amount_with_fee = &tl->details.purse_refund->refund_amount;
    647         fee_claimed = &tl->details.purse_refund->refund_fee;
    648         TALER_ARL_amount_add (&refunds,
    649                               &refunds,
    650                               amount_with_fee);
    651         TALER_ARL_amount_add (&expenditures,
    652                               &expenditures,
    653                               fee_claimed);
    654         /* Check that the fees given in the transaction list and in dki match */
    655         if (0 !=
    656             TALER_amount_cmp (&issue->fees.refund,
    657                               fee_claimed))
    658         {
    659           /* Disagreement in fee structure between exchange and auditor! */
    660           qs = report_amount_arithmetic_inconsistency ("refund fee",
    661                                                        0,
    662                                                        fee_claimed,
    663                                                        &issue->fees.refund,
    664                                                        1);
    665           if (0 > qs)
    666             return qs;
    667         }
    668         break;
    669       }
    670 
    671     case TALER_EXCHANGEDB_TT_RESERVE_OPEN:
    672       {
    673         const struct TALER_Amount *amount_with_fee;
    674 
    675         amount_with_fee = &tl->details.reserve_open->coin_contribution;
    676         TALER_ARL_amount_add (&expenditures,
    677                               &expenditures,
    678                               amount_with_fee);
    679         break;
    680       }
    681     } /* switch (tl->type) */
    682   } /* for 'tl' */
    683 
    684   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    685               "Deposits for this aggregation (after fees) are %s\n",
    686               TALER_amount2s (merchant_gain));
    687   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    688               "Aggregation loss due to refunds is %s\n",
    689               TALER_amount2s (&merchant_loss));
    690   *deposit_gain = *merchant_gain;
    691   if ((NULL != deposited) &&
    692       (NULL != deposit_fee) &&
    693       (0 == TALER_amount_cmp (&refunds,
    694                               deposited)))
    695   {
    696     /* We had a /deposit operation AND /refund operations adding up to the
    697        total deposited value including deposit fee. Thus, we should not
    698        subtract the /deposit fee from the merchant gain (as it was also
    699        refunded). */
    700     TALER_ARL_amount_add (merchant_gain,
    701                           merchant_gain,
    702                           deposit_fee);
    703   }
    704   {
    705     struct TALER_Amount final_gain;
    706 
    707     if (TALER_ARL_SR_INVALID_NEGATIVE ==
    708         TALER_ARL_amount_subtract_neg (&final_gain,
    709                                        merchant_gain,
    710                                        &merchant_loss))
    711     {
    712       /* refunds above deposits? Bad! */
    713       qs = report_coin_arithmetic_inconsistency ("refund (merchant)",
    714                                                  coin_pub,
    715                                                  merchant_gain,
    716                                                  &merchant_loss,
    717                                                  1);
    718       if (0 > qs)
    719         return qs;
    720       /* For the overall aggregation, we should not count this
    721          as a NEGATIVE contribution as that is not allowed; so
    722          let's count it as zero as that's the best we can do. */
    723       GNUNET_assert (GNUNET_OK ==
    724                      TALER_amount_set_zero (TALER_ARL_currency,
    725                                             merchant_gain));
    726     }
    727     else
    728     {
    729       *merchant_gain = final_gain;
    730     }
    731   }
    732 
    733 
    734   /* Calculate total balance change, i.e. expenditures (recoup, deposit, refresh)
    735      minus refunds (refunds, recoup-to-old) */
    736   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    737               "Subtracting refunds of %s from coin value loss\n",
    738               TALER_amount2s (&refunds));
    739   if (TALER_ARL_SR_INVALID_NEGATIVE ==
    740       TALER_ARL_amount_subtract_neg (&spent,
    741                                      &expenditures,
    742                                      &refunds))
    743   {
    744     /* refunds above expenditures? Bad! */
    745     qs = report_coin_arithmetic_inconsistency ("refund (balance)",
    746                                                coin_pub,
    747                                                &expenditures,
    748                                                &refunds,
    749                                                1);
    750     if (0 > qs)
    751       return qs;
    752   }
    753   else
    754   {
    755     /* Now check that 'spent' is less or equal than the total coin value */
    756     if (1 == TALER_amount_cmp (&spent,
    757                                &issue->value))
    758     {
    759       /* spent > value */
    760       qs = report_coin_arithmetic_inconsistency ("spend",
    761                                                  coin_pub,
    762                                                  &spent,
    763                                                  &issue->value,
    764                                                  -1);
    765       if (0 > qs)
    766         return qs;
    767     }
    768   }
    769   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    770               "Final merchant gain after refunds is %s\n",
    771               TALER_amount2s (deposit_gain));
    772   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    773               "Coin %s contributes %s to contract %s\n",
    774               TALER_B2S (coin_pub),
    775               TALER_amount2s (merchant_gain),
    776               GNUNET_h2s (&h_contract_terms->hash));
    777   return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
    778 }
    779 
    780 
    781 /**
    782  * Function called with the results of the lookup of the
    783  * transaction data associated with a wire transfer identifier.
    784  *
    785  * @param[in,out] wcc a `struct WireCheckContext`
    786  * @param rowid which row in the table is the information from (for diagnostics)
    787  * @param merchant_pub public key of the merchant (should be same for all callbacks with the same @e cls)
    788  * @param account_pay_uri where did we transfer the funds?
    789  * @param h_payto hash over @a account_payto_uri as it is in the DB
    790  * @param exec_time execution time of the wire transfer (should be same for all callbacks with the same @e cls)
    791  * @param h_contract_terms which proposal was this payment about
    792  * @param denom_pub denomination of @a coin_pub
    793  * @param coin_pub which public key was this payment about
    794  * @param coin_value amount contributed by this coin in total (with fee),
    795  *                   but excluding refunds by this coin
    796  * @param deposit_fee applicable deposit fee for this coin, actual
    797  *        fees charged may differ if coin was refunded
    798  */
    799 static void
    800 wire_transfer_information_cb (
    801   struct WireCheckContext *wcc,
    802   uint64_t rowid,
    803   const struct TALER_MerchantPublicKeyP *merchant_pub,
    804   const struct TALER_FullPayto account_pay_uri,
    805   const struct TALER_FullPaytoHashP *h_payto,
    806   struct GNUNET_TIME_Timestamp exec_time,
    807   const struct TALER_PrivateContractHashP *h_contract_terms,
    808   const struct TALER_DenominationPublicKey *denom_pub,
    809   const struct TALER_CoinSpendPublicKeyP *coin_pub,
    810   const struct TALER_Amount *coin_value,
    811   const struct TALER_Amount *deposit_fee)
    812 {
    813   const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue;
    814   struct TALER_Amount computed_value;
    815   struct TALER_Amount total_deposit_without_refunds;
    816   struct TALER_EXCHANGEDB_TransactionList *tl;
    817   struct TALER_CoinPublicInfo coin;
    818   enum GNUNET_DB_QueryStatus qs;
    819   struct TALER_FullPaytoHashP hpt;
    820   uint64_t etag_out;
    821 
    822   if (0 > wcc->qs)
    823     return;
    824   TALER_full_payto_hash (account_pay_uri,
    825                          &hpt);
    826   if (0 !=
    827       GNUNET_memcmp (&hpt,
    828                      h_payto))
    829   {
    830     qs = report_row_inconsistency ("wire_targets",
    831                                    rowid,
    832                                    "h-payto does not match payto URI");
    833     if (0 > qs)
    834     {
    835       wcc->qs = qs;
    836       return;
    837     }
    838   }
    839   /* Obtain coin's transaction history */
    840   /* FIXME-Optimization: could use 'start' mechanism to only fetch
    841      transactions we did not yet process, instead of going over them again and
    842      again.*/
    843 
    844   {
    845     struct TALER_Amount balance;
    846     struct TALER_DenominationHashP h_denom_pub;
    847 
    848     qs = TALER_EXCHANGEDB_get_coin_transactions (TALER_ARL_edb,
    849                                                  false,
    850                                                  coin_pub,
    851                                                  0,
    852                                                  0,
    853                                                  &etag_out,
    854                                                  &balance,
    855                                                  &h_denom_pub,
    856                                                  &tl);
    857   }
    858   if (0 > qs)
    859   {
    860     wcc->qs = qs;
    861     TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    862     return;
    863   }
    864   if (NULL == tl)
    865   {
    866     qs = report_row_inconsistency ("aggregation",
    867                                    rowid,
    868                                    "no transaction history for coin claimed in aggregation");
    869     if (0 > qs)
    870       wcc->qs = qs;
    871     return;
    872   }
    873   qs = TALER_EXCHANGEDB_get_known_coin (TALER_ARL_edb,
    874                                         coin_pub,
    875                                         &coin);
    876   if (0 > qs)
    877   {
    878     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    879     wcc->qs = qs;
    880     return;
    881   }
    882   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    883   {
    884     /* this should be a foreign key violation at this point! */
    885     qs = report_row_inconsistency ("aggregation",
    886                                    rowid,
    887                                    "could not get coin details for coin claimed in aggregation");
    888     if (0 > qs)
    889       wcc->qs = qs;
    890     TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    891     return;
    892   }
    893   qs = TALER_ARL_get_denomination_info_by_hash (&coin.denom_pub_hash,
    894                                                 &issue);
    895   if (0 > qs)
    896   {
    897     wcc->qs = qs;
    898     TALER_denom_sig_free (&coin.denom_sig);
    899     TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    900     return;
    901   }
    902   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    903   {
    904     TALER_denom_sig_free (&coin.denom_sig);
    905     TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    906     qs = report_row_inconsistency ("aggregation",
    907                                    rowid,
    908                                    "could not find denomination key for coin claimed in aggregation");
    909     if (0 > qs)
    910       wcc->qs = qs;
    911     return;
    912   }
    913   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    914               "Testing coin `%s' for validity\n",
    915               TALER_B2S (&coin.coin_pub));
    916   if (GNUNET_OK !=
    917       TALER_test_coin_valid (&coin,
    918                              denom_pub))
    919   {
    920     struct TALER_AUDITORDB_BadSigLosses bsl = {
    921       .problem_row_id = rowid,
    922       .operation = (char *) "wire",
    923       .loss = *coin_value,
    924       .operation_specific_pub = coin.coin_pub.eddsa_pub
    925     };
    926 
    927     qs = TALER_AUDITORDB_insert_bad_sig_losses (
    928       TALER_ARL_adb,
    929       &bsl);
    930     if (qs < 0)
    931     {
    932       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    933       wcc->qs = qs;
    934       TALER_denom_sig_free (&coin.denom_sig);
    935       TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    936       return;
    937     }
    938     TALER_ARL_amount_add (&TALER_ARL_USE_AB (aggregation_total_bad_sig_loss),
    939                           &TALER_ARL_USE_AB (aggregation_total_bad_sig_loss),
    940                           coin_value);
    941     qs = report_row_inconsistency ("deposit",
    942                                    rowid,
    943                                    "coin denomination signature invalid");
    944     if (0 > qs)
    945     {
    946       wcc->qs = qs;
    947       TALER_denom_sig_free (&coin.denom_sig);
    948       TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    949       return;
    950     }
    951   }
    952   TALER_denom_sig_free (&coin.denom_sig);
    953   GNUNET_assert (NULL != issue); /* mostly to help static analysis */
    954   /* Check transaction history to see if it supports aggregate
    955      valuation */
    956   qs = check_transaction_history_for_deposit (
    957     coin_pub,
    958     h_contract_terms,
    959     merchant_pub,
    960     issue,
    961     tl,
    962     &computed_value,
    963     &total_deposit_without_refunds);
    964   if (0 > qs)
    965   {
    966     TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    967     wcc->qs = qs;
    968     return;
    969   }
    970   TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    971   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    972               "Coin contributes %s to aggregate (deposits after fees and refunds)\n",
    973               TALER_amount2s (&computed_value));
    974   {
    975     struct TALER_Amount coin_value_without_fee;
    976 
    977     if (TALER_ARL_SR_INVALID_NEGATIVE ==
    978         TALER_ARL_amount_subtract_neg (&coin_value_without_fee,
    979                                        coin_value,
    980                                        deposit_fee))
    981     {
    982       qs = report_amount_arithmetic_inconsistency (
    983         "aggregation (fee structure)",
    984         rowid,
    985         coin_value,
    986         deposit_fee,
    987         -1);
    988       if (0 > qs)
    989       {
    990         wcc->qs = qs;
    991         return;
    992       }
    993     }
    994     else if (0 !=
    995              TALER_amount_cmp (&total_deposit_without_refunds,
    996                                &coin_value_without_fee))
    997     {
    998       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    999                   "Expected coin contribution of %s to aggregate\n",
   1000                   TALER_amount2s (&coin_value_without_fee));
   1001       qs = report_amount_arithmetic_inconsistency (
   1002         "aggregation (contribution)",
   1003         rowid,
   1004         &coin_value_without_fee,
   1005         &total_deposit_without_refunds,
   1006         -1);
   1007       if (0 > qs)
   1008       {
   1009         wcc->qs = qs;
   1010         return;
   1011       }
   1012     }
   1013   }
   1014   /* Check other details of wire transfer match */
   1015   if (0 != TALER_full_payto_cmp (account_pay_uri,
   1016                                  wcc->payto_uri))
   1017   {
   1018     qs = report_row_inconsistency ("aggregation",
   1019                                    rowid,
   1020                                    "target of outgoing wire transfer do not match hash of wire from deposit");
   1021     if (0 > qs)
   1022     {
   1023       wcc->qs = qs;
   1024       return;
   1025     }
   1026   }
   1027   if (GNUNET_TIME_timestamp_cmp (exec_time,
   1028                                  !=,
   1029                                  wcc->date))
   1030   {
   1031     /* This should be impossible from database constraints */
   1032     GNUNET_break (0);
   1033     qs = report_row_inconsistency ("aggregation",
   1034                                    rowid,
   1035                                    "date given in aggregate does not match wire transfer date");
   1036     if (0 > qs)
   1037     {
   1038       wcc->qs = qs;
   1039       return;
   1040     }
   1041   }
   1042 
   1043   /* Add coin's contribution to total aggregate value */
   1044   {
   1045     struct TALER_Amount res;
   1046 
   1047     TALER_ARL_amount_add (&res,
   1048                           &wcc->total_deposits,
   1049                           &computed_value);
   1050     wcc->total_deposits = res;
   1051   }
   1052 }
   1053 
   1054 
   1055 /**
   1056  * Lookup the wire fee that the exchange charges at @a timestamp.
   1057  *
   1058  * @param ac context for caching the result
   1059  * @param method method of the wire plugin
   1060  * @param timestamp time for which we need the fee
   1061  * @return NULL on error (fee unknown)
   1062  */
   1063 static const struct TALER_Amount *
   1064 get_wire_fee (struct AggregationContext *ac,
   1065               const char *method,
   1066               struct GNUNET_TIME_Timestamp timestamp)
   1067 {
   1068   struct WireFeeInfo *wfi;
   1069   struct WireFeeInfo *pos;
   1070   struct TALER_MasterSignatureP master_sig;
   1071   enum GNUNET_DB_QueryStatus qs;
   1072   uint64_t rowid;
   1073 
   1074   /* Check if fee is already loaded in cache */
   1075   for (pos = ac->fee_head; NULL != pos; pos = pos->next)
   1076   {
   1077     if (GNUNET_TIME_timestamp_cmp (pos->start_date,
   1078                                    <=,
   1079                                    timestamp) &&
   1080         GNUNET_TIME_timestamp_cmp (pos->end_date,
   1081                                    >,
   1082                                    timestamp))
   1083       return &pos->fees.wire;
   1084     if (GNUNET_TIME_timestamp_cmp (pos->start_date,
   1085                                    >,
   1086                                    timestamp))
   1087       break;
   1088   }
   1089 
   1090   /* Lookup fee in exchange database */
   1091   wfi = GNUNET_new (struct WireFeeInfo);
   1092   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
   1093       TALER_EXCHANGEDB_get_wire_fee (TALER_ARL_edb,
   1094                                      method,
   1095                                      timestamp,
   1096                                      &rowid,
   1097                                      &wfi->start_date,
   1098                                      &wfi->end_date,
   1099                                      &wfi->fees,
   1100                                      &master_sig))
   1101   {
   1102     GNUNET_break (0);
   1103     GNUNET_free (wfi);
   1104     return NULL;
   1105   }
   1106 
   1107   /* Check signature. (This is not terribly meaningful as the exchange can
   1108      easily make this one up, but it means that we have proof that the master
   1109      key was used for inconsistent wire fees if a merchant complains.) */
   1110   if (GNUNET_OK !=
   1111       TALER_exchange_offline_wire_fee_verify (
   1112         method,
   1113         wfi->start_date,
   1114         wfi->end_date,
   1115         &wfi->fees,
   1116         &TALER_ARL_master_pub,
   1117         &master_sig))
   1118   {
   1119     ac->qs = report_row_inconsistency ("wire-fee",
   1120                                        timestamp.abs_time.abs_value_us,
   1121                                        "wire fee signature invalid at given time");
   1122     /* Note: continue with the fee despite the signature
   1123        being invalid here; hopefully it is really only the
   1124        signature that is bad ... */
   1125   }
   1126 
   1127   /* Established fee, keep in sorted list */
   1128   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1129               "Wire fee is %s starting at %s\n",
   1130               TALER_amount2s (&wfi->fees.wire),
   1131               GNUNET_TIME_timestamp2s (wfi->start_date));
   1132   if ((NULL == pos) ||
   1133       (NULL == pos->prev))
   1134     GNUNET_CONTAINER_DLL_insert (ac->fee_head,
   1135                                  ac->fee_tail,
   1136                                  wfi);
   1137   else
   1138     GNUNET_CONTAINER_DLL_insert_after (ac->fee_head,
   1139                                        ac->fee_tail,
   1140                                        pos->prev,
   1141                                        wfi);
   1142   /* Check non-overlaping fee invariant */
   1143   if ((NULL != wfi->prev) &&
   1144       GNUNET_TIME_timestamp_cmp (wfi->prev->end_date,
   1145                                  >,
   1146                                  wfi->start_date))
   1147   {
   1148     struct TALER_AUDITORDB_FeeTimeInconsistency ftib = {
   1149       .problem_row_id = rowid,
   1150       .diagnostic = (char *) "start date before previous end date",
   1151       .time = wfi->start_date.abs_time,
   1152       .type = (char *) method
   1153     };
   1154 
   1155     qs = TALER_AUDITORDB_insert_fee_time_inconsistency (
   1156       TALER_ARL_adb,
   1157       &ftib);
   1158     if (qs < 0)
   1159     {
   1160       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1161       ac->qs = qs;
   1162       return NULL;
   1163     }
   1164   }
   1165   if ((NULL != wfi->next) &&
   1166       GNUNET_TIME_timestamp_cmp (wfi->next->start_date,
   1167                                  >=,
   1168                                  wfi->end_date))
   1169   {
   1170     struct TALER_AUDITORDB_FeeTimeInconsistency ftia = {
   1171       .problem_row_id = rowid,
   1172       .diagnostic = (char *) "end date date after next start date",
   1173       .time = wfi->end_date.abs_time,
   1174       .type = (char *) method
   1175     };
   1176 
   1177     qs = TALER_AUDITORDB_insert_fee_time_inconsistency (
   1178       TALER_ARL_adb,
   1179       &ftia);
   1180 
   1181     if (qs < 0)
   1182     {
   1183       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1184       ac->qs = qs;
   1185       return NULL;
   1186     }
   1187   }
   1188   return &wfi->fees.wire;
   1189 }
   1190 
   1191 
   1192 /**
   1193  * Check that a wire transfer made by the exchange is valid
   1194  * (has matching deposits).
   1195  *
   1196  * @param ac a `struct AggregationContext`
   1197  * @param rowid identifier of the respective row in the database
   1198  * @param date timestamp of the wire transfer (roughly)
   1199  * @param wtid wire transfer subject
   1200  * @param payto_uri bank account details of the receiver
   1201  * @param amount amount that was wired
   1202  * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop iteration
   1203  */
   1204 static enum GNUNET_GenericReturnValue
   1205 check_wire_out_cb (struct AggregationContext *ac,
   1206                    uint64_t rowid,
   1207                    struct GNUNET_TIME_Timestamp date,
   1208                    const struct TALER_WireTransferIdentifierRawP *wtid,
   1209                    const struct TALER_FullPayto payto_uri,
   1210                    const struct TALER_Amount *amount)
   1211 {
   1212   struct WireCheckContext wcc;
   1213   struct TALER_Amount final_amount;
   1214   struct TALER_Amount exchange_gain;
   1215   enum GNUNET_DB_QueryStatus qs;
   1216   char *method;
   1217 
   1218   /* should be monotonically increasing */
   1219   GNUNET_assert (rowid >=
   1220                  TALER_ARL_USE_PP (aggregation_last_wire_out_serial_id));
   1221   TALER_ARL_USE_PP (aggregation_last_wire_out_serial_id) = rowid + 1;
   1222 
   1223   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1224               "Checking wire transfer %s over %s performed on %s\n",
   1225               TALER_B2S (wtid),
   1226               TALER_amount2s (amount),
   1227               GNUNET_TIME_timestamp2s (date));
   1228   if (NULL == (method = TALER_payto_get_method (payto_uri.full_payto)))
   1229   {
   1230     qs = report_row_inconsistency ("wire_out",
   1231                                    rowid,
   1232                                    "specified wire address lacks method");
   1233     if (0 > qs)
   1234       ac->qs = qs;
   1235     return GNUNET_OK;
   1236   }
   1237 
   1238   wcc.ac = ac;
   1239   wcc.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
   1240   wcc.date = date;
   1241   GNUNET_assert (GNUNET_OK ==
   1242                  TALER_amount_set_zero (amount->currency,
   1243                                         &wcc.total_deposits));
   1244   wcc.payto_uri = payto_uri;
   1245   qs = TALER_EXCHANGEDB_iterate_wire_transfers (TALER_ARL_edb,
   1246                                                 wtid,
   1247                                                 &wire_transfer_information_cb,
   1248                                                 &wcc);
   1249   if (0 > qs)
   1250   {
   1251     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1252     ac->qs = qs;
   1253     GNUNET_free (method);
   1254     return GNUNET_SYSERR;
   1255   }
   1256   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != wcc.qs)
   1257   {
   1258     /* Note: detailed information was already logged
   1259        in #wire_transfer_information_cb, so here we
   1260        only log for debugging */
   1261     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1262                 "Inconsistency for wire_out %llu (WTID %s) detected\n",
   1263                 (unsigned long long) rowid,
   1264                 TALER_B2S (wtid));
   1265   }
   1266 
   1267 
   1268   /* Subtract aggregation fee from total (if possible) */
   1269   {
   1270     const struct TALER_Amount *wire_fee;
   1271 
   1272     wire_fee = get_wire_fee (ac,
   1273                              method,
   1274                              date);
   1275     if (0 > ac->qs)
   1276     {
   1277       GNUNET_free (method);
   1278       return GNUNET_SYSERR;
   1279     }
   1280     if (NULL == wire_fee)
   1281     {
   1282       qs = report_row_inconsistency ("wire-fee",
   1283                                      date.abs_time.abs_value_us,
   1284                                      "wire fee unavailable for given time");
   1285       if (qs < 0)
   1286       {
   1287         ac->qs = qs;
   1288         GNUNET_free (method);
   1289         return GNUNET_SYSERR;
   1290       }
   1291       /* If fee is unknown, we just assume the fee is zero */
   1292       final_amount = wcc.total_deposits;
   1293     }
   1294     else if (TALER_ARL_SR_INVALID_NEGATIVE ==
   1295              TALER_ARL_amount_subtract_neg (&final_amount,
   1296                                             &wcc.total_deposits,
   1297                                             wire_fee))
   1298     {
   1299       qs = report_amount_arithmetic_inconsistency (
   1300         "wire out (fee structure)",
   1301         rowid,
   1302         &wcc.total_deposits,
   1303         wire_fee,
   1304         -1);
   1305       /* If fee arithmetic fails, we just assume the fee is zero */
   1306       if (0 > qs)
   1307       {
   1308         ac->qs = qs;
   1309         GNUNET_free (method);
   1310         return GNUNET_SYSERR;
   1311       }
   1312       final_amount = wcc.total_deposits;
   1313     }
   1314   }
   1315   GNUNET_free (method);
   1316 
   1317   /* Round down to amount supported by wire method */
   1318   GNUNET_break (GNUNET_SYSERR !=
   1319                 TALER_amount_round_down (&final_amount,
   1320                                          &TALER_ARL_currency_round_unit));
   1321 
   1322   /* Calculate the exchange's gain as the fees plus rounding differences! */
   1323   TALER_ARL_amount_subtract (&exchange_gain,
   1324                              &wcc.total_deposits,
   1325                              &final_amount);
   1326 
   1327   /* Sum up aggregation fees (we simply include the rounding gains) */
   1328   TALER_ARL_amount_add (&TALER_ARL_USE_AB (aggregation_total_wire_fee_revenue),
   1329                         &TALER_ARL_USE_AB (aggregation_total_wire_fee_revenue),
   1330                         &exchange_gain);
   1331 
   1332   /* Check that calculated amount matches actual amount */
   1333   if (0 != TALER_amount_cmp (amount,
   1334                              &final_amount))
   1335   {
   1336     struct TALER_Amount delta;
   1337 
   1338     if (0 < TALER_amount_cmp (amount,
   1339                               &final_amount))
   1340     {
   1341       /* amount > final_amount */
   1342       TALER_ARL_amount_subtract (&delta,
   1343                                  amount,
   1344                                  &final_amount);
   1345       TALER_ARL_amount_add (&TALER_ARL_USE_AB (
   1346                               aggregation_total_wire_out_delta_plus),
   1347                             &TALER_ARL_USE_AB (
   1348                               aggregation_total_wire_out_delta_plus),
   1349                             &delta);
   1350     }
   1351     else
   1352     {
   1353       /* amount < final_amount */
   1354       TALER_ARL_amount_subtract (&delta,
   1355                                  &final_amount,
   1356                                  amount);
   1357       TALER_ARL_amount_add (&TALER_ARL_USE_AB (
   1358                               aggregation_total_wire_out_delta_minus),
   1359                             &TALER_ARL_USE_AB (
   1360                               aggregation_total_wire_out_delta_minus),
   1361                             &delta);
   1362     }
   1363 
   1364     {
   1365       struct TALER_AUDITORDB_WireOutInconsistency woi = {
   1366         .destination_account = payto_uri,
   1367         .diagnostic = (char *) "aggregated amount does not match expectations",
   1368         .wire_out_row_id = rowid,
   1369         .expected = final_amount,
   1370         .claimed = *amount
   1371       };
   1372 
   1373       qs = TALER_AUDITORDB_insert_wire_out_inconsistency (
   1374         TALER_ARL_adb,
   1375         &woi);
   1376 
   1377       if (qs < 0)
   1378       {
   1379         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1380         ac->qs = qs;
   1381         return GNUNET_SYSERR;
   1382       }
   1383     }
   1384     return GNUNET_OK;
   1385   }
   1386   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1387               "Aggregation unit %s is OK\n",
   1388               TALER_B2S (wtid));
   1389   return GNUNET_OK;
   1390 }
   1391 
   1392 
   1393 /**
   1394  * Analyze the exchange aggregator's payment processing.
   1395  *
   1396  * @param cls closure
   1397  * @return transaction status code
   1398  */
   1399 static enum GNUNET_DB_QueryStatus
   1400 analyze_aggregations (void *cls)
   1401 {
   1402   struct AggregationContext ac;
   1403   struct WireFeeInfo *wfi;
   1404   enum GNUNET_DB_QueryStatus qs;
   1405 
   1406   (void) cls;
   1407   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1408               "Analyzing aggregations\n");
   1409   qs = TALER_AUDITORDB_get_auditor_progress (
   1410     TALER_ARL_adb,
   1411     TALER_ARL_GET_PP (aggregation_last_wire_out_serial_id),
   1412     NULL);
   1413   if (0 > qs)
   1414   {
   1415     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1416     return qs;
   1417   }
   1418   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
   1419   {
   1420     GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
   1421                 "First analysis using this auditor, starting audit from scratch\n");
   1422   }
   1423   else
   1424   {
   1425     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1426                 "Resuming aggregation audit at %llu\n",
   1427                 (unsigned long long) TALER_ARL_USE_PP (
   1428                   aggregation_last_wire_out_serial_id));
   1429   }
   1430 
   1431   memset (&ac,
   1432           0,
   1433           sizeof (ac));
   1434   qs = TALER_AUDITORDB_get_balance (
   1435     TALER_ARL_adb,
   1436     TALER_ARL_GET_AB (aggregation_total_wire_fee_revenue),
   1437     TALER_ARL_GET_AB (aggregation_total_arithmetic_delta_plus),
   1438     TALER_ARL_GET_AB (aggregation_total_arithmetic_delta_minus),
   1439     TALER_ARL_GET_AB (aggregation_total_bad_sig_loss),
   1440     TALER_ARL_GET_AB (aggregation_total_wire_out_delta_plus),
   1441     TALER_ARL_GET_AB (aggregation_total_wire_out_delta_minus),
   1442     TALER_ARL_GET_AB (aggregation_total_coin_delta_plus),
   1443     TALER_ARL_GET_AB (aggregation_total_coin_delta_minus),
   1444     NULL);
   1445   if (0 > qs)
   1446   {
   1447     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1448     return qs;
   1449   }
   1450 
   1451   ac.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
   1452   qs = TALER_EXCHANGEDB_iterate_wire_outs_above_serial_id (
   1453     TALER_ARL_edb,
   1454     TALER_ARL_USE_PP (aggregation_last_wire_out_serial_id),
   1455     &check_wire_out_cb,
   1456     &ac);
   1457   if (0 > qs)
   1458   {
   1459     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1460     ac.qs = qs;
   1461   }
   1462   while (NULL != (wfi = ac.fee_head))
   1463   {
   1464     GNUNET_CONTAINER_DLL_remove (ac.fee_head,
   1465                                  ac.fee_tail,
   1466                                  wfi);
   1467     GNUNET_free (wfi);
   1468   }
   1469   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
   1470   {
   1471     /* there were no wire out entries to be looked at, we are done */
   1472     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1473                 "No wire out entries found\n");
   1474     return qs;
   1475   }
   1476   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != ac.qs)
   1477   {
   1478     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == ac.qs);
   1479     return ac.qs;
   1480   }
   1481   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1482               "Finished aggregation audit at %llu\n",
   1483               (unsigned long long) TALER_ARL_USE_PP (
   1484                 aggregation_last_wire_out_serial_id));
   1485   qs = TALER_AUDITORDB_insert_balance (
   1486     TALER_ARL_adb,
   1487     TALER_ARL_SET_AB (aggregation_total_wire_fee_revenue),
   1488     TALER_ARL_SET_AB (aggregation_total_arithmetic_delta_plus),
   1489     TALER_ARL_SET_AB (aggregation_total_arithmetic_delta_minus),
   1490     TALER_ARL_SET_AB (aggregation_total_bad_sig_loss),
   1491     TALER_ARL_SET_AB (aggregation_total_wire_out_delta_plus),
   1492     TALER_ARL_SET_AB (aggregation_total_wire_out_delta_minus),
   1493     TALER_ARL_SET_AB (aggregation_total_coin_delta_plus),
   1494     TALER_ARL_SET_AB (aggregation_total_coin_delta_minus),
   1495     NULL);
   1496   if (0 > qs)
   1497   {
   1498     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1499                 "Failed to update auditor DB, not recording progress\n");
   1500     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1501     return qs;
   1502   }
   1503 
   1504   qs = TALER_AUDITORDB_insert_auditor_progress (
   1505     TALER_ARL_adb,
   1506     TALER_ARL_SET_PP (aggregation_last_wire_out_serial_id),
   1507     NULL);
   1508   if (0 > qs)
   1509   {
   1510     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1511                 "Failed to update auditor DB, not recording progress\n");
   1512     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1513     return qs;
   1514   }
   1515   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1516               "Concluded aggregation audit step at %llu\n",
   1517               (unsigned long long) TALER_ARL_USE_PP (
   1518                 aggregation_last_wire_out_serial_id));
   1519 
   1520   return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
   1521 }
   1522 
   1523 
   1524 /**
   1525  * Function called on events received from Postgres.
   1526  *
   1527  * @param cls closure, NULL
   1528  * @param extra additional event data provided
   1529  * @param extra_size number of bytes in @a extra
   1530  */
   1531 static void
   1532 db_notify (void *cls,
   1533            const void *extra,
   1534            size_t extra_size)
   1535 {
   1536   (void) cls;
   1537   (void) extra;
   1538   (void) extra_size;
   1539   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1540               "Received notification to wake aggregation helper\n");
   1541   if (GNUNET_OK !=
   1542       TALER_ARL_setup_sessions_and_run (&analyze_aggregations,
   1543                                         NULL))
   1544   {
   1545     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1546                 "Audit failed\n");
   1547     GNUNET_SCHEDULER_shutdown ();
   1548     global_ret = EXIT_FAILURE;
   1549     return;
   1550   }
   1551 }
   1552 
   1553 
   1554 /**
   1555  * Function called on shutdown.
   1556  */
   1557 static void
   1558 do_shutdown (void *cls)
   1559 {
   1560   (void) cls;
   1561   if (NULL != eh)
   1562   {
   1563     TALER_AUDITORDB_event_listen_cancel (eh);
   1564     eh = NULL;
   1565   }
   1566   TALER_ARL_done ();
   1567 }
   1568 
   1569 
   1570 /**
   1571  * Main function that will be run.
   1572  *
   1573  * @param cls closure
   1574  * @param args remaining command-line arguments
   1575  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
   1576  * @param c configuration
   1577  */
   1578 static void
   1579 run (void *cls,
   1580      char *const *args,
   1581      const char *cfgfile,
   1582      const struct GNUNET_CONFIGURATION_Handle *c)
   1583 {
   1584   (void) cls;
   1585   (void) args;
   1586   (void) cfgfile;
   1587 
   1588   cfg = c;
   1589   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
   1590                                  NULL);
   1591   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1592               "Launching aggregation auditor\n");
   1593   if (GNUNET_OK !=
   1594       TALER_ARL_init (c))
   1595   {
   1596     global_ret = EXIT_FAILURE;
   1597     return;
   1598   }
   1599 
   1600   if (test_mode != 1)
   1601   {
   1602     struct GNUNET_DB_EventHeaderP es = {
   1603       .size = htons (sizeof (es)),
   1604       .type = htons (TALER_DBEVENT_EXCHANGE_AUDITOR_WAKE_HELPER_AGGREGATION)
   1605     };
   1606 
   1607     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1608                 "Running helper indefinitely\n");
   1609     eh = TALER_AUDITORDB_event_listen (TALER_ARL_adb,
   1610                                        &es,
   1611                                        GNUNET_TIME_UNIT_FOREVER_REL,
   1612                                        &db_notify,
   1613                                        NULL);
   1614   }
   1615   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1616               "Starting audit\n");
   1617   if (GNUNET_OK !=
   1618       TALER_ARL_setup_sessions_and_run (&analyze_aggregations,
   1619                                         NULL))
   1620   {
   1621     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1622                 "Audit failed\n");
   1623     GNUNET_SCHEDULER_shutdown ();
   1624     global_ret = EXIT_FAILURE;
   1625     return;
   1626   }
   1627 }
   1628 
   1629 
   1630 /**
   1631  * The main function to audit the exchange's aggregation processing.
   1632  *
   1633  * @param argc number of arguments from the command line
   1634  * @param argv command line arguments
   1635  * @return 0 ok, 1 on error
   1636  */
   1637 int
   1638 main (int argc,
   1639       char *const *argv)
   1640 {
   1641   const struct GNUNET_GETOPT_CommandLineOption options[] = {
   1642     GNUNET_GETOPT_option_flag ('i',
   1643                                "internal",
   1644                                "perform checks only applicable for exchange-internal audits",
   1645                                &internal_checks),
   1646     GNUNET_GETOPT_option_flag ('t',
   1647                                "test",
   1648                                "run in test mode and exit when idle",
   1649                                &test_mode),
   1650     GNUNET_GETOPT_option_timetravel ('T',
   1651                                      "timetravel"),
   1652     GNUNET_GETOPT_OPTION_END
   1653   };
   1654   enum GNUNET_GenericReturnValue ret;
   1655 
   1656   ret = GNUNET_PROGRAM_run (
   1657     TALER_AUDITOR_project_data (),
   1658     argc,
   1659     argv,
   1660     "taler-helper-auditor-aggregation",
   1661     gettext_noop ("Audit Taler exchange aggregation activity"),
   1662     options,
   1663     &run,
   1664     NULL);
   1665   if (GNUNET_SYSERR == ret)
   1666     return EXIT_INVALIDARGUMENT;
   1667   if (GNUNET_NO == ret)
   1668     return EXIT_SUCCESS;
   1669   return global_ret;
   1670 }
   1671 
   1672 
   1673 /* end of taler-helper-auditor-aggregation.c */