LCOV - code coverage report
Current view: top level - auditor - taler-helper-auditor-aggregation.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 241 470 51.3 %
Date: 2025-06-05 21:03:14 Functions: 10 12 83.3 %

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

Generated by: LCOV version 1.16