LCOV - code coverage report
Current view: top level - auditor - taler-helper-auditor-aggregation.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 10.6 % 444 47
Test Date: 2026-09-09 15:11:34 Functions: 33.3 % 12 4

            Line data    Source code
       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            0 : 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            0 :   if (0 < TALER_amount_cmp (exchange,
     149              :                             auditor))
     150              :   {
     151              :     /* exchange > auditor */
     152            0 :     TALER_ARL_amount_subtract (&delta,
     153              :                                exchange,
     154              :                                auditor);
     155              :   }
     156              :   else
     157              :   {
     158              :     /* exchange <= auditor */
     159            0 :     profitable = -profitable;
     160            0 :     TALER_ARL_amount_subtract (&delta,
     161              :                                auditor,
     162              :                                exchange);
     163              :   }
     164              : 
     165              :   {
     166            0 :     struct TALER_AUDITORDB_AmountArithmeticInconsistency aai = {
     167              :       .problem_row_id = rowid,
     168            0 :       .profitable = profitable,
     169              :       .operation = (char *) operation,
     170              :       .exchange_amount = *exchange,
     171              :       .auditor_amount = *auditor
     172              :     };
     173              :     enum GNUNET_DB_QueryStatus qs;
     174              : 
     175            0 :     qs = TALER_AUDITORDB_insert_amount_arithmetic_inconsistency (
     176              :       TALER_ARL_adb,
     177              :       &aai);
     178              : 
     179            0 :     if (qs < 0)
     180              :     {
     181            0 :       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     182            0 :       return qs;
     183              :     }
     184              :   }
     185            0 :   if (0 != profitable)
     186              :   {
     187            0 :     target = (1 == profitable)
     188              :       ? &TALER_ARL_USE_AB (aggregation_total_arithmetic_delta_plus)
     189            0 :       : &TALER_ARL_USE_AB (aggregation_total_arithmetic_delta_minus);
     190            0 :     TALER_ARL_amount_add (target,
     191              :                           target,
     192              :                           &delta);
     193              :   }
     194            0 :   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            0 : 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            0 :   if (0 < TALER_amount_cmp (exchange,
     224              :                             auditor))
     225              :   {
     226              :     /* exchange > auditor */
     227            0 :     TALER_ARL_amount_subtract (&delta,
     228              :                                exchange,
     229              :                                auditor);
     230              :   }
     231              :   else
     232              :   {
     233              :     /* exchange <= auditor */
     234            0 :     profitable = -profitable;
     235            0 :     TALER_ARL_amount_subtract (&delta,
     236              :                                auditor,
     237              :                                exchange);
     238              :   }
     239              : 
     240              :   {
     241              :     enum GNUNET_DB_QueryStatus qs;
     242            0 :     struct TALER_AUDITORDB_CoinInconsistency ci = {
     243              :       .operation = (char *) operation,
     244              :       .auditor_amount = *auditor,
     245              :       .exchange_amount = *exchange,
     246            0 :       .profitable = profitable,
     247              :       .coin_pub = coin_pub->eddsa_pub
     248              :     };
     249              : 
     250            0 :     qs = TALER_AUDITORDB_insert_coin_inconsistency (
     251              :       TALER_ARL_adb,
     252              :       &ci);
     253              : 
     254            0 :     if (qs < 0)
     255              :     {
     256            0 :       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     257            0 :       return qs;
     258              :     }
     259              :   }
     260            0 :   if (0 != profitable)
     261              :   {
     262            0 :     target = (1 == profitable)
     263              :       ? &TALER_ARL_USE_AB (aggregation_total_coin_delta_plus)
     264            0 :       : &TALER_ARL_USE_AB (aggregation_total_coin_delta_minus);
     265            0 :     TALER_ARL_amount_add (target,
     266              :                           target,
     267              :                           &delta);
     268              :   }
     269            0 :   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            0 : report_row_inconsistency (const char *table,
     283              :                           uint64_t rowid,
     284              :                           const char *diagnostic)
     285              : {
     286              :   enum GNUNET_DB_QueryStatus qs;
     287            0 :   struct TALER_AUDITORDB_RowInconsistency ri = {
     288              :     .diagnostic = (char *) diagnostic,
     289              :     .row_table = (char *) table,
     290              :     .row_id = rowid
     291              :   };
     292              : 
     293            0 :   qs = TALER_AUDITORDB_insert_row_inconsistency (
     294              :     TALER_ARL_adb,
     295              :     &ri);
     296              : 
     297            0 :   if (qs < 0)
     298              :   {
     299            0 :     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     300            0 :     return qs;
     301              :   }
     302            0 :   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            0 : 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            0 :   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            0 :   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     437              :               "Checking transaction history of coin %s\n",
     438              :               TALER_B2S (coin_pub));
     439            0 :   GNUNET_assert (GNUNET_OK ==
     440              :                  TALER_amount_set_zero (TALER_ARL_currency,
     441              :                                         &expenditures));
     442            0 :   GNUNET_assert (GNUNET_OK ==
     443              :                  TALER_amount_set_zero (TALER_ARL_currency,
     444              :                                         &refunds));
     445            0 :   GNUNET_assert (GNUNET_OK ==
     446              :                  TALER_amount_set_zero (TALER_ARL_currency,
     447              :                                         merchant_gain));
     448            0 :   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            0 :   deposit_fee = NULL;
     457            0 :   for (const struct TALER_EXCHANGEDB_TransactionList *tl = tl_head;
     458            0 :        NULL != tl;
     459            0 :        tl = tl->next)
     460              :   {
     461              :     const struct TALER_Amount *fee_claimed;
     462              : 
     463            0 :     switch (tl->type)
     464              :     {
     465            0 :     case TALER_EXCHANGEDB_TT_DEPOSIT:
     466              :       /* check wire and h_wire are consistent */
     467            0 :       if (NULL != deposited)
     468              :       {
     469            0 :         struct TALER_AUDITORDB_RowInconsistency ri = {
     470            0 :           .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            0 :         qs = TALER_AUDITORDB_insert_row_inconsistency (
     477              :           TALER_ARL_adb,
     478              :           &ri);
     479              : 
     480            0 :         if (qs < 0)
     481              :         {
     482            0 :           GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     483            0 :           return qs;
     484              :         }
     485              :       }
     486            0 :       deposited = &tl->details.deposit->amount_with_fee;       /* according to exchange*/
     487            0 :       fee_claimed = &tl->details.deposit->deposit_fee;       /* Fee according to exchange DB */
     488            0 :       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            0 :       if ((0 == GNUNET_memcmp (merchant_pub,
     494            0 :                                &tl->details.deposit->merchant_pub)) &&
     495            0 :           (0 == GNUNET_memcmp (h_contract_terms,
     496              :                                &tl->details.deposit->h_contract_terms)))
     497              :       {
     498              :         struct TALER_Amount amount_without_fee;
     499              : 
     500            0 :         TALER_ARL_amount_subtract (&amount_without_fee,
     501              :                                    deposited,
     502              :                                    fee_claimed);
     503            0 :         TALER_ARL_amount_add (merchant_gain,
     504              :                               merchant_gain,
     505              :                               &amount_without_fee);
     506            0 :         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     507              :                     "Detected applicable deposit of %s\n",
     508              :                     TALER_amount2s (&amount_without_fee));
     509            0 :         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            0 :       if (0 !=
     513            0 :           TALER_amount_cmp (&issue->fees.deposit,
     514              :                             fee_claimed))
     515              :       {
     516              :         /* Disagreement in fee structure between auditor and exchange DB! */
     517            0 :         qs = report_amount_arithmetic_inconsistency ("deposit fee",
     518              :                                                      0,
     519              :                                                      fee_claimed,
     520              :                                                      &issue->fees.deposit,
     521              :                                                      1);
     522            0 :         if (0 > qs)
     523            0 :           return qs;
     524              :       }
     525            0 :       break;
     526            0 :     case TALER_EXCHANGEDB_TT_MELT:
     527              :       {
     528              :         const struct TALER_Amount *amount_with_fee;
     529              : 
     530            0 :         amount_with_fee = &tl->details.melt->amount_with_fee;
     531            0 :         fee_claimed = &tl->details.melt->melt_fee;
     532            0 :         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            0 :         if (0 !=
     537            0 :             TALER_amount_cmp (&issue->fees.refresh,
     538              :                               fee_claimed))
     539              :         {
     540              :           /* Disagreement in fee structure between exchange and auditor */
     541            0 :           qs = report_amount_arithmetic_inconsistency ("melt fee",
     542              :                                                        0,
     543              :                                                        fee_claimed,
     544              :                                                        &issue->fees.refresh,
     545              :                                                        1);
     546            0 :           if (0 > qs)
     547            0 :             return qs;
     548              :         }
     549            0 :         break;
     550              :       }
     551            0 :     case TALER_EXCHANGEDB_TT_REFUND:
     552              :       {
     553              :         const struct TALER_Amount *amount_with_fee;
     554              : 
     555            0 :         amount_with_fee = &tl->details.refund->refund_amount;
     556            0 :         fee_claimed = &tl->details.refund->refund_fee;
     557            0 :         TALER_ARL_amount_add (&refunds,
     558              :                               &refunds,
     559              :                               amount_with_fee);
     560            0 :         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            0 :         if ((0 == GNUNET_memcmp (merchant_pub,
     566            0 :                                  &tl->details.refund->merchant_pub)) &&
     567            0 :             (0 == GNUNET_memcmp (h_contract_terms,
     568              :                                  &tl->details.refund->h_contract_terms)))
     569              :         {
     570            0 :           GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     571              :                       "Detected applicable refund of %s\n",
     572              :                       TALER_amount2s (amount_with_fee));
     573            0 :           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            0 :         if (0 !=
     579            0 :             TALER_amount_cmp (&issue->fees.refund,
     580              :                               fee_claimed))
     581              :         {
     582              :           /* Disagreement in fee structure between exchange and auditor! */
     583            0 :           qs = report_amount_arithmetic_inconsistency ("refund fee",
     584              :                                                        0,
     585              :                                                        fee_claimed,
     586              :                                                        &issue->fees.refund,
     587              :                                                        1);
     588            0 :           if (0 > qs)
     589            0 :             return qs;
     590              :         }
     591            0 :         break;
     592              :       }
     593            0 :     case TALER_EXCHANGEDB_TT_RECOUP_REFRESH_RECEIVER:
     594              :       {
     595              :         const struct TALER_Amount *amount_with_fee;
     596              : 
     597            0 :         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            0 :         TALER_ARL_amount_add (&refunds,
     602              :                               &refunds,
     603              :                               amount_with_fee);
     604            0 :         break;
     605              :       }
     606            0 :     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            0 :         amount_with_fee = &tl->details.recoup->value;
     613            0 :         TALER_ARL_amount_add (&expenditures,
     614              :                               &expenditures,
     615              :                               amount_with_fee);
     616            0 :         break;
     617              :       }
     618            0 :     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            0 :         amount_with_fee = &tl->details.recoup_refresh->value;
     625            0 :         TALER_ARL_amount_add (&expenditures,
     626              :                               &expenditures,
     627              :                               amount_with_fee);
     628            0 :         break;
     629              :       }
     630            0 :     case TALER_EXCHANGEDB_TT_PURSE_DEPOSIT:
     631              :       {
     632              :         const struct TALER_Amount *amount_with_fee;
     633              : 
     634            0 :         amount_with_fee = &tl->details.purse_deposit->amount;
     635            0 :         if (! tl->details.purse_deposit->refunded)
     636            0 :           TALER_ARL_amount_add (&expenditures,
     637              :                                 &expenditures,
     638              :                                 amount_with_fee);
     639            0 :         break;
     640              :       }
     641              : 
     642            0 :     case TALER_EXCHANGEDB_TT_PURSE_REFUND:
     643              :       {
     644              :         const struct TALER_Amount *amount_with_fee;
     645              : 
     646            0 :         amount_with_fee = &tl->details.purse_refund->refund_amount;
     647            0 :         fee_claimed = &tl->details.purse_refund->refund_fee;
     648            0 :         TALER_ARL_amount_add (&refunds,
     649              :                               &refunds,
     650              :                               amount_with_fee);
     651            0 :         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            0 :         if (0 !=
     656            0 :             TALER_amount_cmp (&issue->fees.refund,
     657              :                               fee_claimed))
     658              :         {
     659              :           /* Disagreement in fee structure between exchange and auditor! */
     660            0 :           qs = report_amount_arithmetic_inconsistency ("refund fee",
     661              :                                                        0,
     662              :                                                        fee_claimed,
     663              :                                                        &issue->fees.refund,
     664              :                                                        1);
     665            0 :           if (0 > qs)
     666            0 :             return qs;
     667              :         }
     668            0 :         break;
     669              :       }
     670              : 
     671            0 :     case TALER_EXCHANGEDB_TT_RESERVE_OPEN:
     672              :       {
     673              :         const struct TALER_Amount *amount_with_fee;
     674              : 
     675            0 :         amount_with_fee = &tl->details.reserve_open->coin_contribution;
     676            0 :         TALER_ARL_amount_add (&expenditures,
     677              :                               &expenditures,
     678              :                               amount_with_fee);
     679            0 :         break;
     680              :       }
     681              :     } /* switch (tl->type) */
     682              :   } /* for 'tl' */
     683              : 
     684            0 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     685              :               "Deposits for this aggregation (after fees) are %s\n",
     686              :               TALER_amount2s (merchant_gain));
     687            0 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     688              :               "Aggregation loss due to refunds is %s\n",
     689              :               TALER_amount2s (&merchant_loss));
     690            0 :   *deposit_gain = *merchant_gain;
     691            0 :   if ((NULL != deposited) &&
     692            0 :       (NULL != deposit_fee) &&
     693            0 :       (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            0 :     TALER_ARL_amount_add (merchant_gain,
     701              :                           merchant_gain,
     702              :                           deposit_fee);
     703              :   }
     704              :   {
     705              :     struct TALER_Amount final_gain;
     706              : 
     707            0 :     if (TALER_ARL_SR_INVALID_NEGATIVE ==
     708            0 :         TALER_ARL_amount_subtract_neg (&final_gain,
     709              :                                        merchant_gain,
     710              :                                        &merchant_loss))
     711              :     {
     712              :       /* refunds above deposits? Bad! */
     713            0 :       qs = report_coin_arithmetic_inconsistency ("refund (merchant)",
     714              :                                                  coin_pub,
     715              :                                                  merchant_gain,
     716              :                                                  &merchant_loss,
     717              :                                                  1);
     718            0 :       if (0 > qs)
     719            0 :         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            0 :       GNUNET_assert (GNUNET_OK ==
     724              :                      TALER_amount_set_zero (TALER_ARL_currency,
     725              :                                             merchant_gain));
     726              :     }
     727              :     else
     728              :     {
     729            0 :       *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            0 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     737              :               "Subtracting refunds of %s from coin value loss\n",
     738              :               TALER_amount2s (&refunds));
     739            0 :   if (TALER_ARL_SR_INVALID_NEGATIVE ==
     740            0 :       TALER_ARL_amount_subtract_neg (&spent,
     741              :                                      &expenditures,
     742              :                                      &refunds))
     743              :   {
     744              :     /* refunds above expenditures? Bad! */
     745            0 :     qs = report_coin_arithmetic_inconsistency ("refund (balance)",
     746              :                                                coin_pub,
     747              :                                                &expenditures,
     748              :                                                &refunds,
     749              :                                                1);
     750            0 :     if (0 > qs)
     751            0 :       return qs;
     752              :   }
     753              :   else
     754              :   {
     755              :     /* Now check that 'spent' is less or equal than the total coin value */
     756            0 :     if (1 == TALER_amount_cmp (&spent,
     757              :                                &issue->value))
     758              :     {
     759              :       /* spent > value */
     760            0 :       qs = report_coin_arithmetic_inconsistency ("spend",
     761              :                                                  coin_pub,
     762              :                                                  &spent,
     763              :                                                  &issue->value,
     764              :                                                  -1);
     765            0 :       if (0 > qs)
     766            0 :         return qs;
     767              :     }
     768              :   }
     769            0 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     770              :               "Final merchant gain after refunds is %s\n",
     771              :               TALER_amount2s (deposit_gain));
     772            0 :   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            0 :   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            0 : 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            0 :   if (0 > wcc->qs)
     826            0 :     return;
     827            0 :   TALER_full_payto_hash (account_pay_uri,
     828              :                          &hpt);
     829            0 :   if (0 !=
     830            0 :       GNUNET_memcmp (&hpt,
     831              :                      h_payto))
     832              :   {
     833            0 :     qs = report_row_inconsistency ("wire_targets",
     834              :                                    rowid,
     835              :                                    "h-payto does not match payto URI");
     836            0 :     if (0 > qs)
     837              :     {
     838            0 :       wcc->qs = qs;
     839            0 :       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            0 :     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            0 :   if (0 > qs)
     862              :   {
     863            0 :     wcc->qs = qs;
     864            0 :     TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     865            0 :     return;
     866              :   }
     867            0 :   if (NULL == tl)
     868              :   {
     869            0 :     qs = report_row_inconsistency ("aggregation",
     870              :                                    rowid,
     871              :                                    "no transaction history for coin claimed in aggregation");
     872            0 :     if (0 > qs)
     873            0 :       wcc->qs = qs;
     874            0 :     return;
     875              :   }
     876            0 :   qs = TALER_EXCHANGEDB_get_known_coin (TALER_ARL_edb,
     877              :                                         coin_pub,
     878              :                                         &coin);
     879            0 :   if (0 > qs)
     880              :   {
     881            0 :     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     882            0 :     wcc->qs = qs;
     883            0 :     return;
     884              :   }
     885            0 :   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     886              :   {
     887              :     /* this should be a foreign key violation at this point! */
     888            0 :     qs = report_row_inconsistency ("aggregation",
     889              :                                    rowid,
     890              :                                    "could not get coin details for coin claimed in aggregation");
     891            0 :     if (0 > qs)
     892            0 :       wcc->qs = qs;
     893            0 :     TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     894            0 :     return;
     895              :   }
     896            0 :   qs = TALER_ARL_get_denomination_info_by_hash (&coin.denom_pub_hash,
     897              :                                                 &issue);
     898            0 :   if (0 > qs)
     899              :   {
     900            0 :     wcc->qs = qs;
     901            0 :     TALER_denom_sig_free (&coin.denom_sig);
     902            0 :     TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     903            0 :     return;
     904              :   }
     905            0 :   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     906              :   {
     907            0 :     TALER_denom_sig_free (&coin.denom_sig);
     908            0 :     TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     909            0 :     qs = report_row_inconsistency ("aggregation",
     910              :                                    rowid,
     911              :                                    "could not find denomination key for coin claimed in aggregation");
     912            0 :     if (0 > qs)
     913            0 :       wcc->qs = qs;
     914            0 :     return;
     915              :   }
     916            0 :   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     917              :               "Testing coin `%s' for validity\n",
     918              :               TALER_B2S (&coin.coin_pub));
     919            0 :   if (GNUNET_OK !=
     920            0 :       TALER_test_coin_valid (&coin,
     921              :                              denom_pub))
     922              :   {
     923            0 :     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            0 :     qs = TALER_AUDITORDB_insert_bad_sig_losses (
     931              :       TALER_ARL_adb,
     932              :       &bsl);
     933            0 :     if (qs < 0)
     934              :     {
     935            0 :       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     936            0 :       wcc->qs = qs;
     937            0 :       TALER_denom_sig_free (&coin.denom_sig);
     938            0 :       TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     939            0 :       return;
     940              :     }
     941            0 :     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            0 :     qs = report_row_inconsistency ("deposit",
     945              :                                    rowid,
     946              :                                    "coin denomination signature invalid");
     947            0 :     if (0 > qs)
     948              :     {
     949            0 :       wcc->qs = qs;
     950            0 :       TALER_denom_sig_free (&coin.denom_sig);
     951            0 :       TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     952            0 :       return;
     953              :     }
     954              :   }
     955            0 :   TALER_denom_sig_free (&coin.denom_sig);
     956            0 :   GNUNET_assert (NULL != issue); /* mostly to help static analysis */
     957              :   /* Check transaction history to see if it supports aggregate
     958              :      valuation */
     959            0 :   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            0 :   if (0 > qs)
     968              :   {
     969            0 :     TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     970            0 :     wcc->qs = qs;
     971            0 :     return;
     972              :   }
     973            0 :   TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     974            0 :   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            0 :     if (TALER_ARL_SR_INVALID_NEGATIVE ==
     981            0 :         TALER_ARL_amount_subtract_neg (&coin_value_without_fee,
     982              :                                        coin_value,
     983              :                                        deposit_fee))
     984              :     {
     985            0 :       qs = report_amount_arithmetic_inconsistency (
     986              :         "aggregation (fee structure)",
     987              :         rowid,
     988              :         coin_value,
     989              :         deposit_fee,
     990              :         -1);
     991            0 :       if (0 > qs)
     992              :       {
     993            0 :         wcc->qs = qs;
     994            0 :         return;
     995              :       }
     996              :     }
     997            0 :     else if (0 !=
     998            0 :              TALER_amount_cmp (&total_deposit_without_refunds,
     999              :                                &coin_value_without_fee))
    1000              :     {
    1001            0 :       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    1002              :                   "Expected coin contribution of %s to aggregate\n",
    1003              :                   TALER_amount2s (&coin_value_without_fee));
    1004            0 :       qs = report_amount_arithmetic_inconsistency (
    1005              :         "aggregation (contribution)",
    1006              :         rowid,
    1007              :         &coin_value_without_fee,
    1008              :         &total_deposit_without_refunds,
    1009              :         -1);
    1010            0 :       if (0 > qs)
    1011              :       {
    1012            0 :         wcc->qs = qs;
    1013            0 :         return;
    1014              :       }
    1015              :     }
    1016              :   }
    1017              :   /* Check other details of wire transfer match */
    1018            0 :   if (0 != TALER_full_payto_cmp (account_pay_uri,
    1019              :                                  wcc->payto_uri))
    1020              :   {
    1021            0 :     qs = report_row_inconsistency ("aggregation",
    1022              :                                    rowid,
    1023              :                                    "target of outgoing wire transfer do not match hash of wire from deposit");
    1024            0 :     if (0 > qs)
    1025              :     {
    1026            0 :       wcc->qs = qs;
    1027            0 :       return;
    1028              :     }
    1029              :   }
    1030            0 :   if (GNUNET_TIME_timestamp_cmp (exec_time,
    1031              :                                  !=,
    1032              :                                  wcc->date))
    1033              :   {
    1034              :     /* This should be impossible from database constraints */
    1035            0 :     GNUNET_break (0);
    1036            0 :     qs = report_row_inconsistency ("aggregation",
    1037              :                                    rowid,
    1038              :                                    "date given in aggregate does not match wire transfer date");
    1039            0 :     if (0 > qs)
    1040              :     {
    1041            0 :       wcc->qs = qs;
    1042            0 :       return;
    1043              :     }
    1044              :   }
    1045              : 
    1046              :   /* Add coin's contribution to total aggregate value */
    1047              :   {
    1048              :     struct TALER_Amount res;
    1049              : 
    1050            0 :     TALER_ARL_amount_add (&res,
    1051              :                           &wcc->total_deposits,
    1052              :                           &computed_value);
    1053            0 :     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            0 : 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            0 :   for (pos = ac->fee_head; NULL != pos; pos = pos->next)
    1079              :   {
    1080            0 :     if (GNUNET_TIME_timestamp_cmp (pos->start_date,
    1081              :                                    <=,
    1082            0 :                                    timestamp) &&
    1083            0 :         GNUNET_TIME_timestamp_cmp (pos->end_date,
    1084              :                                    >,
    1085              :                                    timestamp))
    1086            0 :       return &pos->fees.wire;
    1087            0 :     if (GNUNET_TIME_timestamp_cmp (pos->start_date,
    1088              :                                    >,
    1089              :                                    timestamp))
    1090            0 :       break;
    1091              :   }
    1092              : 
    1093              :   /* Lookup fee in exchange database */
    1094            0 :   wfi = GNUNET_new (struct WireFeeInfo);
    1095            0 :   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    1096            0 :       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            0 :     GNUNET_break (0);
    1106            0 :     GNUNET_free (wfi);
    1107            0 :     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            0 :   if (GNUNET_OK !=
    1114            0 :       TALER_exchange_offline_wire_fee_verify (
    1115              :         method,
    1116              :         wfi->start_date,
    1117              :         wfi->end_date,
    1118            0 :         &wfi->fees,
    1119              :         &TALER_ARL_master_pub,
    1120              :         &master_sig))
    1121              :   {
    1122            0 :     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            0 :   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            0 :   if ((NULL == pos) ||
    1136            0 :       (NULL == pos->prev))
    1137            0 :     GNUNET_CONTAINER_DLL_insert (ac->fee_head,
    1138              :                                  ac->fee_tail,
    1139              :                                  wfi);
    1140              :   else
    1141            0 :     GNUNET_CONTAINER_DLL_insert_after (ac->fee_head,
    1142              :                                        ac->fee_tail,
    1143              :                                        pos->prev,
    1144              :                                        wfi);
    1145              :   /* Check non-overlaping fee invariant */
    1146            0 :   if ((NULL != wfi->prev) &&
    1147            0 :       GNUNET_TIME_timestamp_cmp (wfi->prev->end_date,
    1148              :                                  >,
    1149              :                                  wfi->start_date))
    1150              :   {
    1151            0 :     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            0 :     qs = TALER_AUDITORDB_insert_fee_time_inconsistency (
    1159              :       TALER_ARL_adb,
    1160              :       &ftib);
    1161            0 :     if (qs < 0)
    1162              :     {
    1163            0 :       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    1164            0 :       ac->qs = qs;
    1165            0 :       return NULL;
    1166              :     }
    1167              :   }
    1168            0 :   if ((NULL != wfi->next) &&
    1169            0 :       GNUNET_TIME_timestamp_cmp (wfi->next->start_date,
    1170              :                                  <,
    1171              :                                  wfi->end_date))
    1172              :   {
    1173            0 :     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            0 :     qs = TALER_AUDITORDB_insert_fee_time_inconsistency (
    1181              :       TALER_ARL_adb,
    1182              :       &ftia);
    1183              : 
    1184            0 :     if (qs < 0)
    1185              :     {
    1186            0 :       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    1187            0 :       ac->qs = qs;
    1188            0 :       return NULL;
    1189              :     }
    1190              :   }
    1191            0 :   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            0 : 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            0 :   GNUNET_assert (rowid >=
    1223              :                  TALER_ARL_USE_PP (aggregation_last_wire_out_serial_id));
    1224            0 :   TALER_ARL_USE_PP (aggregation_last_wire_out_serial_id) = rowid + 1;
    1225              : 
    1226            0 :   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            0 :   if (NULL == (method = TALER_payto_get_method (payto_uri.full_payto)))
    1232              :   {
    1233            0 :     qs = report_row_inconsistency ("wire_out",
    1234              :                                    rowid,
    1235              :                                    "specified wire address lacks method");
    1236            0 :     if (0 > qs)
    1237            0 :       ac->qs = qs;
    1238            0 :     return GNUNET_OK;
    1239              :   }
    1240              : 
    1241            0 :   wcc.ac = ac;
    1242            0 :   wcc.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
    1243            0 :   wcc.date = date;
    1244            0 :   GNUNET_assert (GNUNET_OK ==
    1245              :                  TALER_amount_set_zero (amount->currency,
    1246              :                                         &wcc.total_deposits));
    1247            0 :   wcc.payto_uri = payto_uri;
    1248            0 :   qs = TALER_EXCHANGEDB_iterate_wire_transfers (TALER_ARL_edb,
    1249              :                                                 wtid,
    1250              :                                                 &wire_transfer_information_cb,
    1251              :                                                 &wcc);
    1252            0 :   if (0 > qs)
    1253              :   {
    1254            0 :     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    1255            0 :     ac->qs = qs;
    1256            0 :     GNUNET_free (method);
    1257            0 :     return GNUNET_SYSERR;
    1258              :   }
    1259            0 :   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            0 :     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            0 :     wire_fee = get_wire_fee (ac,
    1276              :                              method,
    1277              :                              date);
    1278            0 :     if (0 > ac->qs)
    1279              :     {
    1280            0 :       GNUNET_free (method);
    1281            0 :       return GNUNET_SYSERR;
    1282              :     }
    1283            0 :     if (NULL == wire_fee)
    1284              :     {
    1285            0 :       qs = report_row_inconsistency ("wire-fee",
    1286              :                                      date.abs_time.abs_value_us,
    1287              :                                      "wire fee unavailable for given time");
    1288            0 :       if (qs < 0)
    1289              :       {
    1290            0 :         ac->qs = qs;
    1291            0 :         GNUNET_free (method);
    1292            0 :         return GNUNET_SYSERR;
    1293              :       }
    1294              :       /* If fee is unknown, we just assume the fee is zero */
    1295            0 :       final_amount = wcc.total_deposits;
    1296              :     }
    1297            0 :     else if (TALER_ARL_SR_INVALID_NEGATIVE ==
    1298            0 :              TALER_ARL_amount_subtract_neg (&final_amount,
    1299              :                                             &wcc.total_deposits,
    1300              :                                             wire_fee))
    1301              :     {
    1302            0 :       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            0 :       if (0 > qs)
    1310              :       {
    1311            0 :         ac->qs = qs;
    1312            0 :         GNUNET_free (method);
    1313            0 :         return GNUNET_SYSERR;
    1314              :       }
    1315            0 :       final_amount = wcc.total_deposits;
    1316              :     }
    1317              :   }
    1318            0 :   GNUNET_free (method);
    1319              : 
    1320              :   /* Round down to amount supported by wire method */
    1321            0 :   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            0 :   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            0 :   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            0 :   if (0 != TALER_amount_cmp (amount,
    1337              :                              &final_amount))
    1338              :   {
    1339              :     struct TALER_Amount delta;
    1340              : 
    1341            0 :     if (0 < TALER_amount_cmp (amount,
    1342              :                               &final_amount))
    1343              :     {
    1344              :       /* amount > final_amount */
    1345            0 :       TALER_ARL_amount_subtract (&delta,
    1346              :                                  amount,
    1347              :                                  &final_amount);
    1348            0 :       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            0 :       TALER_ARL_amount_subtract (&delta,
    1358              :                                  &final_amount,
    1359              :                                  amount);
    1360            0 :       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            0 :       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            0 :       qs = TALER_AUDITORDB_insert_wire_out_inconsistency (
    1377              :         TALER_ARL_adb,
    1378              :         &woi);
    1379              : 
    1380            0 :       if (qs < 0)
    1381              :       {
    1382            0 :         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    1383            0 :         ac->qs = qs;
    1384            0 :         return GNUNET_SYSERR;
    1385              :       }
    1386              :     }
    1387            0 :     return GNUNET_OK;
    1388              :   }
    1389            0 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1390              :               "Aggregation unit %s is OK\n",
    1391              :               TALER_B2S (wtid));
    1392            0 :   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            4 : analyze_aggregations (void *cls)
    1404              : {
    1405              :   struct AggregationContext ac;
    1406              :   struct WireFeeInfo *wfi;
    1407              :   enum GNUNET_DB_QueryStatus qs;
    1408              : 
    1409              :   (void) cls;
    1410            4 :   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    1411              :               "Analyzing aggregations\n");
    1412            4 :   qs = TALER_AUDITORDB_get_auditor_progress (
    1413              :     TALER_ARL_adb,
    1414              :     TALER_ARL_GET_PP (aggregation_last_wire_out_serial_id),
    1415              :     NULL);
    1416            4 :   if (0 > qs)
    1417              :   {
    1418            0 :     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    1419            0 :     return qs;
    1420              :   }
    1421            4 :   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    1422              :   {
    1423            0 :     GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
    1424              :                 "First analysis using this auditor, starting audit from scratch\n");
    1425              :   }
    1426              :   else
    1427              :   {
    1428            4 :     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            4 :   memset (&ac,
    1435              :           0,
    1436              :           sizeof (ac));
    1437            4 :   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            4 :   if (0 > qs)
    1449              :   {
    1450            0 :     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    1451            0 :     return qs;
    1452              :   }
    1453              : 
    1454            4 :   ac.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
    1455            4 :   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            4 :   if (0 > qs)
    1461              :   {
    1462            0 :     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    1463            0 :     ac.qs = qs;
    1464              :   }
    1465            4 :   while (NULL != (wfi = ac.fee_head))
    1466              :   {
    1467            0 :     GNUNET_CONTAINER_DLL_remove (ac.fee_head,
    1468              :                                  ac.fee_tail,
    1469              :                                  wfi);
    1470            0 :     GNUNET_free (wfi);
    1471              :   }
    1472            4 :   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    1473              :   {
    1474              :     /* there were no wire out entries to be looked at, we are done */
    1475            4 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1476              :                 "No wire out entries found\n");
    1477            4 :     return qs;
    1478              :   }
    1479            0 :   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != ac.qs)
    1480              :   {
    1481            0 :     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == ac.qs);
    1482            0 :     return ac.qs;
    1483              :   }
    1484            0 :   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            0 :   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            0 :   if (0 > qs)
    1500              :   {
    1501            0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1502              :                 "Failed to update auditor DB, not recording progress\n");
    1503            0 :     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    1504            0 :     return qs;
    1505              :   }
    1506              : 
    1507            0 :   qs = TALER_AUDITORDB_insert_auditor_progress (
    1508              :     TALER_ARL_adb,
    1509              :     TALER_ARL_SET_PP (aggregation_last_wire_out_serial_id),
    1510              :     NULL);
    1511            0 :   if (0 > qs)
    1512              :   {
    1513            0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1514              :                 "Failed to update auditor DB, not recording progress\n");
    1515            0 :     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    1516            0 :     return qs;
    1517              :   }
    1518            0 :   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            0 :   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            0 : 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            0 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1543              :               "Received notification to wake aggregation helper\n");
    1544            0 :   if (GNUNET_OK !=
    1545            0 :       TALER_ARL_setup_sessions_and_run (&analyze_aggregations,
    1546              :                                         NULL))
    1547              :   {
    1548            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1549              :                 "Audit failed\n");
    1550            0 :     GNUNET_SCHEDULER_shutdown ();
    1551            0 :     global_ret = EXIT_FAILURE;
    1552            0 :     return;
    1553              :   }
    1554              : }
    1555              : 
    1556              : 
    1557              : /**
    1558              :  * Function called on shutdown.
    1559              :  */
    1560              : static void
    1561            4 : do_shutdown (void *cls)
    1562              : {
    1563              :   (void) cls;
    1564            4 :   if (NULL != eh)
    1565              :   {
    1566            4 :     TALER_AUDITORDB_event_listen_cancel (eh);
    1567            4 :     eh = NULL;
    1568              :   }
    1569            4 :   TALER_ARL_done ();
    1570            4 : }
    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            4 : 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            4 :   cfg = c;
    1592            4 :   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
    1593              :                                  NULL);
    1594            4 :   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    1595              :               "Launching aggregation auditor\n");
    1596            4 :   if (EXIT_SUCCESS !=
    1597            4 :       (global_ret = TALER_ARL_init (c)))
    1598              :   {
    1599            0 :     return;
    1600              :   }
    1601              : 
    1602            4 :   if (test_mode != 1)
    1603              :   {
    1604            4 :     struct GNUNET_DB_EventHeaderP es = {
    1605            4 :       .size = htons (sizeof (es)),
    1606            4 :       .type = htons (TALER_DBEVENT_EXCHANGE_AUDITOR_WAKE_HELPER_AGGREGATION)
    1607              :     };
    1608              : 
    1609            4 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1610              :                 "Running helper indefinitely\n");
    1611            4 :     eh = TALER_AUDITORDB_event_listen (TALER_ARL_adb,
    1612              :                                        &es,
    1613            4 :                                        GNUNET_TIME_UNIT_FOREVER_REL,
    1614              :                                        &db_notify,
    1615              :                                        NULL);
    1616              :   }
    1617            4 :   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    1618              :               "Starting audit\n");
    1619            4 :   if (GNUNET_OK !=
    1620            4 :       TALER_ARL_setup_sessions_and_run (&analyze_aggregations,
    1621              :                                         NULL))
    1622              :   {
    1623            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1624              :                 "Audit failed\n");
    1625            0 :     GNUNET_SCHEDULER_shutdown ();
    1626            0 :     global_ret = EXIT_FAILURE;
    1627            0 :     return;
    1628              :   }
    1629              : }
    1630              : 
    1631              : 
    1632              : /**
    1633              :  * The main function to audit the exchange's aggregation processing.
    1634              :  *
    1635              :  * @param argc number of arguments from the command line
    1636              :  * @param argv command line arguments
    1637              :  * @return 0 ok, 1 on error
    1638              :  */
    1639              : int
    1640            4 : main (int argc,
    1641              :       char *const *argv)
    1642              : {
    1643            4 :   const struct GNUNET_GETOPT_CommandLineOption options[] = {
    1644            4 :     GNUNET_GETOPT_option_flag ('i',
    1645              :                                "internal",
    1646              :                                "perform checks only applicable for exchange-internal audits",
    1647              :                                &internal_checks),
    1648            4 :     GNUNET_GETOPT_option_flag ('t',
    1649              :                                "test",
    1650              :                                "run in test mode and exit when idle",
    1651              :                                &test_mode),
    1652            4 :     GNUNET_GETOPT_option_timetravel ('T',
    1653              :                                      "timetravel"),
    1654              :     GNUNET_GETOPT_OPTION_END
    1655              :   };
    1656              :   enum GNUNET_GenericReturnValue ret;
    1657              : 
    1658            4 :   ret = GNUNET_PROGRAM_run (
    1659              :     TALER_AUDITOR_project_data (),
    1660              :     argc,
    1661              :     argv,
    1662              :     "taler-helper-auditor-aggregation",
    1663              :     gettext_noop ("Audit Taler exchange aggregation activity"),
    1664              :     options,
    1665              :     &run,
    1666              :     NULL);
    1667            4 :   if (GNUNET_SYSERR == ret)
    1668            0 :     return EXIT_NOTCONFIGURED;
    1669            4 :   if (GNUNET_NO == ret)
    1670            0 :     return EXIT_SUCCESS;
    1671            4 :   return global_ret;
    1672              : }
    1673              : 
    1674              : 
    1675              : /* end of taler-helper-auditor-aggregation.c */
        

Generated by: LCOV version 2.0-1