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


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