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


      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 exchange_payto_uri which of the exchange's accounts was debited,
    791  *        `full_payto` NULL if not recorded; unused here
    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   const struct TALER_FullPayto exchange_payto_uri,
    809   struct GNUNET_TIME_Timestamp exec_time,
    810   const struct TALER_PrivateContractHashP *h_contract_terms,
    811   const struct TALER_DenominationPublicKey *denom_pub,
    812   const struct TALER_CoinSpendPublicKeyP *coin_pub,
    813   const struct TALER_Amount *coin_value,
    814   const struct TALER_Amount *deposit_fee)
    815 {
    816   const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue;
    817   struct TALER_Amount computed_value;
    818   struct TALER_Amount total_deposit_without_refunds;
    819   struct TALER_EXCHANGEDB_TransactionList *tl;
    820   struct TALER_CoinPublicInfo coin;
    821   enum GNUNET_DB_QueryStatus qs;
    822   struct TALER_FullPaytoHashP hpt;
    823   uint64_t etag_out;
    824 
    825   if (0 > wcc->qs)
    826     return;
    827   TALER_full_payto_hash (account_pay_uri,
    828                          &hpt);
    829   if (0 !=
    830       GNUNET_memcmp (&hpt,
    831                      h_payto))
    832   {
    833     qs = report_row_inconsistency ("wire_targets",
    834                                    rowid,
    835                                    "h-payto does not match payto URI");
    836     if (0 > qs)
    837     {
    838       wcc->qs = qs;
    839       return;
    840     }
    841   }
    842   /* Obtain coin's transaction history */
    843   /* FIXME-Optimization: could use 'start' mechanism to only fetch
    844      transactions we did not yet process, instead of going over them again and
    845      again.*/
    846 
    847   {
    848     struct TALER_Amount balance;
    849     struct TALER_DenominationHashP h_denom_pub;
    850 
    851     qs = TALER_EXCHANGEDB_get_coin_transactions (TALER_ARL_edb,
    852                                                  false,
    853                                                  coin_pub,
    854                                                  0,
    855                                                  0,
    856                                                  &etag_out,
    857                                                  &balance,
    858                                                  &h_denom_pub,
    859                                                  &tl);
    860   }
    861   if (0 > qs)
    862   {
    863     wcc->qs = qs;
    864     TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    865     return;
    866   }
    867   if (NULL == tl)
    868   {
    869     qs = report_row_inconsistency ("aggregation",
    870                                    rowid,
    871                                    "no transaction history for coin claimed in aggregation");
    872     if (0 > qs)
    873       wcc->qs = qs;
    874     return;
    875   }
    876   qs = TALER_EXCHANGEDB_get_known_coin (TALER_ARL_edb,
    877                                         coin_pub,
    878                                         &coin);
    879   if (0 > qs)
    880   {
    881     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    882     wcc->qs = qs;
    883     return;
    884   }
    885   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    886   {
    887     /* this should be a foreign key violation at this point! */
    888     qs = report_row_inconsistency ("aggregation",
    889                                    rowid,
    890                                    "could not get coin details for coin claimed in aggregation");
    891     if (0 > qs)
    892       wcc->qs = qs;
    893     TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    894     return;
    895   }
    896   qs = TALER_ARL_get_denomination_info_by_hash (&coin.denom_pub_hash,
    897                                                 &issue);
    898   if (0 > qs)
    899   {
    900     wcc->qs = qs;
    901     TALER_denom_sig_free (&coin.denom_sig);
    902     TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    903     return;
    904   }
    905   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    906   {
    907     TALER_denom_sig_free (&coin.denom_sig);
    908     TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    909     qs = report_row_inconsistency ("aggregation",
    910                                    rowid,
    911                                    "could not find denomination key for coin claimed in aggregation");
    912     if (0 > qs)
    913       wcc->qs = qs;
    914     return;
    915   }
    916   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    917               "Testing coin `%s' for validity\n",
    918               TALER_B2S (&coin.coin_pub));
    919   if (GNUNET_OK !=
    920       TALER_test_coin_valid (&coin,
    921                              denom_pub))
    922   {
    923     struct TALER_AUDITORDB_BadSigLosses bsl = {
    924       .problem_row_id = rowid,
    925       .operation = (char *) "wire",
    926       .loss = *coin_value,
    927       .operation_specific_pub = coin.coin_pub.eddsa_pub
    928     };
    929 
    930     qs = TALER_AUDITORDB_insert_bad_sig_losses (
    931       TALER_ARL_adb,
    932       &bsl);
    933     if (qs < 0)
    934     {
    935       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    936       wcc->qs = qs;
    937       TALER_denom_sig_free (&coin.denom_sig);
    938       TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    939       return;
    940     }
    941     TALER_ARL_amount_add (&TALER_ARL_USE_AB (aggregation_total_bad_sig_loss),
    942                           &TALER_ARL_USE_AB (aggregation_total_bad_sig_loss),
    943                           coin_value);
    944     qs = report_row_inconsistency ("deposit",
    945                                    rowid,
    946                                    "coin denomination signature invalid");
    947     if (0 > qs)
    948     {
    949       wcc->qs = qs;
    950       TALER_denom_sig_free (&coin.denom_sig);
    951       TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    952       return;
    953     }
    954   }
    955   TALER_denom_sig_free (&coin.denom_sig);
    956   GNUNET_assert (NULL != issue); /* mostly to help static analysis */
    957   /* Check transaction history to see if it supports aggregate
    958      valuation */
    959   qs = check_transaction_history_for_deposit (
    960     coin_pub,
    961     h_contract_terms,
    962     merchant_pub,
    963     issue,
    964     tl,
    965     &computed_value,
    966     &total_deposit_without_refunds);
    967   if (0 > qs)
    968   {
    969     TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    970     wcc->qs = qs;
    971     return;
    972   }
    973   TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    974   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    975               "Coin contributes %s to aggregate (deposits after fees and refunds)\n",
    976               TALER_amount2s (&computed_value));
    977   {
    978     struct TALER_Amount coin_value_without_fee;
    979 
    980     if (TALER_ARL_SR_INVALID_NEGATIVE ==
    981         TALER_ARL_amount_subtract_neg (&coin_value_without_fee,
    982                                        coin_value,
    983                                        deposit_fee))
    984     {
    985       qs = report_amount_arithmetic_inconsistency (
    986         "aggregation (fee structure)",
    987         rowid,
    988         coin_value,
    989         deposit_fee,
    990         -1);
    991       if (0 > qs)
    992       {
    993         wcc->qs = qs;
    994         return;
    995       }
    996     }
    997     else if (0 !=
    998              TALER_amount_cmp (&total_deposit_without_refunds,
    999                                &coin_value_without_fee))
   1000     {
   1001       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1002                   "Expected coin contribution of %s to aggregate\n",
   1003                   TALER_amount2s (&coin_value_without_fee));
   1004       qs = report_amount_arithmetic_inconsistency (
   1005         "aggregation (contribution)",
   1006         rowid,
   1007         &coin_value_without_fee,
   1008         &total_deposit_without_refunds,
   1009         -1);
   1010       if (0 > qs)
   1011       {
   1012         wcc->qs = qs;
   1013         return;
   1014       }
   1015     }
   1016   }
   1017   /* Check other details of wire transfer match */
   1018   if (0 != TALER_full_payto_cmp (account_pay_uri,
   1019                                  wcc->payto_uri))
   1020   {
   1021     qs = report_row_inconsistency ("aggregation",
   1022                                    rowid,
   1023                                    "target of outgoing wire transfer do not match hash of wire from deposit");
   1024     if (0 > qs)
   1025     {
   1026       wcc->qs = qs;
   1027       return;
   1028     }
   1029   }
   1030   if (GNUNET_TIME_timestamp_cmp (exec_time,
   1031                                  !=,
   1032                                  wcc->date))
   1033   {
   1034     /* This should be impossible from database constraints */
   1035     GNUNET_break (0);
   1036     qs = report_row_inconsistency ("aggregation",
   1037                                    rowid,
   1038                                    "date given in aggregate does not match wire transfer date");
   1039     if (0 > qs)
   1040     {
   1041       wcc->qs = qs;
   1042       return;
   1043     }
   1044   }
   1045 
   1046   /* Add coin's contribution to total aggregate value */
   1047   {
   1048     struct TALER_Amount res;
   1049 
   1050     TALER_ARL_amount_add (&res,
   1051                           &wcc->total_deposits,
   1052                           &computed_value);
   1053     wcc->total_deposits = res;
   1054   }
   1055 }
   1056 
   1057 
   1058 /**
   1059  * Lookup the wire fee that the exchange charges at @a timestamp.
   1060  *
   1061  * @param ac context for caching the result
   1062  * @param method method of the wire plugin
   1063  * @param timestamp time for which we need the fee
   1064  * @return NULL on error (fee unknown)
   1065  */
   1066 static const struct TALER_Amount *
   1067 get_wire_fee (struct AggregationContext *ac,
   1068               const char *method,
   1069               struct GNUNET_TIME_Timestamp timestamp)
   1070 {
   1071   struct WireFeeInfo *wfi;
   1072   struct WireFeeInfo *pos;
   1073   struct TALER_MasterSignatureP master_sig;
   1074   enum GNUNET_DB_QueryStatus qs;
   1075   uint64_t rowid;
   1076 
   1077   /* Check if fee is already loaded in cache */
   1078   for (pos = ac->fee_head; NULL != pos; pos = pos->next)
   1079   {
   1080     if (GNUNET_TIME_timestamp_cmp (pos->start_date,
   1081                                    <=,
   1082                                    timestamp) &&
   1083         GNUNET_TIME_timestamp_cmp (pos->end_date,
   1084                                    >,
   1085                                    timestamp))
   1086       return &pos->fees.wire;
   1087     if (GNUNET_TIME_timestamp_cmp (pos->start_date,
   1088                                    >,
   1089                                    timestamp))
   1090       break;
   1091   }
   1092 
   1093   /* Lookup fee in exchange database */
   1094   wfi = GNUNET_new (struct WireFeeInfo);
   1095   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
   1096       TALER_EXCHANGEDB_get_wire_fee (TALER_ARL_edb,
   1097                                      method,
   1098                                      timestamp,
   1099                                      &rowid,
   1100                                      &wfi->start_date,
   1101                                      &wfi->end_date,
   1102                                      &wfi->fees,
   1103                                      &master_sig))
   1104   {
   1105     GNUNET_break (0);
   1106     GNUNET_free (wfi);
   1107     return NULL;
   1108   }
   1109 
   1110   /* Check signature. (This is not terribly meaningful as the exchange can
   1111      easily make this one up, but it means that we have proof that the master
   1112      key was used for inconsistent wire fees if a merchant complains.) */
   1113   if (GNUNET_OK !=
   1114       TALER_exchange_offline_wire_fee_verify (
   1115         method,
   1116         wfi->start_date,
   1117         wfi->end_date,
   1118         &wfi->fees,
   1119         &TALER_ARL_master_pub,
   1120         &master_sig))
   1121   {
   1122     ac->qs = report_row_inconsistency ("wire-fee",
   1123                                        timestamp.abs_time.abs_value_us,
   1124                                        "wire fee signature invalid at given time");
   1125     /* Note: continue with the fee despite the signature
   1126        being invalid here; hopefully it is really only the
   1127        signature that is bad ... */
   1128   }
   1129 
   1130   /* Established fee, keep in sorted list */
   1131   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1132               "Wire fee is %s starting at %s\n",
   1133               TALER_amount2s (&wfi->fees.wire),
   1134               GNUNET_TIME_timestamp2s (wfi->start_date));
   1135   if ((NULL == pos) ||
   1136       (NULL == pos->prev))
   1137     GNUNET_CONTAINER_DLL_insert (ac->fee_head,
   1138                                  ac->fee_tail,
   1139                                  wfi);
   1140   else
   1141     GNUNET_CONTAINER_DLL_insert_after (ac->fee_head,
   1142                                        ac->fee_tail,
   1143                                        pos->prev,
   1144                                        wfi);
   1145   /* Check non-overlaping fee invariant */
   1146   if ((NULL != wfi->prev) &&
   1147       GNUNET_TIME_timestamp_cmp (wfi->prev->end_date,
   1148                                  >,
   1149                                  wfi->start_date))
   1150   {
   1151     struct TALER_AUDITORDB_FeeTimeInconsistency ftib = {
   1152       .problem_row_id = rowid,
   1153       .diagnostic = (char *) "start date before previous end date",
   1154       .time = wfi->start_date.abs_time,
   1155       .type = (char *) method
   1156     };
   1157 
   1158     qs = TALER_AUDITORDB_insert_fee_time_inconsistency (
   1159       TALER_ARL_adb,
   1160       &ftib);
   1161     if (qs < 0)
   1162     {
   1163       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1164       ac->qs = qs;
   1165       return NULL;
   1166     }
   1167   }
   1168   if ((NULL != wfi->next) &&
   1169       GNUNET_TIME_timestamp_cmp (wfi->next->start_date,
   1170                                  <,
   1171                                  wfi->end_date))
   1172   {
   1173     struct TALER_AUDITORDB_FeeTimeInconsistency ftia = {
   1174       .problem_row_id = rowid,
   1175       .diagnostic = (char *) "end date after next start date",
   1176       .time = wfi->end_date.abs_time,
   1177       .type = (char *) method
   1178     };
   1179 
   1180     qs = TALER_AUDITORDB_insert_fee_time_inconsistency (
   1181       TALER_ARL_adb,
   1182       &ftia);
   1183 
   1184     if (qs < 0)
   1185     {
   1186       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1187       ac->qs = qs;
   1188       return NULL;
   1189     }
   1190   }
   1191   return &wfi->fees.wire;
   1192 }
   1193 
   1194 
   1195 /**
   1196  * Check that a wire transfer made by the exchange is valid
   1197  * (has matching deposits).
   1198  *
   1199  * @param ac a `struct AggregationContext`
   1200  * @param rowid identifier of the respective row in the database
   1201  * @param date timestamp of the wire transfer (roughly)
   1202  * @param wtid wire transfer subject
   1203  * @param payto_uri bank account details of the receiver
   1204  * @param amount amount that was wired
   1205  * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop iteration
   1206  */
   1207 static enum GNUNET_GenericReturnValue
   1208 check_wire_out_cb (struct AggregationContext *ac,
   1209                    uint64_t rowid,
   1210                    struct GNUNET_TIME_Timestamp date,
   1211                    const struct TALER_WireTransferIdentifierRawP *wtid,
   1212                    const struct TALER_FullPayto payto_uri,
   1213                    const struct TALER_Amount *amount)
   1214 {
   1215   struct WireCheckContext wcc;
   1216   struct TALER_Amount final_amount;
   1217   struct TALER_Amount exchange_gain;
   1218   enum GNUNET_DB_QueryStatus qs;
   1219   char *method;
   1220 
   1221   /* should be monotonically increasing */
   1222   GNUNET_assert (rowid >=
   1223                  TALER_ARL_USE_PP (aggregation_last_wire_out_serial_id));
   1224   TALER_ARL_USE_PP (aggregation_last_wire_out_serial_id) = rowid + 1;
   1225 
   1226   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1227               "Checking wire transfer %s over %s performed on %s\n",
   1228               TALER_B2S (wtid),
   1229               TALER_amount2s (amount),
   1230               GNUNET_TIME_timestamp2s (date));
   1231   if (NULL == (method = TALER_payto_get_method (payto_uri.full_payto)))
   1232   {
   1233     qs = report_row_inconsistency ("wire_out",
   1234                                    rowid,
   1235                                    "specified wire address lacks method");
   1236     if (0 > qs)
   1237       ac->qs = qs;
   1238     return GNUNET_OK;
   1239   }
   1240 
   1241   wcc.ac = ac;
   1242   wcc.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
   1243   wcc.date = date;
   1244   GNUNET_assert (GNUNET_OK ==
   1245                  TALER_amount_set_zero (amount->currency,
   1246                                         &wcc.total_deposits));
   1247   wcc.payto_uri = payto_uri;
   1248   qs = TALER_EXCHANGEDB_iterate_wire_transfers (TALER_ARL_edb,
   1249                                                 wtid,
   1250                                                 &wire_transfer_information_cb,
   1251                                                 &wcc);
   1252   if (0 > qs)
   1253   {
   1254     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1255     ac->qs = qs;
   1256     GNUNET_free (method);
   1257     return GNUNET_SYSERR;
   1258   }
   1259   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != wcc.qs)
   1260   {
   1261     /* Note: detailed information was already logged
   1262        in #wire_transfer_information_cb, so here we
   1263        only log for debugging */
   1264     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1265                 "Inconsistency for wire_out %llu (WTID %s) detected\n",
   1266                 (unsigned long long) rowid,
   1267                 TALER_B2S (wtid));
   1268   }
   1269 
   1270 
   1271   /* Subtract aggregation fee from total (if possible) */
   1272   {
   1273     const struct TALER_Amount *wire_fee;
   1274 
   1275     wire_fee = get_wire_fee (ac,
   1276                              method,
   1277                              date);
   1278     if (0 > ac->qs)
   1279     {
   1280       GNUNET_free (method);
   1281       return GNUNET_SYSERR;
   1282     }
   1283     if (NULL == wire_fee)
   1284     {
   1285       qs = report_row_inconsistency ("wire-fee",
   1286                                      date.abs_time.abs_value_us,
   1287                                      "wire fee unavailable for given time");
   1288       if (qs < 0)
   1289       {
   1290         ac->qs = qs;
   1291         GNUNET_free (method);
   1292         return GNUNET_SYSERR;
   1293       }
   1294       /* If fee is unknown, we just assume the fee is zero */
   1295       final_amount = wcc.total_deposits;
   1296     }
   1297     else if (TALER_ARL_SR_INVALID_NEGATIVE ==
   1298              TALER_ARL_amount_subtract_neg (&final_amount,
   1299                                             &wcc.total_deposits,
   1300                                             wire_fee))
   1301     {
   1302       qs = report_amount_arithmetic_inconsistency (
   1303         "wire out (fee structure)",
   1304         rowid,
   1305         &wcc.total_deposits,
   1306         wire_fee,
   1307         -1);
   1308       /* If fee arithmetic fails, we just assume the fee is zero */
   1309       if (0 > qs)
   1310       {
   1311         ac->qs = qs;
   1312         GNUNET_free (method);
   1313         return GNUNET_SYSERR;
   1314       }
   1315       final_amount = wcc.total_deposits;
   1316     }
   1317   }
   1318   GNUNET_free (method);
   1319 
   1320   /* Round down to amount supported by wire method */
   1321   GNUNET_break (GNUNET_SYSERR !=
   1322                 TALER_amount_round_down (&final_amount,
   1323                                          &TALER_ARL_currency_round_unit));
   1324 
   1325   /* Calculate the exchange's gain as the fees plus rounding differences! */
   1326   TALER_ARL_amount_subtract (&exchange_gain,
   1327                              &wcc.total_deposits,
   1328                              &final_amount);
   1329 
   1330   /* Sum up aggregation fees (we simply include the rounding gains) */
   1331   TALER_ARL_amount_add (&TALER_ARL_USE_AB (aggregation_total_wire_fee_revenue),
   1332                         &TALER_ARL_USE_AB (aggregation_total_wire_fee_revenue),
   1333                         &exchange_gain);
   1334 
   1335   /* Check that calculated amount matches actual amount */
   1336   if (0 != TALER_amount_cmp (amount,
   1337                              &final_amount))
   1338   {
   1339     struct TALER_Amount delta;
   1340 
   1341     if (0 < TALER_amount_cmp (amount,
   1342                               &final_amount))
   1343     {
   1344       /* amount > final_amount */
   1345       TALER_ARL_amount_subtract (&delta,
   1346                                  amount,
   1347                                  &final_amount);
   1348       TALER_ARL_amount_add (&TALER_ARL_USE_AB (
   1349                               aggregation_total_wire_out_delta_plus),
   1350                             &TALER_ARL_USE_AB (
   1351                               aggregation_total_wire_out_delta_plus),
   1352                             &delta);
   1353     }
   1354     else
   1355     {
   1356       /* amount < final_amount */
   1357       TALER_ARL_amount_subtract (&delta,
   1358                                  &final_amount,
   1359                                  amount);
   1360       TALER_ARL_amount_add (&TALER_ARL_USE_AB (
   1361                               aggregation_total_wire_out_delta_minus),
   1362                             &TALER_ARL_USE_AB (
   1363                               aggregation_total_wire_out_delta_minus),
   1364                             &delta);
   1365     }
   1366 
   1367     {
   1368       struct TALER_AUDITORDB_WireOutInconsistency woi = {
   1369         .destination_account = payto_uri,
   1370         .diagnostic = (char *) "aggregated amount does not match expectations",
   1371         .wire_out_row_id = rowid,
   1372         .expected = final_amount,
   1373         .claimed = *amount
   1374       };
   1375 
   1376       qs = TALER_AUDITORDB_insert_wire_out_inconsistency (
   1377         TALER_ARL_adb,
   1378         &woi);
   1379 
   1380       if (qs < 0)
   1381       {
   1382         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1383         ac->qs = qs;
   1384         return GNUNET_SYSERR;
   1385       }
   1386     }
   1387     return GNUNET_OK;
   1388   }
   1389   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1390               "Aggregation unit %s is OK\n",
   1391               TALER_B2S (wtid));
   1392   return GNUNET_OK;
   1393 }
   1394 
   1395 
   1396 /**
   1397  * Analyze the exchange aggregator's payment processing.
   1398  *
   1399  * @param cls closure
   1400  * @return transaction status code
   1401  */
   1402 static enum GNUNET_DB_QueryStatus
   1403 analyze_aggregations (void *cls)
   1404 {
   1405   struct AggregationContext ac;
   1406   struct WireFeeInfo *wfi;
   1407   enum GNUNET_DB_QueryStatus qs;
   1408 
   1409   (void) cls;
   1410   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1411               "Analyzing aggregations\n");
   1412   qs = TALER_AUDITORDB_get_auditor_progress (
   1413     TALER_ARL_adb,
   1414     TALER_ARL_GET_PP (aggregation_last_wire_out_serial_id),
   1415     NULL);
   1416   if (0 > qs)
   1417   {
   1418     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1419     return qs;
   1420   }
   1421   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
   1422   {
   1423     GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
   1424                 "First analysis using this auditor, starting audit from scratch\n");
   1425   }
   1426   else
   1427   {
   1428     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1429                 "Resuming aggregation audit at %llu\n",
   1430                 (unsigned long long) TALER_ARL_USE_PP (
   1431                   aggregation_last_wire_out_serial_id));
   1432   }
   1433 
   1434   memset (&ac,
   1435           0,
   1436           sizeof (ac));
   1437   qs = TALER_AUDITORDB_get_balance (
   1438     TALER_ARL_adb,
   1439     TALER_ARL_GET_AB (aggregation_total_wire_fee_revenue),
   1440     TALER_ARL_GET_AB (aggregation_total_arithmetic_delta_plus),
   1441     TALER_ARL_GET_AB (aggregation_total_arithmetic_delta_minus),
   1442     TALER_ARL_GET_AB (aggregation_total_bad_sig_loss),
   1443     TALER_ARL_GET_AB (aggregation_total_wire_out_delta_plus),
   1444     TALER_ARL_GET_AB (aggregation_total_wire_out_delta_minus),
   1445     TALER_ARL_GET_AB (aggregation_total_coin_delta_plus),
   1446     TALER_ARL_GET_AB (aggregation_total_coin_delta_minus),
   1447     NULL);
   1448   if (0 > qs)
   1449   {
   1450     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1451     return qs;
   1452   }
   1453 
   1454   ac.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
   1455   qs = TALER_EXCHANGEDB_iterate_wire_outs_above_serial_id (
   1456     TALER_ARL_edb,
   1457     TALER_ARL_USE_PP (aggregation_last_wire_out_serial_id),
   1458     &check_wire_out_cb,
   1459     &ac);
   1460   if (0 > qs)
   1461   {
   1462     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1463     ac.qs = qs;
   1464   }
   1465   while (NULL != (wfi = ac.fee_head))
   1466   {
   1467     GNUNET_CONTAINER_DLL_remove (ac.fee_head,
   1468                                  ac.fee_tail,
   1469                                  wfi);
   1470     GNUNET_free (wfi);
   1471   }
   1472   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
   1473   {
   1474     /* there were no wire out entries to be looked at, we are done */
   1475     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1476                 "No wire out entries found\n");
   1477     return qs;
   1478   }
   1479   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != ac.qs)
   1480   {
   1481     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == ac.qs);
   1482     return ac.qs;
   1483   }
   1484   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1485               "Finished aggregation audit at %llu\n",
   1486               (unsigned long long) TALER_ARL_USE_PP (
   1487                 aggregation_last_wire_out_serial_id));
   1488   qs = TALER_AUDITORDB_insert_balance (
   1489     TALER_ARL_adb,
   1490     TALER_ARL_SET_AB (aggregation_total_wire_fee_revenue),
   1491     TALER_ARL_SET_AB (aggregation_total_arithmetic_delta_plus),
   1492     TALER_ARL_SET_AB (aggregation_total_arithmetic_delta_minus),
   1493     TALER_ARL_SET_AB (aggregation_total_bad_sig_loss),
   1494     TALER_ARL_SET_AB (aggregation_total_wire_out_delta_plus),
   1495     TALER_ARL_SET_AB (aggregation_total_wire_out_delta_minus),
   1496     TALER_ARL_SET_AB (aggregation_total_coin_delta_plus),
   1497     TALER_ARL_SET_AB (aggregation_total_coin_delta_minus),
   1498     NULL);
   1499   if (0 > qs)
   1500   {
   1501     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1502                 "Failed to update auditor DB, not recording progress\n");
   1503     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1504     return qs;
   1505   }
   1506 
   1507   qs = TALER_AUDITORDB_insert_auditor_progress (
   1508     TALER_ARL_adb,
   1509     TALER_ARL_SET_PP (aggregation_last_wire_out_serial_id),
   1510     NULL);
   1511   if (0 > qs)
   1512   {
   1513     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1514                 "Failed to update auditor DB, not recording progress\n");
   1515     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
   1516     return qs;
   1517   }
   1518   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1519               "Concluded aggregation audit step at %llu\n",
   1520               (unsigned long long) TALER_ARL_USE_PP (
   1521                 aggregation_last_wire_out_serial_id));
   1522 
   1523   return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
   1524 }
   1525 
   1526 
   1527 /**
   1528  * Function called on events received from Postgres.
   1529  *
   1530  * @param cls closure, NULL
   1531  * @param extra additional event data provided
   1532  * @param extra_size number of bytes in @a extra
   1533  */
   1534 static void
   1535 db_notify (void *cls,
   1536            const void *extra,
   1537            size_t extra_size)
   1538 {
   1539   (void) cls;
   1540   (void) extra;
   1541   (void) extra_size;
   1542   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1543               "Received notification to wake aggregation helper\n");
   1544   if (GNUNET_OK !=
   1545       TALER_ARL_setup_sessions_and_run (&analyze_aggregations,
   1546                                         NULL))
   1547   {
   1548     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1549                 "Audit failed\n");
   1550     GNUNET_SCHEDULER_shutdown ();
   1551     global_ret = EXIT_FAILURE;
   1552     return;
   1553   }
   1554 }
   1555 
   1556 
   1557 /**
   1558  * Function called on shutdown.
   1559  */
   1560 static void
   1561 do_shutdown (void *cls)
   1562 {
   1563   (void) cls;
   1564   if (NULL != eh)
   1565   {
   1566     TALER_AUDITORDB_event_listen_cancel (eh);
   1567     eh = NULL;
   1568   }
   1569   TALER_ARL_done ();
   1570 }
   1571 
   1572 
   1573 /**
   1574  * Main function that will be run.
   1575  *
   1576  * @param cls closure
   1577  * @param args remaining command-line arguments
   1578  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
   1579  * @param c configuration
   1580  */
   1581 static void
   1582 run (void *cls,
   1583      char *const *args,
   1584      const char *cfgfile,
   1585      const struct GNUNET_CONFIGURATION_Handle *c)
   1586 {
   1587   (void) cls;
   1588   (void) args;
   1589   (void) cfgfile;
   1590 
   1591   cfg = c;
   1592   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
   1593                                  NULL);
   1594   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1595               "Launching aggregation auditor\n");
   1596   if (GNUNET_OK !=
   1597       TALER_ARL_init (c))
   1598   {
   1599     global_ret = EXIT_FAILURE;
   1600     return;
   1601   }
   1602 
   1603   if (test_mode != 1)
   1604   {
   1605     struct GNUNET_DB_EventHeaderP es = {
   1606       .size = htons (sizeof (es)),
   1607       .type = htons (TALER_DBEVENT_EXCHANGE_AUDITOR_WAKE_HELPER_AGGREGATION)
   1608     };
   1609 
   1610     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1611                 "Running helper indefinitely\n");
   1612     eh = TALER_AUDITORDB_event_listen (TALER_ARL_adb,
   1613                                        &es,
   1614                                        GNUNET_TIME_UNIT_FOREVER_REL,
   1615                                        &db_notify,
   1616                                        NULL);
   1617   }
   1618   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1619               "Starting audit\n");
   1620   if (GNUNET_OK !=
   1621       TALER_ARL_setup_sessions_and_run (&analyze_aggregations,
   1622                                         NULL))
   1623   {
   1624     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1625                 "Audit failed\n");
   1626     GNUNET_SCHEDULER_shutdown ();
   1627     global_ret = EXIT_FAILURE;
   1628     return;
   1629   }
   1630 }
   1631 
   1632 
   1633 /**
   1634  * The main function to audit the exchange's aggregation processing.
   1635  *
   1636  * @param argc number of arguments from the command line
   1637  * @param argv command line arguments
   1638  * @return 0 ok, 1 on error
   1639  */
   1640 int
   1641 main (int argc,
   1642       char *const *argv)
   1643 {
   1644   const struct GNUNET_GETOPT_CommandLineOption options[] = {
   1645     GNUNET_GETOPT_option_flag ('i',
   1646                                "internal",
   1647                                "perform checks only applicable for exchange-internal audits",
   1648                                &internal_checks),
   1649     GNUNET_GETOPT_option_flag ('t',
   1650                                "test",
   1651                                "run in test mode and exit when idle",
   1652                                &test_mode),
   1653     GNUNET_GETOPT_option_timetravel ('T',
   1654                                      "timetravel"),
   1655     GNUNET_GETOPT_OPTION_END
   1656   };
   1657   enum GNUNET_GenericReturnValue ret;
   1658 
   1659   ret = GNUNET_PROGRAM_run (
   1660     TALER_AUDITOR_project_data (),
   1661     argc,
   1662     argv,
   1663     "taler-helper-auditor-aggregation",
   1664     gettext_noop ("Audit Taler exchange aggregation activity"),
   1665     options,
   1666     &run,
   1667     NULL);
   1668   if (GNUNET_SYSERR == ret)
   1669     return EXIT_INVALIDARGUMENT;
   1670   if (GNUNET_NO == ret)
   1671     return EXIT_SUCCESS;
   1672   return global_ret;
   1673 }
   1674 
   1675 
   1676 /* end of taler-helper-auditor-aggregation.c */