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

Generated by: LCOV version 2.0-1