exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

taler-helper-auditor-deposits.c (16153B)


      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/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 test_dc (void *cls,
    123          const struct TALER_AUDITORDB_DepositConfirmation *dc)
    124 {
    125   bool missing = false;
    126   enum GNUNET_DB_QueryStatus qs;
    127 
    128   (void) cls;
    129   TALER_ARL_USE_PP (deposit_confirmation_serial_id) = dc->row_id;
    130   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     qs = TALER_EXCHANGEDB_have_deposit2 (TALER_ARL_edb,
    136                                          &dc->h_contract_terms,
    137                                          &dc->h_wire,
    138                                          &dc->coin_pubs[i],
    139                                          &dc->merchant,
    140                                          dc->refund_deadline,
    141                                          &deposit_fee,
    142                                          &exchange_timestamp);
    143     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     missing |= (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs);
    149     if (qs < 0)
    150     {
    151       GNUNET_break (0); /* DB error, complain */
    152       eqs = qs;
    153       return GNUNET_SYSERR;
    154     }
    155   }
    156   if (! missing)
    157   {
    158     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    159                 "Deleting matching deposit confirmation %llu\n",
    160                 (unsigned long long) dc->row_id);
    161     qs = TALER_AUDITORDB_delete_generic (
    162       TALER_ARL_adb,
    163       TALER_AUDITORDB_DEPOSIT_CONFIRMATION,
    164       dc->row_id);
    165     if (qs < 0)
    166     {
    167       GNUNET_break (0); /* DB error, complain */
    168       eqs = qs;
    169       return GNUNET_SYSERR;
    170     }
    171     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    172                 "Found deposit %s in exchange database\n",
    173                 GNUNET_h2s (&dc->h_contract_terms.hash));
    174     return GNUNET_OK; /* all coins found, all good */
    175   }
    176   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   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 recheck_dc (void *cls,
    194             const struct TALER_AUDITORDB_DepositConfirmation *dc)
    195 {
    196   bool missing = false;
    197   enum GNUNET_DB_QueryStatus qs;
    198 
    199   (void) cls;
    200   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     qs = TALER_EXCHANGEDB_have_deposit2 (TALER_ARL_edb,
    206                                          &dc->h_contract_terms,
    207                                          &dc->h_wire,
    208                                          &dc->coin_pubs[i],
    209                                          &dc->merchant,
    210                                          dc->refund_deadline,
    211                                          &deposit_fee,
    212                                          &exchange_timestamp);
    213     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     missing |= (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs);
    219     if (qs < 0)
    220     {
    221       GNUNET_break (0); /* DB error, complain */
    222       eqs = qs;
    223       return GNUNET_SYSERR;
    224     }
    225   }
    226   if (! missing)
    227   {
    228     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    229                 "Deleting matching deposit confirmation %llu\n",
    230                 (unsigned long long) dc->row_id);
    231     qs = TALER_AUDITORDB_delete_generic (
    232       TALER_ARL_adb,
    233       TALER_AUDITORDB_DEPOSIT_CONFIRMATION,
    234       dc->row_id);
    235     if (qs < 0)
    236     {
    237       GNUNET_break (0); /* DB error, complain */
    238       eqs = qs;
    239       return GNUNET_SYSERR;
    240     }
    241     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     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     return GNUNET_OK; /* all coins found, all good */
    251   }
    252   /* still missing, no change to totalmissing balance */
    253   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 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   qs = TALER_AUDITORDB_get_auditor_progress (
    275     TALER_ARL_adb,
    276     TALER_ARL_GET_PP (deposit_confirmation_serial_id),
    277     NULL);
    278   if (0 > qs)
    279   {
    280     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    281     return qs;
    282   }
    283   had_pp = (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs);
    284   if (had_pp)
    285   {
    286     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     pp = TALER_ARL_USE_PP (deposit_confirmation_serial_id);
    291   }
    292   else
    293   {
    294     GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
    295                 "First analysis using deposit auditor, starting audit from scratch\n");
    296   }
    297   qs = TALER_AUDITORDB_get_balance (
    298     TALER_ARL_adb,
    299     TALER_ARL_GET_AB (total_missed_deposit_confirmations),
    300     NULL);
    301   if (0 > qs)
    302   {
    303     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    304     return qs;
    305   }
    306   had_bal = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
    307   had_missing = ! TALER_amount_is_zero (
    308     &TALER_ARL_USE_AB (total_missed_deposit_confirmations));
    309   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   if (0 > qs)
    317   {
    318     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    319     return qs;
    320   }
    321   if (0 > eqs)
    322   {
    323     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == eqs);
    324     return eqs;
    325   }
    326   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    327               "Analyzed %d deposit confirmations\n",
    328               (int) qs);
    329   if (had_pp)
    330     qs = TALER_AUDITORDB_update_auditor_progress (
    331       TALER_ARL_adb,
    332       TALER_ARL_SET_PP (deposit_confirmation_serial_id),
    333       NULL);
    334   else
    335     qs = TALER_AUDITORDB_insert_auditor_progress (
    336       TALER_ARL_adb,
    337       TALER_ARL_SET_PP (deposit_confirmation_serial_id),
    338       NULL);
    339   if (0 > qs)
    340   {
    341     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    342                 "Failed to update auditor DB, not recording progress\n");
    343     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    344     return qs;
    345   }
    346   if (had_bal && had_pp && had_missing)
    347   {
    348     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     if (0 > qs)
    356     {
    357       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    358       return qs;
    359     }
    360     if (0 > eqs)
    361     {
    362       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == eqs);
    363       return eqs;
    364     }
    365     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    366                 "Re-analyzed %d deposit confirmations\n",
    367                 (int) qs);
    368   }
    369   if (had_bal)
    370     qs = TALER_AUDITORDB_update_balance (
    371       TALER_ARL_adb,
    372       TALER_ARL_SET_AB (total_missed_deposit_confirmations),
    373       NULL);
    374   else
    375     qs = TALER_AUDITORDB_insert_balance (
    376       TALER_ARL_adb,
    377       TALER_ARL_SET_AB (total_missed_deposit_confirmations),
    378       NULL);
    379   if (0 > qs)
    380   {
    381     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    382                 "Failed to update auditor DB, not recording progress\n");
    383     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    384     return qs;
    385   }
    386   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 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   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    406               "Received notification for new deposit_confirmation\n");
    407   if (GNUNET_OK !=
    408       TALER_ARL_setup_sessions_and_run (&analyze_deposit_confirmations,
    409                                         NULL))
    410   {
    411     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    412                 "Audit failed\n");
    413     GNUNET_SCHEDULER_shutdown ();
    414     global_ret = EXIT_FAILURE;
    415     return;
    416   }
    417 }
    418 
    419 
    420 /**
    421  * Function called on shutdown.
    422  */
    423 static void
    424 do_shutdown (void *cls)
    425 {
    426   (void) cls;
    427   if (NULL != eh)
    428   {
    429     TALER_AUDITORDB_event_listen_cancel (eh);
    430     eh = NULL;
    431   }
    432   TALER_ARL_done ();
    433 }
    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 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   cfg = c;
    454   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
    455                                  NULL);
    456   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    457               "Launching deposit auditor\n");
    458   if (GNUNET_OK !=
    459       TALER_ARL_init (c))
    460   {
    461     global_ret = EXIT_FAILURE;
    462     return;
    463   }
    464 
    465   if (test_mode != 1)
    466   {
    467     struct GNUNET_DB_EventHeaderP es = {
    468       .size = htons (sizeof (es)),
    469       .type = htons (TALER_DBEVENT_EXCHANGE_AUDITOR_WAKE_HELPER_DEPOSITS)
    470     };
    471 
    472     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    473                 "Running helper indefinitely\n");
    474     eh = TALER_AUDITORDB_event_listen (TALER_ARL_adb,
    475                                        &es,
    476                                        GNUNET_TIME_UNIT_FOREVER_REL,
    477                                        &db_notify,
    478                                        NULL);
    479   }
    480   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    481               "Starting audit\n");
    482   if (GNUNET_OK !=
    483       TALER_ARL_setup_sessions_and_run (&analyze_deposit_confirmations,
    484                                         NULL))
    485   {
    486     GNUNET_SCHEDULER_shutdown ();
    487     global_ret = EXIT_FAILURE;
    488     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 main (int argc,
    502       char *const *argv)
    503 {
    504   const struct GNUNET_GETOPT_CommandLineOption options[] = {
    505     GNUNET_GETOPT_option_flag ('i',
    506                                "internal",
    507                                "perform checks only applicable for exchange-internal audits",
    508                                &internal_checks),
    509     GNUNET_GETOPT_option_flag ('t',
    510                                "test",
    511                                "run in test mode and exit when idle",
    512                                &test_mode),
    513     GNUNET_GETOPT_option_timetravel ('T',
    514                                      "timetravel"),
    515     GNUNET_GETOPT_OPTION_END
    516   };
    517   enum GNUNET_GenericReturnValue ret;
    518 
    519   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   if (GNUNET_SYSERR == ret)
    530     return EXIT_INVALIDARGUMENT;
    531   if (GNUNET_NO == ret)
    532     return EXIT_SUCCESS;
    533   return global_ret;
    534 }
    535 
    536 
    537 /* end of taler-helper-auditor-deposits.c */