LCOV - code coverage report
Current view: top level - auditor - taler-helper-auditor-deposits.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 39.5 % 152 60
Test Date: 2026-01-18 12:54:31 Functions: 57.1 % 7 4

            Line data    Source code
       1              : /*
       2              :   This file is part of TALER
       3              :   Copyright (C) 2016-2025 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-deposits.c
      18              :  * @brief audits an exchange database for deposit confirmation consistency
      19              :  * @author Christian Grothoff
      20              :  * @author Nic Eigel
      21              :  *
      22              :  * We simply check that all of the deposit confirmations reported to us
      23              :  * by merchants were also reported to us by the exchange.
      24              :  */
      25              : #include "taler/platform.h"
      26              : #include <gnunet/gnunet_util_lib.h>
      27              : #include "taler/taler_auditordb_plugin.h"
      28              : #include "taler/taler_exchangedb_lib.h"
      29              : #include "taler/taler_bank_service.h"
      30              : #include "taler/taler_signatures.h"
      31              : #include "report-lib.h"
      32              : #include "taler/taler_dbevents.h"
      33              : #include <jansson.h>
      34              : #include <inttypes.h>
      35              : 
      36              : /*
      37              : --
      38              : -- SELECT serial_id,h_contract_terms,h_wire,merchant_pub ...
      39              : --   FROM auditor.auditor_deposit_confirmations
      40              : --   WHERE NOT ancient
      41              : --    ORDER BY exchange_timestamp ASC;
      42              : --  SELECT 1
      43              : -      FROM exchange.deposits dep
      44              :        WHERE ($RESULT.contract_terms = dep.h_contract_terms) AND ($RESULT.h_wire = dep.h_wire) AND ...);
      45              : -- IF FOUND
      46              : -- DELETE FROM auditor.auditor_deposit_confirmations
      47              : --   WHERE serial_id = $RESULT.serial_id;
      48              : -- SELECT exchange_timestamp AS latest
      49              : --   FROM exchange.deposits ORDER BY exchange_timestamp DESC;
      50              : -- latest -= 1 hour; // time is not exactly monotonic...
      51              : -- UPDATE auditor.deposit_confirmations
      52              : --   SET ancient=TRUE
      53              : --  WHERE exchange_timestamp < latest
      54              : --    AND NOT ancient;
      55              : */
      56              : 
      57              : /**
      58              :  * Return value from main().
      59              :  */
      60              : static int global_ret;
      61              : 
      62              : /**
      63              :  * Row ID until which we have added up missing deposit confirmations
      64              :  * in the total_missed_deposit_confirmations amount. Missing deposit
      65              :  * confirmations above this value need to be added, and if any appear
      66              :  * below this value we should subtract them from the reported amount.
      67              :  */
      68              : static TALER_ARL_DEF_PP (deposit_confirmation_serial_id);
      69              : 
      70              : /**
      71              :  * Run in test mode. Exit when idle instead of
      72              :  * going to sleep and waiting for more work.
      73              :  */
      74              : static int test_mode;
      75              : 
      76              : /**
      77              :  * Total amount involved in deposit confirmations that we did not get.
      78              :  */
      79              : static TALER_ARL_DEF_AB (total_missed_deposit_confirmations);
      80              : 
      81              : /**
      82              :  * Should we run checks that only work for exchange-internal audits?
      83              :  * Does nothing for this helper (present only for uniformity).
      84              :  */
      85              : static int internal_checks;
      86              : 
      87              : /**
      88              :  * Handler to wake us up on new deposit confirmations.
      89              :  */
      90              : static struct GNUNET_DB_EventHandler *eh;
      91              : 
      92              : /**
      93              :  * The auditors's configuration.
      94              :  */
      95              : static const struct GNUNET_CONFIGURATION_Handle *cfg;
      96              : 
      97              : /**
      98              :  * Success or failure of (exchange) database operations within
      99              :  * #test_dc and #recheck_dc.
     100              :  */
     101              : static enum GNUNET_DB_QueryStatus eqs;
     102              : 
     103              : 
     104              : /**
     105              :  * Given a deposit confirmation from #TALER_ARL_adb, check that it is also
     106              :  * in #TALER_ARL_edb.  Update the deposit confirmation context accordingly.
     107              :  *
     108              :  * @param cls NULL
     109              :  * @param dc the deposit confirmation we know
     110              :  * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop iterating
     111              :  */
     112              : static enum GNUNET_GenericReturnValue
     113            0 : test_dc (void *cls,
     114              :          const struct TALER_AUDITORDB_DepositConfirmation *dc)
     115              : {
     116            0 :   bool missing = false;
     117              :   enum GNUNET_DB_QueryStatus qs;
     118              : 
     119              :   (void) cls;
     120            0 :   TALER_ARL_USE_PP (deposit_confirmation_serial_id) = dc->row_id;
     121            0 :   for (unsigned int i = 0; i < dc->num_coins; i++)
     122              :   {
     123              :     struct GNUNET_TIME_Timestamp exchange_timestamp;
     124              :     struct TALER_Amount deposit_fee;
     125              : 
     126            0 :     qs = TALER_ARL_edb->have_deposit2 (TALER_ARL_edb->cls,
     127              :                                        &dc->h_contract_terms,
     128              :                                        &dc->h_wire,
     129            0 :                                        &dc->coin_pubs[i],
     130              :                                        &dc->merchant,
     131              :                                        dc->refund_deadline,
     132              :                                        &deposit_fee,
     133              :                                        &exchange_timestamp);
     134            0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     135              :                 "Status for deposit confirmation %llu-%u is %d\n",
     136              :                 (unsigned long long) dc->row_id,
     137              :                 i,
     138              :                 qs);
     139            0 :     missing |= (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs);
     140            0 :     if (qs < 0)
     141              :     {
     142            0 :       GNUNET_break (0); /* DB error, complain */
     143            0 :       eqs = qs;
     144            0 :       return GNUNET_SYSERR;
     145              :     }
     146              :   }
     147            0 :   if (! missing)
     148              :   {
     149            0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     150              :                 "Deleting matching deposit confirmation %llu\n",
     151              :                 (unsigned long long) dc->row_id);
     152            0 :     qs = TALER_ARL_adb->delete_generic (
     153            0 :       TALER_ARL_adb->cls,
     154              :       TALER_AUDITORDB_DEPOSIT_CONFIRMATION,
     155            0 :       dc->row_id);
     156            0 :     if (qs < 0)
     157              :     {
     158            0 :       GNUNET_break (0); /* DB error, complain */
     159            0 :       eqs = qs;
     160            0 :       return GNUNET_SYSERR;
     161              :     }
     162            0 :     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     163              :                 "Found deposit %s in exchange database\n",
     164              :                 GNUNET_h2s (&dc->h_contract_terms.hash));
     165            0 :     return GNUNET_OK; /* all coins found, all good */
     166              :   }
     167            0 :   TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_missed_deposit_confirmations),
     168              :                         &TALER_ARL_USE_AB (total_missed_deposit_confirmations),
     169              :                         &dc->total_without_fee);
     170            0 :   return GNUNET_OK;
     171              : }
     172              : 
     173              : 
     174              : /**
     175              :  * Given a previously missing deposit confirmation from #TALER_ARL_adb, check
     176              :  * *again* whether it is now in #TALER_ARL_edb.  Update the deposit
     177              :  * confirmation context accordingly.
     178              :  *
     179              :  * @param cls NULL
     180              :  * @param dc the deposit confirmation we know
     181              :  * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop iterating
     182              :  */
     183              : static enum GNUNET_GenericReturnValue
     184            0 : recheck_dc (void *cls,
     185              :             const struct TALER_AUDITORDB_DepositConfirmation *dc)
     186              : {
     187            0 :   bool missing = false;
     188              :   enum GNUNET_DB_QueryStatus qs;
     189              : 
     190              :   (void) cls;
     191            0 :   for (unsigned int i = 0; i < dc->num_coins; i++)
     192              :   {
     193              :     struct GNUNET_TIME_Timestamp exchange_timestamp;
     194              :     struct TALER_Amount deposit_fee;
     195              : 
     196            0 :     qs = TALER_ARL_edb->have_deposit2 (TALER_ARL_edb->cls,
     197              :                                        &dc->h_contract_terms,
     198              :                                        &dc->h_wire,
     199            0 :                                        &dc->coin_pubs[i],
     200              :                                        &dc->merchant,
     201              :                                        dc->refund_deadline,
     202              :                                        &deposit_fee,
     203              :                                        &exchange_timestamp);
     204            0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     205              :                 "Status for deposit confirmation %llu-%u is %d on re-check\n",
     206              :                 (unsigned long long) dc->row_id,
     207              :                 i,
     208              :                 qs);
     209            0 :     missing |= (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs);
     210            0 :     if (qs < 0)
     211              :     {
     212            0 :       GNUNET_break (0); /* DB error, complain */
     213            0 :       eqs = qs;
     214            0 :       return GNUNET_SYSERR;
     215              :     }
     216              :   }
     217            0 :   if (! missing)
     218              :   {
     219            0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     220              :                 "Deleting matching deposit confirmation %llu\n",
     221              :                 (unsigned long long) dc->row_id);
     222            0 :     qs = TALER_ARL_adb->delete_generic (
     223            0 :       TALER_ARL_adb->cls,
     224              :       TALER_AUDITORDB_DEPOSIT_CONFIRMATION,
     225            0 :       dc->row_id);
     226            0 :     if (qs < 0)
     227              :     {
     228            0 :       GNUNET_break (0); /* DB error, complain */
     229            0 :       eqs = qs;
     230            0 :       return GNUNET_SYSERR;
     231              :     }
     232            0 :     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     233              :                 "Previously missing deposit %s appeared in exchange database\n",
     234              :                 GNUNET_h2s (&dc->h_contract_terms.hash));
     235              :     /* It appeared, so *reduce* total missing balance */
     236            0 :     TALER_ARL_amount_subtract (&TALER_ARL_USE_AB (
     237              :                                  total_missed_deposit_confirmations),
     238              :                                &TALER_ARL_USE_AB (
     239              :                                  total_missed_deposit_confirmations),
     240              :                                &dc->total_without_fee);
     241            0 :     return GNUNET_OK; /* all coins found, all good */
     242              :   }
     243              :   /* still missing, no change to totalmissing balance */
     244            0 :   return GNUNET_OK;
     245              : }
     246              : 
     247              : 
     248              : /**
     249              :  * Check that the deposit-confirmations that were reported to
     250              :  * us by merchants are also in the exchange's database.
     251              :  *
     252              :  * @param cls closure
     253              :  * @return transaction status code
     254              :  */
     255              : static enum GNUNET_DB_QueryStatus
     256            4 : analyze_deposit_confirmations (void *cls)
     257              : {
     258              :   enum GNUNET_DB_QueryStatus qs;
     259              :   bool had_pp;
     260              :   bool had_bal;
     261              :   bool had_missing;
     262              :   uint64_t pp;
     263              : 
     264              :   (void) cls;
     265            4 :   qs = TALER_ARL_adb->get_auditor_progress (
     266            4 :     TALER_ARL_adb->cls,
     267              :     TALER_ARL_GET_PP (deposit_confirmation_serial_id),
     268              :     NULL);
     269            4 :   if (0 > qs)
     270              :   {
     271            0 :     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     272            0 :     return qs;
     273              :   }
     274            4 :   had_pp = (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs);
     275            4 :   if (had_pp)
     276              :   {
     277            4 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     278              :                 "Resuming deposit confirmation audit at %llu\n",
     279              :                 (unsigned long long) TALER_ARL_USE_PP (
     280              :                   deposit_confirmation_serial_id));
     281            4 :     pp = TALER_ARL_USE_PP (deposit_confirmation_serial_id);
     282              :   }
     283              :   else
     284              :   {
     285            0 :     GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
     286              :                 "First analysis using deposit auditor, starting audit from scratch\n");
     287              :   }
     288            4 :   qs = TALER_ARL_adb->get_balance (
     289            4 :     TALER_ARL_adb->cls,
     290              :     TALER_ARL_GET_AB (total_missed_deposit_confirmations),
     291              :     NULL);
     292            4 :   if (0 > qs)
     293              :   {
     294            0 :     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     295            0 :     return qs;
     296              :   }
     297            4 :   had_bal = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
     298            4 :   had_missing = ! TALER_amount_is_zero (
     299            4 :     &TALER_ARL_USE_AB (total_missed_deposit_confirmations));
     300            4 :   qs = TALER_ARL_adb->get_deposit_confirmations (
     301            4 :     TALER_ARL_adb->cls,
     302              :     INT64_MAX,
     303              :     TALER_ARL_USE_PP (deposit_confirmation_serial_id),
     304              :     true, /* return suppressed */
     305              :     &test_dc,
     306              :     NULL);
     307            4 :   if (0 > qs)
     308              :   {
     309            0 :     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     310            0 :     return qs;
     311              :   }
     312            4 :   if (0 > eqs)
     313              :   {
     314            0 :     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == eqs);
     315            0 :     return eqs;
     316              :   }
     317            4 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     318              :               "Analyzed %d deposit confirmations\n",
     319              :               (int) qs);
     320            4 :   if (had_pp)
     321            4 :     qs = TALER_ARL_adb->update_auditor_progress (
     322            4 :       TALER_ARL_adb->cls,
     323              :       TALER_ARL_SET_PP (deposit_confirmation_serial_id),
     324              :       NULL);
     325              :   else
     326            0 :     qs = TALER_ARL_adb->insert_auditor_progress (
     327            0 :       TALER_ARL_adb->cls,
     328              :       TALER_ARL_SET_PP (deposit_confirmation_serial_id),
     329              :       NULL);
     330            4 :   if (0 > qs)
     331              :   {
     332            0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     333              :                 "Failed to update auditor DB, not recording progress\n");
     334            0 :     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     335            0 :     return qs;
     336              :   }
     337            4 :   if (had_bal && had_pp && had_missing)
     338              :   {
     339            0 :     qs = TALER_ARL_adb->get_deposit_confirmations (
     340            0 :       TALER_ARL_adb->cls,
     341              :       -INT64_MAX,
     342              :       pp + 1, /* previous iteration went up to 'pp', try missing again */
     343              :       true, /* return suppressed */
     344              :       &recheck_dc,
     345              :       NULL);
     346            0 :     if (0 > qs)
     347              :     {
     348            0 :       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     349            0 :       return qs;
     350              :     }
     351            0 :     if (0 > eqs)
     352              :     {
     353            0 :       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == eqs);
     354            0 :       return eqs;
     355              :     }
     356            0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     357              :                 "Re-analyzed %d deposit confirmations\n",
     358              :                 (int) qs);
     359              :   }
     360            4 :   if (had_bal)
     361            4 :     qs = TALER_ARL_adb->update_balance (
     362            4 :       TALER_ARL_adb->cls,
     363              :       TALER_ARL_SET_AB (total_missed_deposit_confirmations),
     364              :       NULL);
     365              :   else
     366            0 :     qs = TALER_ARL_adb->insert_balance (
     367            0 :       TALER_ARL_adb->cls,
     368              :       TALER_ARL_SET_AB (total_missed_deposit_confirmations),
     369              :       NULL);
     370            4 :   if (0 > qs)
     371              :   {
     372            0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     373              :                 "Failed to update auditor DB, not recording progress\n");
     374            0 :     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     375            0 :     return qs;
     376              :   }
     377            4 :   return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
     378              : }
     379              : 
     380              : 
     381              : /**
     382              :  * Function called on events received from Postgres.
     383              :  *
     384              :  * @param cls closure, NULL
     385              :  * @param extra additional event data provided
     386              :  * @param extra_size number of bytes in @a extra
     387              :  */
     388              : static void
     389            0 : db_notify (void *cls,
     390              :            const void *extra,
     391              :            size_t extra_size)
     392              : {
     393              :   (void) cls;
     394              :   (void) extra;
     395              :   (void) extra_size;
     396            0 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     397              :               "Received notification for new deposit_confirmation\n");
     398            0 :   if (GNUNET_OK !=
     399            0 :       TALER_ARL_setup_sessions_and_run (&analyze_deposit_confirmations,
     400              :                                         NULL))
     401              :   {
     402            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     403              :                 "Audit failed\n");
     404            0 :     GNUNET_SCHEDULER_shutdown ();
     405            0 :     global_ret = EXIT_FAILURE;
     406            0 :     return;
     407              :   }
     408              : }
     409              : 
     410              : 
     411              : /**
     412              :  * Function called on shutdown.
     413              :  */
     414              : static void
     415            4 : do_shutdown (void *cls)
     416              : {
     417              :   (void) cls;
     418            4 :   if (NULL != eh)
     419              :   {
     420            4 :     TALER_ARL_adb->event_listen_cancel (eh);
     421            4 :     eh = NULL;
     422              :   }
     423            4 :   TALER_ARL_done ();
     424            4 : }
     425              : 
     426              : 
     427              : /**
     428              :  * Main function that will be run.
     429              :  *
     430              :  * @param cls closure
     431              :  * @param args remaining command-line arguments
     432              :  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
     433              :  * @param c configuration
     434              :  */
     435              : static void
     436            4 : run (void *cls,
     437              :      char *const *args,
     438              :      const char *cfgfile,
     439              :      const struct GNUNET_CONFIGURATION_Handle *c)
     440              : {
     441              :   (void) cls;
     442              :   (void) args;
     443              :   (void) cfgfile;
     444            4 :   cfg = c;
     445            4 :   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
     446              :                                  NULL);
     447            4 :   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     448              :               "Launching deposit auditor\n");
     449            4 :   if (GNUNET_OK !=
     450            4 :       TALER_ARL_init (c))
     451              :   {
     452            0 :     global_ret = EXIT_FAILURE;
     453            0 :     return;
     454              :   }
     455              : 
     456            4 :   if (test_mode != 1)
     457              :   {
     458            4 :     struct GNUNET_DB_EventHeaderP es = {
     459            4 :       .size = htons (sizeof (es)),
     460            4 :       .type = htons (TALER_DBEVENT_EXCHANGE_AUDITOR_WAKE_HELPER_DEPOSITS)
     461              :     };
     462              : 
     463            4 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     464              :                 "Running helper indefinitely\n");
     465            4 :     eh = TALER_ARL_adb->event_listen (TALER_ARL_adb->cls,
     466              :                                       &es,
     467            4 :                                       GNUNET_TIME_UNIT_FOREVER_REL,
     468              :                                       &db_notify,
     469              :                                       NULL);
     470              :   }
     471            4 :   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     472              :               "Starting audit\n");
     473            4 :   if (GNUNET_OK !=
     474            4 :       TALER_ARL_setup_sessions_and_run (&analyze_deposit_confirmations,
     475              :                                         NULL))
     476              :   {
     477            0 :     GNUNET_SCHEDULER_shutdown ();
     478            0 :     global_ret = EXIT_FAILURE;
     479            0 :     return;
     480              :   }
     481              : }
     482              : 
     483              : 
     484              : /**
     485              :  * The main function of the deposit auditing helper tool.
     486              :  *
     487              :  * @param argc number of arguments from the command line
     488              :  * @param argv command line arguments
     489              :  * @return 0 ok, 1 on error
     490              :  */
     491              : int
     492            4 : main (int argc,
     493              :       char *const *argv)
     494              : {
     495            4 :   const struct GNUNET_GETOPT_CommandLineOption options[] = {
     496            4 :     GNUNET_GETOPT_option_flag ('i',
     497              :                                "internal",
     498              :                                "perform checks only applicable for exchange-internal audits",
     499              :                                &internal_checks),
     500            4 :     GNUNET_GETOPT_option_flag ('t',
     501              :                                "test",
     502              :                                "run in test mode and exit when idle",
     503              :                                &test_mode),
     504            4 :     GNUNET_GETOPT_option_timetravel ('T',
     505              :                                      "timetravel"),
     506              :     GNUNET_GETOPT_OPTION_END
     507              :   };
     508              :   enum GNUNET_GenericReturnValue ret;
     509              : 
     510            4 :   ret = GNUNET_PROGRAM_run (
     511              :     TALER_AUDITOR_project_data (),
     512              :     argc,
     513              :     argv,
     514              :     "taler-helper-auditor-deposits",
     515              :     gettext_noop (
     516              :       "Audit Taler exchange database for deposit confirmation consistency"),
     517              :     options,
     518              :     &run,
     519              :     NULL);
     520            4 :   if (GNUNET_SYSERR == ret)
     521            0 :     return EXIT_INVALIDARGUMENT;
     522            4 :   if (GNUNET_NO == ret)
     523            0 :     return EXIT_SUCCESS;
     524            4 :   return global_ret;
     525              : }
     526              : 
     527              : 
     528              : /* end of taler-helper-auditor-deposits.c */
        

Generated by: LCOV version 2.0-1