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

Generated by: LCOV version 2.0-1