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


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