LCOV - code coverage report
Current view: top level - auditor - taler-helper-auditor-aggregation.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 42.9 % 445 191
Test Date: 2026-10-17 15:46:49 Functions: 66.7 % 12 8

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

Generated by: LCOV version 2.0-1