diff options
Diffstat (limited to 'src/auditor/taler-helper-auditor-deposits.c')
-rw-r--r-- | src/auditor/taler-helper-auditor-deposits.c | 372 |
1 files changed, 266 insertions, 106 deletions
diff --git a/src/auditor/taler-helper-auditor-deposits.c b/src/auditor/taler-helper-auditor-deposits.c index 291558590..3dbce0183 100644 --- a/src/auditor/taler-helper-auditor-deposits.c +++ b/src/auditor/taler-helper-auditor-deposits.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2016-2020 Taler Systems SA + Copyright (C) 2016-2023 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero Public License as published by the Free Software @@ -17,6 +17,7 @@ * @file auditor/taler-helper-auditor-deposits.c * @brief audits an exchange database for deposit confirmation consistency * @author Christian Grothoff + * @author Nic Eigel * * We simply check that all of the deposit confirmations reported to us * by merchants were also reported to us by the exchange. @@ -29,7 +30,29 @@ #include "taler_bank_service.h" #include "taler_signatures.h" #include "report-lib.h" +#include "taler_dbevents.h" +#include <jansson.h> +/* +-- +-- SELECT serial_id,h_contract_terms,h_wire,merchant_pub ... +-- FROM auditor.auditor_deposit_confirmations +-- WHERE NOT ancient +-- ORDER BY exchange_timestamp ASC; +-- SELECT 1 +- FROM exchange.deposits dep + WHERE ($RESULT.contract_terms = dep.h_contract_terms) AND ($RESULT.h_wire = dep.h_wire) AND ...); +-- IF FOUND +-- DELETE FROM auditor.auditor_deposit_confirmations +-- WHERE serial_id = $RESULT.serial_id; +-- SELECT exchange_timestamp AS latest +-- FROM exchange.deposits ORDER BY exchange_timestamp DESC; +-- latest -= 1 hour; // time is not exactly monotonic... +-- UPDATE auditor.deposit_confirmations +-- SET ancient=TRUE +-- WHERE exchange_timestamp < latest +-- AND NOT ancient; +*/ /** * Return value from main(). @@ -37,6 +60,14 @@ static int global_ret; /** + * Run in test mode. Exit when idle instead of + * going to sleep and waiting for more work. + * + * FIXME: not yet implemented! + */ +static int test_mode; + +/** * Array of reports about missing deposit confirmations. */ static json_t *report_deposit_confirmation_inconsistencies; @@ -51,6 +82,22 @@ static json_int_t number_missed_deposit_confirmations; */ static struct TALER_Amount total_missed_deposit_confirmations; +/** + * Should we run checks that only work for exchange-internal audits? + */ +static int internal_checks; + +static struct GNUNET_DB_EventHandler *eh; + +/** + * Our database plugin. + */ +static struct TALER_AUDITORDB_Plugin *db_plugin; + +/** + * The auditors's configuration. + */ +static const struct GNUNET_CONFIGURATION_Handle *cfg; /** * Closure for #test_dc. @@ -88,7 +135,6 @@ struct DepositConfirmationContext }; - /** * Given a deposit confirmation from #TALER_ARL_adb, check that it is also * in #TALER_ARL_edb. Update the deposit confirmation context accordingly. @@ -96,60 +142,69 @@ struct DepositConfirmationContext * @param cls our `struct DepositConfirmationContext` * @param serial_id row of the @a dc in the database * @param dc the deposit confirmation we know + * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop iterating */ -static void +static enum GNUNET_GenericReturnValue test_dc (void *cls, uint64_t serial_id, const struct TALER_AUDITORDB_DepositConfirmation *dc) { struct DepositConfirmationContext *dcc = cls; + bool missing = false; dcc->last_seen_coin_serial = serial_id; + for (unsigned int i = 0; i < dc->num_coins; i++) { enum GNUNET_DB_QueryStatus qs; - struct TALER_EXCHANGEDB_Deposit dep = { - .coin.coin_pub = dc->coin_pub, - .h_contract_terms = dc->h_contract_terms, - .merchant_pub = dc->merchant, - .h_wire = dc->h_wire, - .refund_deadline = dc->refund_deadline - }; - - qs = TALER_ARL_edb->have_deposit (TALER_ARL_edb->cls, - TALER_ARL_esession, - &dep, - GNUNET_NO /* do not check refund deadline */); - if (qs > 0) - { - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Found deposit %s in exchange database\n", - GNUNET_h2s (&dc->h_contract_terms)); - return; /* found, all good */ - } + struct GNUNET_TIME_Timestamp exchange_timestamp; + struct TALER_Amount deposit_fee; + + qs = TALER_ARL_edb->have_deposit2 (TALER_ARL_edb->cls, + &dc->h_contract_terms, + &dc->h_wire, + &dc->coin_pubs[i], + &dc->merchant, + dc->refund_deadline, + &deposit_fee, + &exchange_timestamp); + missing |= (0 == qs); if (qs < 0) { GNUNET_break (0); /* DB error, complain */ dcc->qs = qs; - return; + return GNUNET_SYSERR; } } + if (! missing) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Found deposit %s in exchange database\n", + GNUNET_h2s (&dc->h_contract_terms.hash)); + if (TALER_ARL_do_abort ()) + return GNUNET_SYSERR; + return GNUNET_OK; /* all coins found, all good */ + } /* deposit confirmation missing! report! */ - TALER_ARL_report (report_deposit_confirmation_inconsistencies, - json_pack ("{s:o, s:o, s:I, s:o}", - "timestamp", - TALER_ARL_json_from_time_abs (dc->timestamp), - "amount", - TALER_JSON_from_amount (&dc->amount_without_fee), - "rowid", - (json_int_t) serial_id, - "account", - GNUNET_JSON_from_data_auto (&dc->h_wire))); + TALER_ARL_report ( + report_deposit_confirmation_inconsistencies, + GNUNET_JSON_PACK ( + TALER_JSON_pack_time_abs_human ("timestamp", + dc->exchange_timestamp.abs_time), + TALER_JSON_pack_amount ("amount", + &dc->total_without_fee), + GNUNET_JSON_pack_uint64 ("rowid", + serial_id), + GNUNET_JSON_pack_data_auto ("account", + &dc->h_wire))); dcc->first_missed_coin_serial = GNUNET_MIN (dcc->first_missed_coin_serial, serial_id); dcc->missed_count++; TALER_ARL_amount_add (&dcc->missed_amount, &dcc->missed_amount, - &dc->amount_without_fee); + &dc->total_without_fee); + if (TALER_ARL_do_abort ()) + return GNUNET_SYSERR; + return GNUNET_OK; } @@ -163,21 +218,18 @@ test_dc (void *cls, static enum GNUNET_DB_QueryStatus analyze_deposit_confirmations (void *cls) { - struct TALER_AUDITORDB_ProgressPointDepositConfirmation ppdc; + TALER_ARL_DEF_PP (deposit_confirmation_serial_id); struct DepositConfirmationContext dcc; enum GNUNET_DB_QueryStatus qs; enum GNUNET_DB_QueryStatus qsx; enum GNUNET_DB_QueryStatus qsp; - (void) cls; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Analyzing deposit confirmations\n"); - ppdc.last_deposit_confirmation_serial_id = 0; - qsp = TALER_ARL_adb->get_auditor_progress_deposit_confirmation ( + + qsp = TALER_ARL_adb->get_auditor_progress ( TALER_ARL_adb->cls, - TALER_ARL_asession, - &TALER_ARL_master_pub, - &ppdc); + TALER_ARL_GET_PP (deposit_confirmation_serial_id), + NULL); + if (0 > qsp) { GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qsp); @@ -192,23 +244,26 @@ analyze_deposit_confirmations (void *cls) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Resuming deposit confirmation audit at %llu\n", - (unsigned long long) ppdc.last_deposit_confirmation_serial_id); + (unsigned long long) TALER_ARL_USE_PP ( + deposit_confirmation_serial_id)); } /* setup 'cc' */ GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (TALER_ARL_currency, + TALER_amount_set_zero (TALER_ARL_currency, &dcc.missed_amount)); dcc.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; dcc.missed_count = 0LLU; dcc.first_missed_coin_serial = UINT64_MAX; - qsx = TALER_ARL_adb->get_deposit_confirmations (TALER_ARL_adb->cls, - TALER_ARL_asession, - &TALER_ARL_master_pub, - ppdc. - last_deposit_confirmation_serial_id, - &test_dc, - &dcc); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "lastdepconfserialid %lu\n", + TALER_ARL_USE_PP (deposit_confirmation_serial_id)); + qsx = TALER_ARL_adb->get_deposit_confirmations ( + TALER_ARL_adb->cls, + TALER_ARL_USE_PP (deposit_confirmation_serial_id), + true, /* return suppressed */ + &test_dc, + &dcc); if (0 > qsx) { GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qsx); @@ -217,30 +272,30 @@ analyze_deposit_confirmations (void *cls) GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Analyzed %d deposit confirmations (above serial ID %llu)\n", (int) qsx, - (unsigned long long) ppdc.last_deposit_confirmation_serial_id); + (unsigned long long) TALER_ARL_USE_PP ( + deposit_confirmation_serial_id)); if (0 > dcc.qs) { GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == dcc.qs); return dcc.qs; } - if (UINT64_MAX == dcc.first_missed_coin_serial) - ppdc.last_deposit_confirmation_serial_id = dcc.last_seen_coin_serial; - else - ppdc.last_deposit_confirmation_serial_id = dcc.first_missed_coin_serial - 1; + /* if (UINT64_MAX == dcc.first_missed_coin_serial) + ppdc.last_deposit_confirmation_serial_id = dcc.last_seen_coin_serial; + else + ppdc.last_deposit_confirmation_serial_id = dcc.first_missed_coin_serial - 1; + */ /* sync 'cc' back to disk */ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qsp) - qs = TALER_ARL_adb->update_auditor_progress_deposit_confirmation ( + qs = TALER_ARL_adb->update_auditor_progress ( TALER_ARL_adb->cls, - TALER_ARL_asession, - &TALER_ARL_master_pub, - &ppdc); + TALER_ARL_SET_PP (deposit_confirmation_serial_id), + NULL); else - qs = TALER_ARL_adb->insert_auditor_progress_deposit_confirmation ( + qs = TALER_ARL_adb->insert_auditor_progress ( TALER_ARL_adb->cls, - TALER_ARL_asession, - &TALER_ARL_master_pub, - &ppdc); + TALER_ARL_SET_PP (deposit_confirmation_serial_id), + NULL); if (0 >= qs) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, @@ -248,17 +303,90 @@ analyze_deposit_confirmations (void *cls) GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); return qs; } + number_missed_deposit_confirmations = (json_int_t) dcc.missed_count; total_missed_deposit_confirmations = dcc.missed_amount; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Concluded deposit confirmation audit step at %llu\n", - (unsigned long long) ppdc.last_deposit_confirmation_serial_id); + (unsigned long long) TALER_ARL_USE_PP ( + deposit_confirmation_serial_id)); return qs; } /** + * Function called on events received from Postgres. + * + * @param cls closure, NULL + * @param extra additional event data provided + * @param extra_size number of bytes in @a extra + */ +static void +db_notify (void *cls, + const void *extra, + size_t extra_size) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Received notification for new deposit_confirmation\n"); + + (void) cls; + (void) extra; + (void) extra_size; + + if (NULL == + (db_plugin = TALER_AUDITORDB_plugin_load (cfg))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to initialize DB subsystem\n"); + GNUNET_SCHEDULER_shutdown (); + return; + } + GNUNET_assert (NULL != + (report_deposit_confirmation_inconsistencies = json_array ())); + + if (GNUNET_OK != + TALER_ARL_setup_sessions_and_run (&analyze_deposit_confirmations, + NULL)) + { + global_ret = EXIT_FAILURE; + return; + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Deposit audit complete\n"); + TALER_ARL_done ( + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_array_steal ("deposit_confirmation_inconsistencies", + report_deposit_confirmation_inconsistencies), + GNUNET_JSON_pack_uint64 ("missing_deposit_confirmation_count", + number_missed_deposit_confirmations), + TALER_JSON_pack_amount ("missing_deposit_confirmation_total", + &total_missed_deposit_confirmations), + TALER_JSON_pack_time_abs_human ("auditor_start_time", + start_time), + TALER_JSON_pack_time_abs_human ("auditor_end_time", + GNUNET_TIME_absolute_get ()))); +} + + +/** + * Function called on shutdown. + */ +static void +do_shutdown (void *cls) +{ + (void) cls; + + db_plugin->event_listen_cancel (eh); + eh = NULL; + TALER_AUDITORDB_plugin_unload (db_plugin); + db_plugin = NULL; + TALER_ARL_done (NULL); +} + + +/** * Main function that will be run. * * @param cls closure @@ -275,14 +403,46 @@ run (void *cls, (void) cls; (void) args; (void) cfgfile; + cfg = c; + + GNUNET_SCHEDULER_add_shutdown (&do_shutdown, + NULL); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Launching deposit auditor\n"); if (GNUNET_OK != TALER_ARL_init (c)) { - global_ret = 1; + global_ret = EXIT_FAILURE; + return; + } + + if (NULL == + (db_plugin = TALER_AUDITORDB_plugin_load (cfg))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to initialize DB subsystem\n"); + GNUNET_SCHEDULER_shutdown (); return; } + if (GNUNET_OK != + db_plugin->preflight (db_plugin->cls)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to connect to database\n"); + GNUNET_SCHEDULER_shutdown (); + return; + } + + struct GNUNET_DB_EventHeaderP es = { + .size = htons (sizeof (es)), + .type = htons (TALER_DBEVENT_EXCHANGE_AUDITOR_NEW_DEPOSIT_CONFIRMATION) + }; + eh = db_plugin->event_listen (db_plugin->cls, + &es, + GNUNET_TIME_UNIT_FOREVER_REL, + &db_notify, + NULL); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Starting deposit audit\n"); GNUNET_assert (NULL != @@ -291,32 +451,23 @@ run (void *cls, TALER_ARL_setup_sessions_and_run (&analyze_deposit_confirmations, NULL)) { - global_ret = 1; + global_ret = EXIT_FAILURE; return; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Deposit audit complete\n"); - { - json_t *report; - - report = json_pack ("{s:o, s:I, s:o, s:o, s:o}", - "deposit_confirmation_inconsistencies", - report_deposit_confirmation_inconsistencies, - "missing_deposit_confirmation_count", - (json_int_t) number_missed_deposit_confirmations, - "missing_deposit_confirmation_total", - TALER_JSON_from_amount ( - &total_missed_deposit_confirmations), - "auditor_start_time", - TALER_ARL_json_from_time_abs ( - start_time), - "auditor_end_time", - TALER_ARL_json_from_time_abs ( - GNUNET_TIME_absolute_get ()) - ); - GNUNET_break (NULL != report); - TALER_ARL_done (report); - } + TALER_ARL_done ( + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_array_steal ("deposit_confirmation_inconsistencies", + report_deposit_confirmation_inconsistencies), + GNUNET_JSON_pack_uint64 ("missing_deposit_confirmation_count", + number_missed_deposit_confirmations), + TALER_JSON_pack_amount ("missing_deposit_confirmation_total", + &total_missed_deposit_confirmations), + TALER_JSON_pack_time_abs_human ("auditor_start_time", + start_time), + TALER_JSON_pack_time_abs_human ("auditor_end_time", + GNUNET_TIME_absolute_get ()))); } @@ -332,33 +483,42 @@ main (int argc, char *const *argv) { const struct GNUNET_GETOPT_CommandLineOption options[] = { - GNUNET_GETOPT_option_base32_auto ('m', - "exchange-key", - "KEY", - "public key of the exchange (Crockford base32 encoded)", - &TALER_ARL_master_pub), + GNUNET_GETOPT_option_flag ('i', + "internal", + "perform checks only applicable for exchange-internal audits", + &internal_checks), + GNUNET_GETOPT_option_flag ('t', + "test", + "run in test mode and exit when idle", + &test_mode), GNUNET_GETOPT_option_timetravel ('T', "timetravel"), GNUNET_GETOPT_OPTION_END }; + enum GNUNET_GenericReturnValue ret; /* force linker to link against libtalerutil; if we do not do this, the linker may "optimize" libtalerutil away and skip #TALER_OS_init(), which we do need */ (void) TALER_project_data_default (); - GNUNET_assert (GNUNET_OK == - GNUNET_log_setup ("taler-helper-auditor-deposits", - "MESSAGE", - NULL)); if (GNUNET_OK != - GNUNET_PROGRAM_run (argc, - argv, - "taler-helper-auditor-deposits", - "Audit Taler exchange database for deposit confirmation consistency", - options, - &run, - NULL)) - return 1; + GNUNET_STRINGS_get_utf8_args (argc, argv, + &argc, &argv)) + return EXIT_INVALIDARGUMENT; + ret = GNUNET_PROGRAM_run ( + argc, + argv, + "taler-helper-auditor-deposits", + gettext_noop ( + "Audit Taler exchange database for deposit confirmation consistency"), + options, + &run, + NULL); + GNUNET_free_nz ((void *) argv); + if (GNUNET_SYSERR == ret) + return EXIT_INVALIDARGUMENT; + if (GNUNET_NO == ret) + return EXIT_SUCCESS; return global_ret; } |