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 (15971B)


      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 test_dc (void *cls,
    121          const struct TALER_AUDITORDB_DepositConfirmation *dc)
    122 {
    123   bool missing = false;
    124   enum GNUNET_DB_QueryStatus qs;
    125 
    126   (void) cls;
    127   TALER_ARL_USE_PP (deposit_confirmation_serial_id) = dc->row_id;
    128   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     qs = TALER_EXCHANGEDB_get_exists_deposit (TALER_ARL_edb,
    134                                               &dc->h_contract_terms,
    135                                               &dc->h_wire,
    136                                               &dc->coin_pubs[i],
    137                                               &dc->merchant,
    138                                               dc->refund_deadline,
    139                                               &deposit_fee,
    140                                               &exchange_timestamp);
    141     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     missing |= (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs);
    147     if (qs < 0)
    148     {
    149       GNUNET_break (0); /* DB error, complain */
    150       eqs = qs;
    151       return GNUNET_SYSERR;
    152     }
    153   }
    154   if (! missing)
    155   {
    156     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    157                 "Deleting matching deposit confirmation %llu\n",
    158                 (unsigned long long) dc->row_id);
    159     qs = TALER_AUDITORDB_delete_generic (
    160       TALER_ARL_adb,
    161       TALER_AUDITORDB_DEPOSIT_CONFIRMATION,
    162       dc->row_id);
    163     if (qs < 0)
    164     {
    165       GNUNET_break (0); /* DB error, complain */
    166       eqs = qs;
    167       return GNUNET_SYSERR;
    168     }
    169     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    170                 "Found deposit %s in exchange database\n",
    171                 GNUNET_h2s (&dc->h_contract_terms.hash));
    172     return GNUNET_OK; /* all coins found, all good */
    173   }
    174   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   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 recheck_dc (void *cls,
    192             const struct TALER_AUDITORDB_DepositConfirmation *dc)
    193 {
    194   bool missing = false;
    195   enum GNUNET_DB_QueryStatus qs;
    196 
    197   (void) cls;
    198   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     qs = TALER_EXCHANGEDB_get_exists_deposit (TALER_ARL_edb,
    204                                               &dc->h_contract_terms,
    205                                               &dc->h_wire,
    206                                               &dc->coin_pubs[i],
    207                                               &dc->merchant,
    208                                               dc->refund_deadline,
    209                                               &deposit_fee,
    210                                               &exchange_timestamp);
    211     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     missing |= (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs);
    217     if (qs < 0)
    218     {
    219       GNUNET_break (0); /* DB error, complain */
    220       eqs = qs;
    221       return GNUNET_SYSERR;
    222     }
    223   }
    224   if (! missing)
    225   {
    226     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    227                 "Deleting matching deposit confirmation %llu\n",
    228                 (unsigned long long) dc->row_id);
    229     qs = TALER_AUDITORDB_delete_generic (
    230       TALER_ARL_adb,
    231       TALER_AUDITORDB_DEPOSIT_CONFIRMATION,
    232       dc->row_id);
    233     if (qs < 0)
    234     {
    235       GNUNET_break (0); /* DB error, complain */
    236       eqs = qs;
    237       return GNUNET_SYSERR;
    238     }
    239     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     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     return GNUNET_OK; /* all coins found, all good */
    249   }
    250   /* still missing, no change to totalmissing balance */
    251   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 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   eqs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
    277   qs = TALER_AUDITORDB_get_auditor_progress (
    278     TALER_ARL_adb,
    279     TALER_ARL_GET_PP (deposit_confirmation_serial_id),
    280     NULL);
    281   if (0 > qs)
    282   {
    283     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    284     return qs;
    285   }
    286   had_pp = (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs);
    287   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   pp = TALER_ARL_USE_PP (deposit_confirmation_serial_id);
    292   qs = TALER_AUDITORDB_get_balance (
    293     TALER_ARL_adb,
    294     TALER_ARL_GET_AB (total_missed_deposit_confirmations),
    295     NULL);
    296   if (0 > qs)
    297   {
    298     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    299     return qs;
    300   }
    301   had_bal = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
    302   had_missing = ! TALER_amount_is_zero (
    303     &TALER_ARL_USE_AB (total_missed_deposit_confirmations));
    304   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   if (0 > qs)
    312   {
    313     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    314     return qs;
    315   }
    316   if (0 > eqs)
    317   {
    318     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == eqs);
    319     return eqs;
    320   }
    321   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    322               "Analyzed %d deposit confirmations\n",
    323               (int) qs);
    324   qs = TALER_AUDITORDB_insert_auditor_progress (
    325     TALER_ARL_adb,
    326     TALER_ARL_SET_PP (deposit_confirmation_serial_id),
    327     NULL);
    328   if (0 > qs)
    329   {
    330     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    331                 "Failed to update auditor DB, not recording progress\n");
    332     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    333     return qs;
    334   }
    335   if (had_bal && had_pp && had_missing)
    336   {
    337     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     if (0 > qs)
    345     {
    346       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    347       return qs;
    348     }
    349     if (0 > eqs)
    350     {
    351       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == eqs);
    352       return eqs;
    353     }
    354     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    355                 "Re-analyzed %d deposit confirmations\n",
    356                 (int) qs);
    357   }
    358   qs = TALER_AUDITORDB_insert_balance (
    359     TALER_ARL_adb,
    360     TALER_ARL_SET_AB (total_missed_deposit_confirmations),
    361     NULL);
    362   if (0 > qs)
    363   {
    364     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    365                 "Failed to update auditor DB, not recording progress\n");
    366     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    367     return qs;
    368   }
    369   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 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   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    389               "Received notification for new deposit_confirmation\n");
    390   if (GNUNET_OK !=
    391       TALER_ARL_setup_sessions_and_run (&analyze_deposit_confirmations,
    392                                         NULL))
    393   {
    394     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    395                 "Audit failed\n");
    396     GNUNET_SCHEDULER_shutdown ();
    397     global_ret = EXIT_FAILURE;
    398     return;
    399   }
    400 }
    401 
    402 
    403 /**
    404  * Function called on shutdown.
    405  */
    406 static void
    407 do_shutdown (void *cls)
    408 {
    409   (void) cls;
    410   if (NULL != eh)
    411   {
    412     TALER_AUDITORDB_event_listen_cancel (eh);
    413     eh = NULL;
    414   }
    415   TALER_ARL_done ();
    416 }
    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 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   cfg = c;
    437   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
    438                                  NULL);
    439   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    440               "Launching deposit auditor\n");
    441   if (GNUNET_OK !=
    442       TALER_ARL_init (c))
    443   {
    444     global_ret = EXIT_FAILURE;
    445     return;
    446   }
    447 
    448   if (test_mode != 1)
    449   {
    450     struct GNUNET_DB_EventHeaderP es = {
    451       .size = htons (sizeof (es)),
    452       .type = htons (TALER_DBEVENT_EXCHANGE_AUDITOR_WAKE_HELPER_DEPOSITS)
    453     };
    454 
    455     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    456                 "Running helper indefinitely\n");
    457     eh = TALER_AUDITORDB_event_listen (TALER_ARL_adb,
    458                                        &es,
    459                                        GNUNET_TIME_UNIT_FOREVER_REL,
    460                                        &db_notify,
    461                                        NULL);
    462   }
    463   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    464               "Starting audit\n");
    465   if (GNUNET_OK !=
    466       TALER_ARL_setup_sessions_and_run (&analyze_deposit_confirmations,
    467                                         NULL))
    468   {
    469     GNUNET_SCHEDULER_shutdown ();
    470     global_ret = EXIT_FAILURE;
    471     return;
    472   }
    473 }
    474 
    475 
    476 /**
    477  * The main function of the deposit auditing helper tool.
    478  *
    479  * @param argc number of arguments from the command line
    480  * @param argv command line arguments
    481  * @return 0 ok, 1 on error
    482  */
    483 int
    484 main (int argc,
    485       char *const *argv)
    486 {
    487   const struct GNUNET_GETOPT_CommandLineOption options[] = {
    488     GNUNET_GETOPT_option_flag ('i',
    489                                "internal",
    490                                "perform checks only applicable for exchange-internal audits",
    491                                &internal_checks),
    492     GNUNET_GETOPT_option_flag ('t',
    493                                "test",
    494                                "run in test mode and exit when idle",
    495                                &test_mode),
    496     GNUNET_GETOPT_option_timetravel ('T',
    497                                      "timetravel"),
    498     GNUNET_GETOPT_OPTION_END
    499   };
    500   enum GNUNET_GenericReturnValue ret;
    501 
    502   ret = GNUNET_PROGRAM_run (
    503     TALER_AUDITOR_project_data (),
    504     argc,
    505     argv,
    506     "taler-helper-auditor-deposits",
    507     gettext_noop (
    508       "Audit Taler exchange database for deposit confirmation consistency"),
    509     options,
    510     &run,
    511     NULL);
    512   if (GNUNET_SYSERR == ret)
    513     return EXIT_INVALIDARGUMENT;
    514   if (GNUNET_NO == ret)
    515     return EXIT_SUCCESS;
    516   return global_ret;
    517 }
    518 
    519 
    520 /* end of taler-helper-auditor-deposits.c */