diff options
author | Christian Grothoff <christian@grothoff.org> | 2024-08-23 14:15:18 +0200 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2024-08-23 14:16:03 +0200 |
commit | 9c21e22da9e2f411cfc3d2c900f4aabe2e3a88f1 (patch) | |
tree | 0a5bea2d721f5af7597d7930bf7a4b1fa7f042b3 /src | |
parent | a70c64d085bddfae6ebc07ebd9ceb796fbf03e62 (diff) |
split off taler-helper-auditor-transfer to reduce mixing up different things in helpers; also clean up its code
Diffstat (limited to 'src')
-rw-r--r-- | src/auditor/.gitignore | 1 | ||||
-rw-r--r-- | src/auditor/Makefile.am | 15 | ||||
-rw-r--r-- | src/auditor/taler-auditor-httpd_wire-out-inconsistency-get.c | 69 | ||||
-rw-r--r-- | src/auditor/taler-helper-auditor-transfer.c | 567 | ||||
-rw-r--r-- | src/auditor/taler-helper-auditor-wire-debit.c | 1082 | ||||
-rw-r--r-- | src/auditordb/0002-auditor_wire_out_inconsistency.sql | 7 | ||||
-rw-r--r-- | src/auditordb/pg_get_wire_format_inconsistency.c | 52 | ||||
-rw-r--r-- | src/auditordb/pg_get_wire_out_inconsistency.c | 78 | ||||
-rw-r--r-- | src/auditordb/pg_insert_wire_format_inconsistency.c | 10 | ||||
-rw-r--r-- | src/auditordb/pg_insert_wire_out_inconsistency.c | 21 | ||||
-rw-r--r-- | src/include/taler_auditordb_plugin.h | 6 |
11 files changed, 1057 insertions, 851 deletions
diff --git a/src/auditor/.gitignore b/src/auditor/.gitignore index e7812f058..a0bae7d8b 100644 --- a/src/auditor/.gitignore +++ b/src/auditor/.gitignore @@ -29,3 +29,4 @@ generate-kyc-basedb.conf.edited generate-auditor-basedb.conf.edited wallet.wdb libeufin-bank.pid +taler-helper-auditor-transfer diff --git a/src/auditor/Makefile.am b/src/auditor/Makefile.am index 622794b78..1a78cb3a2 100644 --- a/src/auditor/Makefile.am +++ b/src/auditor/Makefile.am @@ -23,6 +23,7 @@ bin_PROGRAMS = \ taler-helper-auditor-deposits \ taler-helper-auditor-purses \ taler-helper-auditor-reserves \ + taler-helper-auditor-transfer \ taler-helper-auditor-wire-credit \ taler-helper-auditor-wire-debit @@ -141,7 +142,19 @@ taler_helper_auditor_reserves_LDADD = \ -lgnunetutil \ $(XLIB) - +taler_helper_auditor_transfer_SOURCES = \ + taler-helper-auditor-transfer.c +taler_helper_auditor_transfer_LDADD = \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/util/libtalerutil.la \ + $(top_builddir)/src/json/libtalerjson.la \ + $(top_builddir)/src/exchangedb/libtalerexchangedb.la \ + $(top_builddir)/src/auditordb/libtalerauditordb.la \ + libauditorreport.la \ + -ljansson \ + -lgnunetjson \ + -lgnunetutil \ + $(XLIB) taler_helper_auditor_wire_credit_SOURCES = \ taler-helper-auditor-wire-credit.c diff --git a/src/auditor/taler-auditor-httpd_wire-out-inconsistency-get.c b/src/auditor/taler-auditor-httpd_wire-out-inconsistency-get.c index 63bcc854b..c7cddceb2 100644 --- a/src/auditor/taler-auditor-httpd_wire-out-inconsistency-get.c +++ b/src/auditor/taler-auditor-httpd_wire-out-inconsistency-get.c @@ -13,8 +13,6 @@ You should have received a copy of the GNU General Public License along with TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ - - #include "platform.h" #include <gnunet/gnunet_util_lib.h> #include <gnunet/gnunet_json_lib.h> @@ -26,38 +24,41 @@ #include "taler-auditor-httpd.h" #include "taler-auditor-httpd_wire-out-inconsistency-get.h" + /** -* Add wire-out-inconsistency to the list. -* -* @param[in,out] cls a `json_t *` array to extend -* @param serial_id location of the @a dc in the database -* @param dc struct of inconsistencies -* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop iterating -*/ + * Add wire-out-inconsistency to the list. + * + * @param[in,out] cls a `json_t *` array to extend + * @param dc struct of inconsistencies + * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop iterating + */ static enum GNUNET_GenericReturnValue process_wire_out_inconsistency ( void *cls, - uint64_t serial_id, const struct TALER_AUDITORDB_WireOutInconsistency *dc) { json_t *list = cls; json_t *obj; obj = GNUNET_JSON_PACK ( - - GNUNET_JSON_pack_int64 ("row_id", serial_id), - GNUNET_JSON_pack_string ("destination_account", dc->destination_account), - TALER_JSON_pack_amount ("expected", &dc->expected), - TALER_JSON_pack_amount ("claimed", &dc->claimed), - GNUNET_JSON_pack_bool ("suppressed", dc->suppressed) - - + GNUNET_JSON_pack_int64 ("row_id", + dc->row_id), + GNUNET_JSON_pack_string ("destination_account", + dc->destination_account), + GNUNET_JSON_pack_int64 ("wire_out_row_id", + dc->wire_out_row_id), + GNUNET_JSON_pack_string ("diagnostic", + dc->diagnostic), + TALER_JSON_pack_amount ("expected", + &dc->expected), + TALER_JSON_pack_amount ("claimed", + &dc->claimed), + GNUNET_JSON_pack_bool ("suppressed", + dc->suppressed) ); GNUNET_break (0 == json_array_append_new (list, obj)); - - return GNUNET_OK; } @@ -73,6 +74,9 @@ TAH_WIRE_OUT_INCONSISTENCY_handler_get ( { json_t *ja; enum GNUNET_DB_QueryStatus qs; + int64_t limit = -20; + uint64_t offset; + enum TALER_EXCHANGE_YesNoAll return_suppressed; (void) rh; (void) connection_cls; @@ -87,39 +91,28 @@ TAH_WIRE_OUT_INCONSISTENCY_handler_get ( TALER_EC_GENERIC_DB_SETUP_FAILED, NULL); } - ja = json_array (); - GNUNET_break (NULL != ja); - - int64_t limit = -20; - uint64_t offset; - TALER_MHD_parse_request_snumber (connection, "limit", &limit); - if (limit < 0) offset = INT64_MAX; else offset = 0; - TALER_MHD_parse_request_number (connection, "offset", &offset); - bool return_suppressed = false; - const char *ret_s = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "return_suppressed"); - if (ret_s != NULL && strcmp (ret_s, "true") == 0) - { - return_suppressed = true; - } - + TALER_MHD_parse_request_yna (connection, + "return_suppressed", + false, + &return_suppressed); + ja = json_array (); + GNUNET_break (NULL != ja); qs = TAH_plugin->get_wire_out_inconsistency ( TAH_plugin->cls, limit, offset, - return_suppressed, + (TALER_EXCHANGE_YNA_NO != return_suppressed), &process_wire_out_inconsistency, ja); diff --git a/src/auditor/taler-helper-auditor-transfer.c b/src/auditor/taler-helper-auditor-transfer.c new file mode 100644 index 000000000..8663ad6f3 --- /dev/null +++ b/src/auditor/taler-helper-auditor-transfer.c @@ -0,0 +1,567 @@ +/* + This file is part of TALER + Copyright (C) 2017-2024 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file auditor/taler-helper-auditor-transfer.c + * @brief audits that deposits past due date are + * aggregated and have a matching wire transfer + * database. + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_auditordb_plugin.h" +#include "taler_exchangedb_lib.h" +#include "taler_json_lib.h" +#include "taler_signatures.h" +#include "report-lib.h" +#include "taler_dbevents.h" + + +/** + * Run in test mode. Exit when idle instead of + * going to sleep and waiting for more work. + */ +static int test_mode; + +/** + * Return value from main(). + */ +static int global_ret; + +/** + * State of the current database transaction with + * the auditor DB. + */ +static enum GNUNET_DB_QueryStatus global_qs; + +/** + * Last reserve_out / wire_out serial IDs seen. + */ +static TALER_ARL_DEF_PP (wire_batch_deposit_id); +static TALER_ARL_DEF_PP (wire_aggregation_id); + +/** + * Should we run checks that only work for exchange-internal audits? + */ +static int internal_checks; + +/** + * Database event handler to wake us up again. + */ +static struct GNUNET_DB_EventHandler *eh; + +/** + * The auditors's configuration. + */ +static const struct GNUNET_CONFIGURATION_Handle *cfg; + + +/** + * Task run on shutdown. + * + * @param cls NULL + */ +static void +do_shutdown (void *cls) +{ + (void) cls; + if (NULL != eh) + { + TALER_ARL_adb->event_listen_cancel (eh); + eh = NULL; + } + TALER_ARL_done (); + TALER_EXCHANGEDB_unload_accounts (); + TALER_ARL_cfg = NULL; +} + + +/** + * Start the database transactions and begin the audit. + * + * @return false on failure + */ +static bool +begin_transaction (void); + + +/** + * Commit the transaction, checkpointing our progress in the auditor DB. + * + * @param qs transaction status so far + * @return transaction status code + */ +static void +commit_transaction (enum GNUNET_DB_QueryStatus qs) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Transaction logic ended with status %d\n", + qs); + if (qs < 0) + goto handle_db_error; + qs = TALER_ARL_adb->update_auditor_progress ( + TALER_ARL_adb->cls, + TALER_ARL_SET_PP (wire_batch_deposit_id), + TALER_ARL_SET_PP (wire_aggregation_id), + NULL); + if (0 > qs) + goto handle_db_error; + qs = TALER_ARL_adb->insert_auditor_progress ( + TALER_ARL_adb->cls, + TALER_ARL_SET_PP (wire_batch_deposit_id), + TALER_ARL_SET_PP (wire_aggregation_id), + NULL); + if (0 > qs) + goto handle_db_error; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Concluded audit step at %llu/%llu\n", + (unsigned long long) TALER_ARL_USE_PP (wire_aggregation_id), + (unsigned long long) TALER_ARL_USE_PP (wire_batch_deposit_id)); + qs = TALER_ARL_edb->commit (TALER_ARL_edb->cls); + if (0 > qs) + goto handle_db_error; + qs = TALER_ARL_adb->commit (TALER_ARL_adb->cls); + if (0 > qs) + goto handle_db_error; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Transaction concluded!\n"); + if (1 == test_mode) + GNUNET_SCHEDULER_shutdown (); + return; +handle_db_error: + TALER_ARL_adb->rollback (TALER_ARL_adb->cls); + TALER_ARL_edb->rollback (TALER_ARL_edb->cls); + if (GNUNET_DB_STATUS_HARD_ERROR != qs) + { + for (unsigned int max_retries = 3; max_retries>0; max_retries--) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Trying again (%u attempts left)\n", + max_retries); + if (begin_transaction ()) + return; + } + } + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Hard database error, terminating\n"); + GNUNET_SCHEDULER_shutdown (); +} + + +/** + * Closure for import_wire_missing_cb(). + */ +struct ImportMissingWireContext +{ + /** + * Set to maximum row ID encountered. + */ + uint64_t max_batch_deposit_uuid; + + /** + * Set to database errors in callback. + */ + enum GNUNET_DB_QueryStatus err; +}; + + +/** + * Function called on deposits that need to be checked for their + * wire transfer. + * + * @param cls closure, points to a `struct ImportMissingWireContext` + * @param batch_deposit_serial_id serial of the entry in the batch deposits table + * @param total_amount value of the missing deposits, including fee + * @param wire_target_h_payto where should the funds be wired + * @param deadline what was the earliest requested wire transfer deadline + */ +static void +import_wire_missing_cb ( + void *cls, + uint64_t batch_deposit_serial_id, + const struct TALER_Amount *total_amount, + const struct TALER_PaytoHashP *wire_target_h_payto, + struct GNUNET_TIME_Timestamp deadline) +{ + struct ImportMissingWireContext *wc = cls; + enum GNUNET_DB_QueryStatus qs; + + if (wc->err < 0) + return; /* already failed */ + GNUNET_assert (batch_deposit_serial_id > wc->max_batch_deposit_uuid); + wc->max_batch_deposit_uuid = batch_deposit_serial_id; + qs = TALER_ARL_adb->insert_pending_deposit ( + TALER_ARL_adb->cls, + batch_deposit_serial_id, + wire_target_h_payto, + total_amount, + deadline); + if (qs < 0) + wc->err = qs; +} + + +/** + * Closure for #clear_finished_transfer_cb(). + */ +struct AggregationContext +{ + /** + * Set to maximum row ID encountered. + */ + uint64_t max_aggregation_serial; + + /** + * Set to database errors in callback. + */ + enum GNUNET_DB_QueryStatus err; +}; + + +/** + * Function called on aggregations that were done for + * a (batch) deposit. + * + * @param cls closure + * @param tracking_serial_id where in the table are we + * @param batch_deposit_serial_id which batch deposit was aggregated + */ +static void +clear_finished_transfer_cb ( + void *cls, + uint64_t tracking_serial_id, + uint64_t batch_deposit_serial_id) +{ + struct AggregationContext *ac = cls; + enum GNUNET_DB_QueryStatus qs; + + if (0 > ac->err) + return; /* already failed */ + GNUNET_assert (ac->max_aggregation_serial < tracking_serial_id); + ac->max_aggregation_serial = tracking_serial_id; + qs = TALER_ARL_adb->delete_pending_deposit ( + TALER_ARL_adb->cls, + batch_deposit_serial_id); + if (0 == qs) + { + /* Aggregated something twice or other error, report! */ + GNUNET_break (0); + // FIXME: report more nicely! + } + if (0 > qs) + ac->err = qs; +} + + +/** + * Checks for wire transfers that should have happened. + * + * @return false on failure + */ +static bool +check_for_required_transfers (void) +{ + enum GNUNET_DB_QueryStatus qs; + struct ImportMissingWireContext wc = { + .max_batch_deposit_uuid = TALER_ARL_USE_PP (wire_batch_deposit_id), + .err = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT + }; + + qs = TALER_ARL_edb->select_batch_deposits_missing_wire ( + TALER_ARL_edb->cls, + TALER_ARL_USE_PP (wire_batch_deposit_id), + &import_wire_missing_cb, + &wc); + if ((0 > qs) || (0 > wc.err)) + { + GNUNET_break (0); + GNUNET_break ((GNUNET_DB_STATUS_SOFT_ERROR == qs) || + (GNUNET_DB_STATUS_SOFT_ERROR == wc.err)); + global_ret = EXIT_FAILURE; + GNUNET_SCHEDULER_shutdown (); + return false; + } + TALER_ARL_USE_PP (wire_batch_deposit_id) = wc.max_batch_deposit_uuid; + return true; +} + + +/** + * Checks that all wire transfers that should have happened + * (based on deposits) have indeed happened. + * + * @return false on failure + */ +static bool +check_for_completed_transfers (void) +{ + struct AggregationContext ac = { + .max_aggregation_serial = TALER_ARL_USE_PP (wire_aggregation_id), + .err = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT + }; + enum GNUNET_DB_QueryStatus qs; + + qs = TALER_ARL_edb->select_aggregations_above_serial ( + TALER_ARL_edb->cls, + TALER_ARL_USE_PP (wire_aggregation_id), + &clear_finished_transfer_cb, + &ac); + if ( (0 > qs) || (0 > ac.err) ) + { + GNUNET_break (0); + GNUNET_break ((GNUNET_DB_STATUS_SOFT_ERROR == qs) || + (GNUNET_DB_STATUS_SOFT_ERROR == ac.err)); + global_ret = EXIT_FAILURE; + GNUNET_SCHEDULER_shutdown (); + return false; + } + TALER_ARL_USE_PP (wire_aggregation_id) = ac.max_aggregation_serial; + return true; +} + + +/** + * Start the database transactions and begin the audit. + * + * @return true on success + */ +static bool +begin_transaction (void) +{ + enum GNUNET_DB_QueryStatus qs; + + if (GNUNET_SYSERR == + TALER_ARL_edb->preflight (TALER_ARL_edb->cls)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to initialize exchange database connection.\n"); + return false; + } + if (GNUNET_SYSERR == + TALER_ARL_adb->preflight (TALER_ARL_adb->cls)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to initialize auditor database session.\n"); + return false; + } + global_qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; + if (GNUNET_OK != + TALER_ARL_adb->start (TALER_ARL_adb->cls)) + { + GNUNET_break (0); + return false; + } + TALER_ARL_edb->preflight (TALER_ARL_edb->cls); + if (GNUNET_OK != + TALER_ARL_edb->start (TALER_ARL_edb->cls, + "transfer auditor")) + { + GNUNET_break (0); + TALER_ARL_adb->rollback (TALER_ARL_adb->cls); + return false; + } + qs = TALER_ARL_adb->get_auditor_progress ( + TALER_ARL_adb->cls, + TALER_ARL_GET_PP (wire_batch_deposit_id), + TALER_ARL_GET_PP (wire_aggregation_id), + NULL); + if (0 > qs) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + TALER_ARL_adb->rollback (TALER_ARL_adb->cls); + TALER_ARL_edb->rollback (TALER_ARL_edb->cls); + return false; + } + + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + { + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "First analysis of with transfer auditor, starting audit from scratch\n"); + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Resuming transfer audit at %llu / %llu\n", + (unsigned long long) TALER_ARL_USE_PP (wire_batch_deposit_id), + (unsigned long long) TALER_ARL_USE_PP (wire_aggregation_id)); + } + + { + bool ok; + + ok = check_for_required_transfers (); + if (ok) + ok = check_for_completed_transfers (); + commit_transaction (global_qs); + if (test_mode) + { + GNUNET_SCHEDULER_shutdown (); + return ok; + } + return ok; + } +} + + +/** + * 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) +{ + (void) cls; + (void) extra; + (void) extra_size; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Received notification to wake transfer helper\n"); + if (! begin_transaction ()) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Audit failed\n"); + GNUNET_break (0); + global_ret = EXIT_FAILURE; + GNUNET_SCHEDULER_shutdown (); + return; + } +} + + +/** + * Main function that will be run. + * + * @param cls closure + * @param args remaining command-line arguments + * @param cfgfile name of the configuration file used (for saving, can be NULL!) + * @param c configuration + */ +static void +run (void *cls, + char *const *args, + const char *cfgfile, + const struct GNUNET_CONFIGURATION_Handle *c) +{ + (void) cls; + (void) args; + (void) cfgfile; + cfg = c; + if (GNUNET_OK != + TALER_ARL_init (c)) + { + global_ret = EXIT_FAILURE; + return; + } + GNUNET_SCHEDULER_add_shutdown (&do_shutdown, + NULL); + if (GNUNET_OK != + TALER_EXCHANGEDB_load_accounts (TALER_ARL_cfg, + TALER_EXCHANGEDB_ALO_DEBIT + | TALER_EXCHANGEDB_ALO_CREDIT + | TALER_EXCHANGEDB_ALO_AUTHDATA)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "No bank accounts configured\n"); + global_ret = EXIT_NOTCONFIGURED; + GNUNET_SCHEDULER_shutdown (); + return; + } + if (0 == test_mode) + { + // FIXME: use different event type in the future! + struct GNUNET_DB_EventHeaderP es = { + .size = htons (sizeof (es)), + .type = htons (TALER_DBEVENT_EXCHANGE_AUDITOR_WAKE_HELPER_WIRE) + }; + + eh = TALER_ARL_adb->event_listen (TALER_ARL_adb->cls, + &es, + GNUNET_TIME_UNIT_FOREVER_REL, + &db_notify, + NULL); + GNUNET_assert (NULL != eh); + } + if (! begin_transaction ()) + { + GNUNET_break (0); + global_ret = EXIT_FAILURE; + GNUNET_SCHEDULER_shutdown (); + return; + } +} + + +/** + * The main function of the wire auditing tool. Checks that + * the exchange's records of wire transfers match that of + * the wire gateway. + * + * @param argc number of arguments from the command line + * @param argv command line arguments + * @return 0 ok, 1 on error + */ +int +main (int argc, + char *const *argv) +{ + const struct GNUNET_GETOPT_CommandLineOption options[] = { + 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 (); + if (GNUNET_OK != + GNUNET_STRINGS_get_utf8_args (argc, argv, + &argc, &argv)) + return EXIT_INVALIDARGUMENT; + ret = GNUNET_PROGRAM_run ( + argc, + argv, + "taler-helper-auditor-transfer", + gettext_noop ( + "Audit exchange database for consistency of transfers with respect to deposit deadlines"), + 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; +} + + +/* end of taler-helper-auditor-transfer.c */ diff --git a/src/auditor/taler-helper-auditor-wire-debit.c b/src/auditor/taler-helper-auditor-wire-debit.c index c9b1157c8..105867bf2 100644 --- a/src/auditor/taler-helper-auditor-wire-debit.c +++ b/src/auditor/taler-helper-auditor-wire-debit.c @@ -21,9 +21,14 @@ * @author Özgür Kesim * * - We check that the outgoing wire transfers match those - * given in the 'wire_out' and 'reserve_closures' tables - * - Finally, we check that all wire transfers that should have been made, - * were actually made + * given in the 'wire_out' and 'reserve_closures' tables; + * any outgoing transfer MUST have a prior justification, + * so if one is missing we flag it (and never remove it). + * - We check that all wire transfers that should + * have been made, were actually made. If any were not made, + * we flag those, but may remove those flags if we later + * find that the wire transfers were made (wire transfers + * could be delayed due to AML/KYC or core-banking issues). */ #include "platform.h" #include <gnunet/gnunet_util_lib.h> @@ -45,6 +50,12 @@ #define GRACE_PERIOD GNUNET_TIME_UNIT_HOURS /** + * Maximum number of wire transfers we process per + * (database) transaction. + */ +#define MAX_PER_TRANSACTION 1024 + +/** * How much do we allow the bank and the exchange to disagree about * timestamps? Should be sufficiently large to avoid bogus reports from deltas * created by imperfect clock synchronization and network delay. @@ -54,6 +65,13 @@ /** + * How long do we long-poll for bank wire transfers? + */ +#define LONG_POLL_MAX GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_HOURS, \ + 1) + + +/** * Run in test mode. Exit when idle instead of * going to sleep and waiting for more work. */ @@ -181,8 +199,6 @@ static struct WireAccount *wa_tail; * Last reserve_out / wire_out serial IDs seen. */ static TALER_ARL_DEF_PP (wire_reserve_close_id); -static TALER_ARL_DEF_PP (wire_batch_deposit_id); -static TALER_ARL_DEF_PP (wire_aggregation_id); /** * Amount that is considered "tiny" @@ -210,9 +226,10 @@ static TALER_ARL_DEF_AB (total_amount_lag); static TALER_ARL_DEF_AB (total_closure_amount_lag); /** - * Total amount affected by wire format troubles. + * Total amount affected by duplicate wire transfer + * subjects. */ -static TALER_ARL_DEF_AB (total_wire_format_amount); +static TALER_ARL_DEF_AB (wire_debit_duplicate_transfer_subject_total); /** * Total amount debited to exchange accounts. @@ -502,7 +519,7 @@ commit (enum GNUNET_DB_QueryStatus qs) TALER_ARL_SET_AB (total_bad_amount_out_minus), TALER_ARL_SET_AB (total_amount_lag), TALER_ARL_SET_AB (total_closure_amount_lag), - TALER_ARL_SET_AB (total_wire_format_amount), + TALER_ARL_SET_AB (wire_debit_duplicate_transfer_subject_total), TALER_ARL_SET_AB (total_wire_out), NULL); if (0 > qs) @@ -515,7 +532,7 @@ commit (enum GNUNET_DB_QueryStatus qs) TALER_ARL_SET_AB (total_bad_amount_out_minus), TALER_ARL_SET_AB (total_amount_lag), TALER_ARL_SET_AB (total_closure_amount_lag), - TALER_ARL_SET_AB (total_wire_format_amount), + TALER_ARL_SET_AB (wire_debit_duplicate_transfer_subject_total), TALER_ARL_SET_AB (total_wire_out), NULL); if (0 > qs) @@ -554,23 +571,18 @@ commit (enum GNUNET_DB_QueryStatus qs) qs = TALER_ARL_adb->update_auditor_progress ( TALER_ARL_adb->cls, TALER_ARL_SET_PP (wire_reserve_close_id), - TALER_ARL_SET_PP (wire_batch_deposit_id), - TALER_ARL_SET_PP (wire_aggregation_id), NULL); if (0 > qs) goto handle_db_error; qs = TALER_ARL_adb->insert_auditor_progress ( TALER_ARL_adb->cls, TALER_ARL_SET_PP (wire_reserve_close_id), - TALER_ARL_SET_PP (wire_batch_deposit_id), - TALER_ARL_SET_PP (wire_aggregation_id), NULL); if (0 > qs) goto handle_db_error; GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Concluded audit step at %llu/%llu\n", - (unsigned long long) TALER_ARL_USE_PP (wire_aggregation_id), - (unsigned long long) TALER_ARL_USE_PP (wire_batch_deposit_id)); + "Concluded audit step at %llu\n", + (unsigned long long) TALER_ARL_USE_PP (wire_reserve_close_id)); qs = TALER_ARL_edb->commit (TALER_ARL_edb->cls); if (0 > qs) goto handle_db_error; @@ -599,440 +611,335 @@ handle_db_error: } -/* ******************** Analyze required outgoing transfers ******************** */ - -/** - * Closure for import_wire_missing_cb(). - */ -struct ImportMissingWireContext -{ - /** - * Set to maximum row ID encountered. - */ - uint64_t max_batch_deposit_uuid; - - /** - * Set to database errors in callback. - */ - enum GNUNET_DB_QueryStatus err; -}; - - /** - * Function called on deposits that need to be checked for their - * wire transfer. + * Check that @a want is within #TIME_TOLERANCE of @a have. + * Otherwise report an inconsistency in row @a rowid of @a table. * - * @param cls closure, points to a `struct ImportMissingWireContext` - * @param batch_deposit_serial_id serial of the entry in the batch deposits table - * @param total_amount value of the missing deposits, including fee - * @param wire_target_h_payto where should the funds be wired - * @param deadline what was the earliest requested wire transfer deadline + * @param table where is the inconsistency (if any) + * @param rowid what is the row + * @param want what is the expected time + * @param have what is the time we got + * @return true on success, false to abort */ -static void -import_wire_missing_cb ( - void *cls, - uint64_t batch_deposit_serial_id, - const struct TALER_Amount *total_amount, - const struct TALER_PaytoHashP *wire_target_h_payto, - struct GNUNET_TIME_Timestamp deadline) +static bool +check_time_difference (const char *table, + uint64_t rowid, + struct GNUNET_TIME_Timestamp want, + struct GNUNET_TIME_Timestamp have) { - struct ImportMissingWireContext *wc = cls; - enum GNUNET_DB_QueryStatus qs; - - if (wc->err < 0) - return; /* already failed */ - GNUNET_assert (batch_deposit_serial_id > wc->max_batch_deposit_uuid); - wc->max_batch_deposit_uuid = batch_deposit_serial_id; - qs = TALER_ARL_adb->insert_pending_deposit ( - TALER_ARL_adb->cls, - batch_deposit_serial_id, - wire_target_h_payto, - total_amount, - deadline); - if (qs < 0) - wc->err = qs; -} - + struct GNUNET_TIME_Relative delta; + char *details; -/** - * Information about a delayed wire transfer and the possible - * reasons for the delay. - */ -struct ReasonDetail -{ - /** - * Batch deposit that may be lacking a wire transfer. - */ - uint64_t batch_deposit_serial_id; + if (GNUNET_TIME_timestamp_cmp (have, >, want)) + delta = GNUNET_TIME_absolute_get_difference (want.abs_time, + have.abs_time); + else + delta = GNUNET_TIME_absolute_get_difference (have.abs_time, + want.abs_time); + if (GNUNET_TIME_relative_cmp (delta, + <=, + TIME_TOLERANCE)) + return true; - /** - * Total amount that should have been transferred. - */ - struct TALER_Amount total_amount; + GNUNET_asprintf (&details, + "execution date mismatch (%s)", + GNUNET_TIME_relative2s (delta, + true)); + { + struct TALER_AUDITORDB_RowMinorInconsistencies rmi = { + .row_id = rowid, + .diagnostic = details, + .row_table = (char *) table + }; + enum GNUNET_DB_QueryStatus qs; - /** - * Earliest deadline for an expected transfer to the account. - */ - struct GNUNET_TIME_Timestamp deadline; + qs = TALER_ARL_adb->insert_row_minor_inconsistencies ( + TALER_ARL_adb->cls, + &rmi); - /** - * Target account hash. - */ - struct TALER_PaytoHashP wire_target_h_payto; + if (qs < 0) + { + global_qs = qs; + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + GNUNET_free (details); + return false; + } + } + GNUNET_free (details); + return true; +} -}; /** - * Closure for report_wire_missing_cb(). + * Closure for #check_rc_matches */ -struct ReportMissingWireContext +struct CheckMatchContext { - /** - * Map from wire_target_h_payto to `struct ReasonDetail`. - */ - struct GNUNET_CONTAINER_MultiShortmap *map; /** - * Set to database errors in callback. - */ - enum GNUNET_DB_QueryStatus err; -}; - - -/** - * Closure for #clear_finished_transfer_cb(). - */ -struct AggregationContext -{ - /** - * Set to maximum row ID encountered. + * Reserve operation looking for a match */ - uint64_t max_aggregation_serial; + const struct ReserveOutInfo *roi; /** - * Set to database errors in callback. + * Set to true if we found a match. */ - enum GNUNET_DB_QueryStatus err; + bool found; }; /** - * Free memory allocated in @a value. - * - * @param cls unused - * @param key unused - * @param value must be a `struct ReasonDetail` - * @return #GNUNET_YES if we should continue to - * iterate, - * #GNUNET_NO if not. - */ -static enum GNUNET_GenericReturnValue -free_report_entry (void *cls, - const struct GNUNET_ShortHashCode *key, - void *value) -{ - struct ReasonDetail *rd = value; - - GNUNET_free (rd); - return GNUNET_YES; -} - - -/** - * We had an entry in our map of wire transfers that - * should have been performed. Generate report. + * Check if any of the reserve closures match the given wire transfer. * - * @param cls unused - * @param key unused - * @param value must be a `struct ReasonDetail` - * @return #GNUNET_YES if we should continue to - * iterate, - * #GNUNET_NO if not. + * @param[in,out] cls a `struct CheckMatchContext` + * @param key key of @a value in #reserve_closures + * @param value a `struct ReserveClosure` */ static enum GNUNET_GenericReturnValue -generate_report (void *cls, - const struct GNUNET_ShortHashCode *key, - void *value) +check_rc_matches (void *cls, + const struct GNUNET_HashCode *key, + void *value) { - struct ReasonDetail *rd = value; - - - /* For now, we simplify and only check that the - amount was tiny */ - if (0 > TALER_amount_cmp (&rd->total_amount, - &tiny_amount)) - return free_report_entry (cls, - key, - value); /* acceptable, amount was tiny */ + struct CheckMatchContext *ctx = cls; + struct ReserveClosure *rc = value; - // TODO: maybe split total_amount_lag up by category below? - TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_amount_lag), - &TALER_ARL_USE_AB (total_amount_lag), - &rd->total_amount); + if ((0 == GNUNET_memcmp (&ctx->roi->details.wtid, + &rc->wtid)) && + (0 == strcasecmp (rc->receiver_account, + ctx->roi->details.credit_account_uri)) && + (0 == TALER_amount_cmp (&rc->amount, + &ctx->roi->details.amount))) { - enum GNUNET_DB_QueryStatus qs; - - qs = TALER_ARL_adb->insert_pending_deposit ( - TALER_ARL_adb->cls, - rd->batch_deposit_serial_id, - &rd->wire_target_h_payto, - &rd->total_amount, - rd->deadline); - if (qs < 0) + if (! check_time_difference ("reserves_closures", + rc->rowid, + rc->execution_date, + ctx->roi->details.execution_date)) { - global_qs = qs; - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + free_rc (NULL, + key, + rc); return GNUNET_SYSERR; } + ctx->found = true; + free_rc (NULL, + key, + rc); + return GNUNET_NO; } - return free_report_entry (cls, - key, - value); -} - - -/** - * Function called on deposits that are past their due date - * and have not yet seen a wire transfer. - * - * @param cls closure, points to a `struct ReportMissingWireContext` - * @param batch_deposit_serial_id row in the database for which the wire transfer is missing - * @param total_amount value of the missing deposits, including fee - * @param wire_target_h_payto hash of payto-URI where the funds should have been wired - * @param deadline what was the earliest requested wire transfer deadline - */ -static void -report_wire_missing_cb ( - void *cls, - uint64_t batch_deposit_serial_id, - const struct TALER_Amount *total_amount, - const struct TALER_PaytoHashP *wire_target_h_payto, - struct GNUNET_TIME_Timestamp deadline) -{ - struct ReportMissingWireContext *rc = cls; - struct ReasonDetail *rd; - - rd = GNUNET_CONTAINER_multishortmap_get (rc->map, - &wire_target_h_payto->hash); - if (NULL == rd) - { - rd = GNUNET_new (struct ReasonDetail); - rd->batch_deposit_serial_id = batch_deposit_serial_id; - rd->wire_target_h_payto = *wire_target_h_payto; - rd->total_amount = *total_amount; - rd->deadline = deadline; - GNUNET_assert (GNUNET_YES == - GNUNET_CONTAINER_multishortmap_put ( - rc->map, - &wire_target_h_payto->hash, - rd, - GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); - } - else - { - TALER_ARL_amount_add (&rd->total_amount, - &rd->total_amount, - total_amount); - rd->deadline = GNUNET_TIME_timestamp_min (rd->deadline, - deadline); - } + return GNUNET_OK; } /** - * Function called on aggregations that were done for - * a (batch) deposit. + * Check if a profit drain operation justified the @a roi * - * @param cls closure - * @param tracking_serial_id where in the table are we - * @param batch_deposit_serial_id which batch deposit was aggregated - */ -static void -clear_finished_transfer_cb ( - void *cls, - uint64_t tracking_serial_id, - uint64_t batch_deposit_serial_id) -{ - struct AggregationContext *ac = cls; - enum GNUNET_DB_QueryStatus qs; - - if (0 > ac->err) - return; /* already failed */ - GNUNET_assert (ac->max_aggregation_serial < tracking_serial_id); - ac->max_aggregation_serial = tracking_serial_id; - qs = TALER_ARL_adb->delete_pending_deposit ( - TALER_ARL_adb->cls, - batch_deposit_serial_id); - if (0 == qs) - { - /* Aggregated something twice or other error, report! */ - GNUNET_break (0); - // FIXME: report more nicely! - } - if (0 > qs) - ac->err = qs; -} - - -/** - * Checks that all wire transfers that should have happened - * (based on deposits) have indeed happened. + * @param roi reserve out operation to check + * @return #GNUNET_YES if @a roi was justified by a profit drain, + * #GNUNET_NO of @a roi was not justified by a proft drain + * #GNUNET_SYSERR on database trouble */ -static void -check_for_required_transfers (void) +static enum GNUNET_GenericReturnValue +check_profit_drain (struct ReserveOutInfo *roi) { - struct ImportMissingWireContext wc = { - .max_batch_deposit_uuid = TALER_ARL_USE_PP (wire_batch_deposit_id), - .err = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT - }; - struct GNUNET_TIME_Absolute deadline; enum GNUNET_DB_QueryStatus qs; - struct ReportMissingWireContext rc = { - .err = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT - }; - struct AggregationContext ac = { - .max_aggregation_serial = TALER_ARL_USE_PP (wire_aggregation_id), - .err = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT - }; + uint64_t serial; + char *account_section; + char *payto_uri; + struct GNUNET_TIME_Timestamp request_timestamp; + struct TALER_Amount amount; + struct TALER_MasterSignatureP master_sig; - qs = TALER_ARL_edb->select_batch_deposits_missing_wire ( + qs = TALER_ARL_edb->get_drain_profit ( TALER_ARL_edb->cls, - TALER_ARL_USE_PP (wire_batch_deposit_id), - &import_wire_missing_cb, - &wc); - if ((0 > qs) || (0 > wc.err)) + &roi->details.wtid, + &serial, + &account_section, + &payto_uri, + &request_timestamp, + &amount, + &master_sig); + switch (qs) { + case GNUNET_DB_STATUS_HARD_ERROR: GNUNET_break (0); - GNUNET_break ((GNUNET_DB_STATUS_SOFT_ERROR == qs) || - (GNUNET_DB_STATUS_SOFT_ERROR == wc.err)); global_ret = EXIT_FAILURE; GNUNET_SCHEDULER_shutdown (); - return; - } - TALER_ARL_USE_PP (wire_batch_deposit_id) = wc.max_batch_deposit_uuid; - qs = TALER_ARL_edb->select_aggregations_above_serial ( - TALER_ARL_edb->cls, - TALER_ARL_USE_PP (wire_aggregation_id), - &clear_finished_transfer_cb, - &ac); - if ((0 > qs) || (0 > ac.err)) - { + return GNUNET_SYSERR; + case GNUNET_DB_STATUS_SOFT_ERROR: + /* should fail on commit later ... */ GNUNET_break (0); - GNUNET_break ((GNUNET_DB_STATUS_SOFT_ERROR == qs) || - (GNUNET_DB_STATUS_SOFT_ERROR == ac.err)); - global_ret = EXIT_FAILURE; - GNUNET_SCHEDULER_shutdown (); - return; + return GNUNET_SYSERR; + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + /* not a profit drain */ + return GNUNET_NO; + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + break; } - TALER_ARL_USE_PP (wire_aggregation_id) = ac.max_aggregation_serial; - /* Subtract #GRACE_PERIOD, so we can be a bit behind in processing - without immediately raising undue concern */ - deadline = GNUNET_TIME_absolute_subtract (GNUNET_TIME_absolute_get (), - GRACE_PERIOD); - rc.map = GNUNET_CONTAINER_multishortmap_create (1024, - GNUNET_NO); - qs = TALER_ARL_adb->select_pending_deposits ( - TALER_ARL_adb->cls, - deadline, - &report_wire_missing_cb, - &rc); - if ((0 > qs) || (0 > rc.err)) + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Profit drain of %s to %s found!\n", + TALER_amount2s (&amount), + payto_uri); + if (GNUNET_OK != + TALER_exchange_offline_profit_drain_verify ( + &roi->details.wtid, + request_timestamp, + &amount, + account_section, + payto_uri, + &TALER_ARL_master_pub, + &master_sig)) { + struct TALER_AUDITORDB_RowInconsistency ri = { + .row_id = roi->details.serial_id, + .row_table = "profit_drains", + .diagnostic = "invalid signature" + }; + GNUNET_break (0); - GNUNET_break ((GNUNET_DB_STATUS_SOFT_ERROR == qs) || - (GNUNET_DB_STATUS_SOFT_ERROR == rc.err)); - GNUNET_CONTAINER_multishortmap_iterate (rc.map, - &free_report_entry, - NULL); - GNUNET_CONTAINER_multishortmap_destroy (rc.map); - global_ret = EXIT_FAILURE; - GNUNET_SCHEDULER_shutdown (); - return; + qs = TALER_ARL_adb->insert_row_inconsistency ( + TALER_ARL_adb->cls, + &ri); + GNUNET_free (payto_uri); + GNUNET_free (account_section); + if (qs < 0) + { + global_qs = qs; + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + return GNUNET_SYSERR; + } + return GNUNET_NO; } - GNUNET_CONTAINER_multishortmap_iterate (rc.map, - &generate_report, - NULL); - GNUNET_CONTAINER_multishortmap_destroy (rc.map); - /* conclude with success */ - commit (global_qs); - if (test_mode) + GNUNET_free (account_section); + if (0 != + strcasecmp (payto_uri, + roi->details.credit_account_uri)) { - GNUNET_SCHEDULER_shutdown (); - return; - } -} - + struct TALER_AUDITORDB_WireOutInconsistency woi = { + .row_id = serial, + .destination_account = (char *) roi->details.credit_account_uri, + .diagnostic = "profit drain wired to invalid account", + .expected = roi->details.amount, + .claimed = zero, + }; -/* ***************************** Analyze reserves_out ************************ */ + qs = TALER_ARL_adb->insert_wire_out_inconsistency ( + TALER_ARL_adb->cls, + &woi); + if (qs < 0) + { + global_qs = qs; + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + GNUNET_free (payto_uri); + return GNUNET_SYSERR; + } + TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_bad_amount_out_plus), + &TALER_ARL_USE_AB (total_bad_amount_out_plus), + &amount); + GNUNET_free (payto_uri); + return GNUNET_YES; /* justified, kind-of */ + } + GNUNET_free (payto_uri); + if (0 != + TALER_amount_cmp (&amount, + &roi->details.amount)) + { + struct TALER_AUDITORDB_WireOutInconsistency woi = { + .row_id = roi->details.serial_id, + .destination_account = (char *) roi->details.credit_account_uri, + .diagnostic = "incorrect amount drained to correct account", + .expected = roi->details.amount, + .claimed = amount, + }; -/** - * Clean up after processing wire out data. - */ -static void -conclude_wire_out (void) -{ - GNUNET_CONTAINER_multihashmap_destroy (out_map); - out_map = NULL; - check_for_required_transfers (); + qs = TALER_ARL_adb->insert_wire_out_inconsistency ( + TALER_ARL_adb->cls, + &woi); + if (qs < 0) + { + global_qs = qs; + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + return GNUNET_SYSERR; + } + TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_bad_amount_out_minus), + &TALER_ARL_USE_AB (total_bad_amount_out_minus), + &roi->details.amount); + TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_bad_amount_out_plus), + &TALER_ARL_USE_AB (total_bad_amount_out_plus), + &amount); + return GNUNET_YES; /* justified, kind-of */ + } + /* profit drain was correct */ + TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_drained), + &TALER_ARL_USE_AB (total_drained), + &amount); + return GNUNET_YES; } /** - * Check that @a want is within #TIME_TOLERANCE of @a have. - * Otherwise report an inconsistency in row @a rowid of @a table. + * Check whether the given transfer was justified by a reserve closure or + * profit drain. If not, complain that we failed to match an entry from + * #out_map. This means a wire transfer was made without proper + * justification. * - * @param table where is the inconsistency (if any) - * @param rowid what is the row - * @param want what is the expected time - * @param have what is the time we got - * @return true on success, false to abort + * @param cls a `struct WireAccount` + * @param key unused key + * @param value the `struct ReserveOutInfo` to report + * @return #GNUNET_OK on success */ -static bool -check_time_difference (const char *table, - uint64_t rowid, - struct GNUNET_TIME_Timestamp want, - struct GNUNET_TIME_Timestamp have) +static enum GNUNET_GenericReturnValue +complain_out_not_found (void *cls, + const struct GNUNET_HashCode *key, + void *value) { - struct GNUNET_TIME_Relative delta; - char *details; + // struct WireAccount *wa = cls; + struct ReserveOutInfo *roi = value; + struct GNUNET_HashCode rkey; + struct CheckMatchContext ctx = { + .roi = roi, + .found = false + }; + enum GNUNET_GenericReturnValue ret; - if (GNUNET_TIME_timestamp_cmp (have, >, want)) - delta = GNUNET_TIME_absolute_get_difference (want.abs_time, - have.abs_time); - else - delta = GNUNET_TIME_absolute_get_difference (have.abs_time, - want.abs_time); - if (GNUNET_TIME_relative_cmp (delta, - <=, - TIME_TOLERANCE)) - return true; + (void) cls; + (void) key; + hash_rc (roi->details.credit_account_uri, + &roi->details.wtid, + &rkey); + GNUNET_CONTAINER_multihashmap_get_multiple (reserve_closures, + &rkey, + &check_rc_matches, + &ctx); + if (ctx.found) + return GNUNET_OK; + ret = check_profit_drain (roi); + if (GNUNET_NO != ret) + return ret; - GNUNET_asprintf (&details, - "execution date mismatch (%s)", - GNUNET_TIME_relative2s (delta, - true)); { - struct TALER_AUDITORDB_RowMinorInconsistencies rmi = { - .row_id = rowid, - .diagnostic = details, - .row_table = (char *) table + struct TALER_AUDITORDB_WireOutInconsistency woi = { + .row_id = roi->details.serial_id, + .destination_account = (char *) roi->details.credit_account_uri, + .diagnostic = "missing justification for outgoing wire transfer", + .expected = zero, + .claimed = roi->details.amount }; enum GNUNET_DB_QueryStatus qs; - qs = TALER_ARL_adb->insert_row_minor_inconsistencies ( + qs = TALER_ARL_adb->insert_wire_out_inconsistency ( TALER_ARL_adb->cls, - &rmi); - + &woi); if (qs < 0) { global_qs = qs; GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - GNUNET_free (details); - return false; + return GNUNET_SYSERR; } } - GNUNET_free (details); - return true; + TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_bad_amount_out_plus), + &TALER_ARL_USE_AB (total_bad_amount_out_plus), + &roi->details.amount); + return GNUNET_OK; } @@ -1049,17 +956,17 @@ check_time_difference (const char *table, * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop */ static enum GNUNET_GenericReturnValue -wire_out_cb (void *cls, - uint64_t rowid, - struct GNUNET_TIME_Timestamp date, - const struct TALER_WireTransferIdentifierRawP *wtid, - const char *payto_uri, - const struct TALER_Amount *amount) +wire_out_cb ( + void *cls, + uint64_t rowid, + struct GNUNET_TIME_Timestamp date, + const struct TALER_WireTransferIdentifierRawP *wtid, + const char *payto_uri, + const struct TALER_Amount *amount) { struct WireAccount *wa = cls; struct GNUNET_HashCode key; struct ReserveOutInfo *roi; - enum GNUNET_GenericReturnValue ret = GNUNET_OK; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Exchange wire OUT #%llu at %s of %s with WTID %s\n", @@ -1084,7 +991,7 @@ wire_out_cb (void *cls, struct TALER_AUDITORDB_WireOutInconsistency woi = { .row_id = rowid, .destination_account = (char *) payto_uri, - .diagnostic = "expected wire transfer missing", + .diagnostic = "expected outgoing wire transfer missing", .expected = *amount, .claimed = zero, }; @@ -1140,9 +1047,9 @@ wire_out_cb (void *cls, amount)) { struct TALER_AUDITORDB_WireOutInconsistency woi = { - .row_id = rowid, .destination_account = (char *) payto_uri, .diagnostic = "wire amount does not match", + .wire_out_row_id = rowid, .expected = *amount, .claimed = zero, }; @@ -1185,268 +1092,28 @@ wire_out_cb (void *cls, return GNUNET_OK; } - if (! check_time_difference ("wire_out", - rowid, - date, - roi->details.execution_date)) - ret = GNUNET_SYSERR; - GNUNET_assert (GNUNET_OK == - free_roi (NULL, - &key, - roi)); - wa->last_wire_out_serial_id = rowid + 1; - return ret; -} - - -/** - * Closure for #check_rc_matches - */ -struct CheckMatchContext -{ - - /** - * Reserve operation looking for a match - */ - const struct ReserveOutInfo *roi; - - /** - * Set to true if we found a match. - */ - bool found; -}; - - -/** - * Check if any of the reserve closures match the given wire transfer. - * - * @param[in,out] cls a `struct CheckMatchContext` - * @param key key of @a value in #reserve_closures - * @param value a `struct ReserveClosure` - */ -static enum GNUNET_GenericReturnValue -check_rc_matches (void *cls, - const struct GNUNET_HashCode *key, - void *value) -{ - struct CheckMatchContext *ctx = cls; - struct ReserveClosure *rc = value; - - if ((0 == GNUNET_memcmp (&ctx->roi->details.wtid, - &rc->wtid)) && - (0 == strcasecmp (rc->receiver_account, - ctx->roi->details.credit_account_uri)) && - (0 == TALER_amount_cmp (&rc->amount, - &ctx->roi->details.amount))) { - if (! check_time_difference ("reserves_closures", - rc->rowid, - rc->execution_date, - ctx->roi->details.execution_date)) - { - free_rc (NULL, - key, - rc); - return GNUNET_SYSERR; - } - ctx->found = true; - free_rc (NULL, - key, - rc); - return GNUNET_NO; - } - return GNUNET_OK; -} - - -/** - * Check whether the given transfer was justified by a reserve closure or - * profit drain. If not, complain that we failed to match an entry from - * #out_map. This means a wire transfer was made without proper - * justification. - * - * @param cls a `struct WireAccount` - * @param key unused key - * @param value the `struct ReserveOutInfo` to report - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -complain_out_not_found (void *cls, - const struct GNUNET_HashCode *key, - void *value) -{ - // struct WireAccount *wa = cls; - struct ReserveOutInfo *roi = value; - struct GNUNET_HashCode rkey; - struct CheckMatchContext ctx = { - .roi = roi, - .found = false - }; + enum GNUNET_GenericReturnValue ret; - (void) key; - hash_rc (roi->details.credit_account_uri, - &roi->details.wtid, - &rkey); - GNUNET_CONTAINER_multihashmap_get_multiple (reserve_closures, - &rkey, - &check_rc_matches, - &ctx); - if (ctx.found) - return GNUNET_OK; - /* check for profit drain */ - { - enum GNUNET_DB_QueryStatus qs; - uint64_t serial; - char *account_section; - char *payto_uri; - struct GNUNET_TIME_Timestamp request_timestamp; - struct TALER_Amount amount; - struct TALER_MasterSignatureP master_sig; - - qs = TALER_ARL_edb->get_drain_profit (TALER_ARL_edb->cls, - &roi->details.wtid, - &serial, - &account_section, - &payto_uri, - &request_timestamp, - &amount, - &master_sig); - switch (qs) + if (! check_time_difference ("wire_out", + rowid, + date, + roi->details.execution_date)) { - case GNUNET_DB_STATUS_HARD_ERROR: - GNUNET_break (0); - global_ret = EXIT_FAILURE; - GNUNET_SCHEDULER_shutdown (); - return GNUNET_SYSERR; - case GNUNET_DB_STATUS_SOFT_ERROR: - /* should fail on commit later ... */ - GNUNET_break (0); - return GNUNET_NO; - case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - /* not a profit drain */ - break; - case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Profit drain of %s to %s found!\n", - TALER_amount2s (&amount), - payto_uri); - if (GNUNET_OK != - TALER_exchange_offline_profit_drain_verify ( - &roi->details.wtid, - request_timestamp, - &amount, - account_section, - payto_uri, - &TALER_ARL_master_pub, - &master_sig)) - { - struct TALER_AUDITORDB_RowInconsistency ri = { - .row_id = roi->details.serial_id, - .row_table = "profit_drains", - .diagnostic = "invalid signature" - }; - - GNUNET_break (0); - qs = TALER_ARL_adb->insert_row_inconsistency ( - TALER_ARL_adb->cls, - &ri); - if (qs < 0) - { - global_qs = qs; - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - return GNUNET_SYSERR; - } - TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_bad_amount_out_plus), - &TALER_ARL_USE_AB (total_bad_amount_out_plus), - &amount); - } - else if (0 != - strcasecmp (payto_uri, - roi->details.credit_account_uri)) - { - struct TALER_AUDITORDB_WireOutInconsistency woi = { - .row_id = serial, - .destination_account = (char *) roi->details.credit_account_uri, - .diagnostic = "amount wired to invalid account", - .expected = roi->details.amount, - .claimed = zero, - }; - - qs = TALER_ARL_adb->insert_wire_out_inconsistency ( - TALER_ARL_adb->cls, - &woi); - if (qs < 0) - { - global_qs = qs; - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - return GNUNET_SYSERR; - } - TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_bad_amount_out_plus), - &TALER_ARL_USE_AB (total_bad_amount_out_plus), - &amount); - } - else if (0 != - TALER_amount_cmp (&amount, - &roi->details.amount)) - { - struct TALER_AUDITORDB_WireOutInconsistency woi = { - .row_id = roi->details.serial_id, - .destination_account = (char *) roi->details.credit_account_uri, - .diagnostic = "incorrect amount to correct account", - .expected = roi->details.amount, - .claimed = amount, - }; - - qs = TALER_ARL_adb->insert_wire_out_inconsistency ( - TALER_ARL_adb->cls, - &woi); - if (qs < 0) - { - global_qs = qs; - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - return GNUNET_SYSERR; - } - TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_bad_amount_out_minus), - &TALER_ARL_USE_AB (total_bad_amount_out_minus), - &roi->details.amount); - TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_bad_amount_out_plus), - &TALER_ARL_USE_AB (total_bad_amount_out_plus), - &amount); - } - GNUNET_free (account_section); - GNUNET_free (payto_uri); - /* profit drain was correct */ - TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_drained), - &TALER_ARL_USE_AB (total_drained), - &amount); - return GNUNET_OK; + /* We had a database error, fail */ + ret = GNUNET_SYSERR; } - } - - { - struct TALER_AUDITORDB_WireOutInconsistency woi = { - .row_id = roi->details.serial_id, - .destination_account = (char *) roi->details.credit_account_uri, - .diagnostic = "missing justification for outgoing wire transfer", - .expected = zero, - .claimed =roi->details.amount - }; - enum GNUNET_DB_QueryStatus qs; - - qs = TALER_ARL_adb->insert_wire_out_inconsistency ( - TALER_ARL_adb->cls, - &woi); - if (qs < 0) + else { - global_qs = qs; - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - return GNUNET_SYSERR; + ret = GNUNET_OK; } + GNUNET_assert (GNUNET_OK == + free_roi (NULL, + &key, + roi)); + wa->last_wire_out_serial_id = rowid + 1; + return ret; } - TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_bad_amount_out_plus), - &TALER_ARL_USE_AB (total_bad_amount_out_plus), - &roi->details.amount); - return GNUNET_OK; } @@ -1455,17 +1122,17 @@ complain_out_not_found (void *cls, * the DEBIT transactions this time, and then verify that all of them are * justified by 'reserves_out'. * - * @param cls `struct WireAccount` with a wire account list to process + * @param[in,out] wa wire account list to process */ static void -process_debits (void *cls); +process_debits (struct WireAccount *wa); /** * Go over the "wire_out" table of the exchange and * verify that all wire outs are in that table. * - * @param wa wire account we are processing + * @param[in,out] wa wire account we are processing */ static void check_exchange_wire_out (struct WireAccount *wa) @@ -1509,8 +1176,9 @@ check_exchange_wire_out (struct WireAccount *wa) * @param dhr HTTP response details */ static void -history_debit_cb (void *cls, - const struct TALER_BANK_DebitHistoryResponse *dhr) +history_debit_cb ( + void *cls, + const struct TALER_BANK_DebitHistoryResponse *dhr) { struct WireAccount *wa = cls; struct ReserveOutInfo *roi; @@ -1524,23 +1192,22 @@ history_debit_cb (void *cls, { const struct TALER_BANK_DebitDetails *dd = &dhr->details.ok.details[i]; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Analyzing bank DEBIT at %s of %s with WTID %s\n", GNUNET_TIME_timestamp2s (dd->execution_date), TALER_amount2s (&dd->amount), TALER_B2S (&dd->wtid)); - /* Update offset */ wa->wire_off_out = dd->serial_id; slen = strlen (dd->credit_account_uri) + 1; - roi = GNUNET_malloc (sizeof (struct ReserveOutInfo) - + slen); GNUNET_CRYPTO_hash (&dd->wtid, sizeof (dd->wtid), &roi->subject_hash); - roi->details.amount = dd->amount; - roi->details.execution_date = dd->execution_date; - roi->details.wtid = dd->wtid; - roi->details.credit_account_uri = (const char *) &roi[1]; + roi = GNUNET_malloc (sizeof (struct ReserveOutInfo) + + slen); + roi->details = *dd; + roi->details.credit_account_uri + = (const char *) &roi[1]; GNUNET_memcpy (&roi[1], dd->credit_account_uri, slen); @@ -1551,17 +1218,15 @@ history_debit_cb (void *cls, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)) { struct TALER_AUDITORDB_WireFormatInconsistency wfi = { - // fixme: rowid! - .diagnostic = "duplicate subject hash", .amount = dd->amount, - .wire_offset = dd->serial_id + .wire_offset = dd->serial_id, + .diagnostic = "duplicate outgoing wire transfer subject" }; enum GNUNET_DB_QueryStatus qs; qs = TALER_ARL_adb->insert_wire_format_inconsistency ( TALER_ARL_adb->cls, &wfi); - if (qs < 0) { global_qs = qs; @@ -1569,8 +1234,10 @@ history_debit_cb (void *cls, commit (qs); return; } - TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_wire_format_amount), - &TALER_ARL_USE_AB (total_wire_format_amount), + TALER_ARL_amount_add (&TALER_ARL_USE_AB ( + wire_debit_duplicate_transfer_subject_total), + &TALER_ARL_USE_AB ( + wire_debit_duplicate_transfer_subject_total), &dd->amount); } } @@ -1600,42 +1267,31 @@ history_debit_cb (void *cls, } -/** - * Main function for processing 'reserves_out' data. We start by going over - * the DEBIT transactions this time, and then verify that all of them are - * justified by 'reserves_out'. - * - * @param cls `struct WireAccount` with a wire account list to process - */ static void -process_debits (void *cls) +process_debits (struct WireAccount *wa) { - struct WireAccount *wa = cls; - /* skip accounts where DEBIT is not enabled */ while ( (NULL != wa) && - (GNUNET_NO == wa->ai->debit_enabled)) + (! wa->ai->debit_enabled) ) wa = wa->next; if (NULL == wa) { - /* end of iteration, now check wire_out to see - if it matches #out_map */ - conclude_wire_out (); + /* end of iteration */ + commit (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); return; } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Checking bank DEBIT records of account `%s'\n", wa->ai->section_name); GNUNET_assert (NULL == wa->dhh); - // FIXME: handle the case where more than INT32_MAX transactions exist. - // (CG: used to be INT64_MAX, changed by MS to INT32_MAX, why? To be discussed with him!) - wa->dhh = TALER_BANK_debit_history (ctx, - wa->ai->auth, - wa->wire_off_out, - INT32_MAX, - GNUNET_TIME_UNIT_ZERO, - &history_debit_cb, - wa); + wa->dhh = TALER_BANK_debit_history ( + ctx, + wa->ai->auth, + wa->wire_off_out, + MAX_PER_TRANSACTION, + GNUNET_TIME_UNIT_ZERO, + &history_debit_cb, + wa); if (NULL == wa->dhh) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, @@ -1650,24 +1306,9 @@ process_debits (void *cls) /** - * Begin analyzing wire_out. - */ -static void -begin_debit_audit (void) -{ - GNUNET_assert (NULL == out_map); - out_map = GNUNET_CONTAINER_multihashmap_create (1024, - true); - process_debits (wa_head); -} - - -/* ***************************** Setup logic ************************ */ - -/** * Function called about reserve closing operations the aggregator triggered. * - * @param cls closure + * @param cls closure; NULL * @param rowid row identifier used to uniquely identify the reserve closing operation * @param execution_date when did we execute the close operation * @param amount_with_fee how much did we debit the reserve @@ -1680,15 +1321,16 @@ begin_debit_audit (void) * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop */ static enum GNUNET_GenericReturnValue -reserve_closed_cb (void *cls, - uint64_t rowid, - struct GNUNET_TIME_Timestamp execution_date, - const struct TALER_Amount *amount_with_fee, - const struct TALER_Amount *closing_fee, - const struct TALER_ReservePublicKeyP *reserve_pub, - const char *receiver_account, - const struct TALER_WireTransferIdentifierRawP *wtid, - uint64_t close_request_row) +reserve_closed_cb ( + void *cls, + uint64_t rowid, + struct GNUNET_TIME_Timestamp execution_date, + const struct TALER_Amount *amount_with_fee, + const struct TALER_Amount *closing_fee, + const struct TALER_ReservePublicKeyP *reserve_pub, + const char *receiver_account, + const struct TALER_WireTransferIdentifierRawP *wtid, + uint64_t close_request_row) { struct ReserveClosure *rc; struct GNUNET_HashCode key; @@ -1702,8 +1344,9 @@ reserve_closed_cb (void *cls, closing_fee)) { struct TALER_AUDITORDB_RowInconsistency ri = { + .row_id = rowid, .row_table = "reserves_closures", - .diagnostic = "closing fee above total amount" + .diagnostic = "closing fee above reserve balance (and closed anyway)" }; enum GNUNET_DB_QueryStatus qs; @@ -1729,10 +1372,11 @@ reserve_closed_cb (void *cls, hash_rc (receiver_account, wtid, &key); - (void) GNUNET_CONTAINER_multihashmap_put (reserve_closures, - &key, - rc, - GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE); + (void) GNUNET_CONTAINER_multihashmap_put ( + reserve_closures, + &key, + rc, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE); return GNUNET_OK; } @@ -1784,7 +1428,7 @@ begin_transaction (void) TALER_ARL_GET_AB (total_bad_amount_out_minus), TALER_ARL_GET_AB (total_amount_lag), TALER_ARL_GET_AB (total_closure_amount_lag), - TALER_ARL_GET_AB (total_wire_format_amount), + TALER_ARL_GET_AB (wire_debit_duplicate_transfer_subject_total), TALER_ARL_GET_AB (total_wire_out), NULL); switch (qs) @@ -1828,8 +1472,6 @@ begin_transaction (void) qs = TALER_ARL_adb->get_auditor_progress ( TALER_ARL_adb->cls, TALER_ARL_GET_PP (wire_reserve_close_id), - TALER_ARL_GET_PP (wire_batch_deposit_id), - TALER_ARL_GET_PP (wire_aggregation_id), NULL); if (0 > qs) { @@ -1844,10 +1486,8 @@ begin_transaction (void) else { GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Resuming wire audit at %llu / %llu / %llu\n", - (unsigned long long) TALER_ARL_USE_PP (wire_reserve_close_id), - (unsigned long long) TALER_ARL_USE_PP (wire_batch_deposit_id), - (unsigned long long) TALER_ARL_USE_PP (wire_aggregation_id)); + "Resuming wire debit audit at %llu\n", + (unsigned long long) TALER_ARL_USE_PP (wire_reserve_close_id)); } qs = TALER_ARL_edb->select_reserve_closed_above_serial_id ( @@ -1860,7 +1500,7 @@ begin_transaction (void) GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); return GNUNET_DB_STATUS_HARD_ERROR; } - begin_debit_audit (); + process_debits (wa_head); return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; } @@ -1880,8 +1520,8 @@ process_account_cb (void *cls, struct WireAccount *wa; (void) cls; - if ((! ai->debit_enabled) && - (! ai->credit_enabled)) + if ( (! ai->debit_enabled) && + (! ai->credit_enabled) ) return; /* not an active exchange account */ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Found exchange account `%s'\n", @@ -1980,6 +1620,8 @@ run (void *cls, } reserve_closures = GNUNET_CONTAINER_multihashmap_create (1024, GNUNET_NO); + out_map = GNUNET_CONTAINER_multihashmap_create (1024, + true); if (GNUNET_OK != TALER_EXCHANGEDB_load_accounts (TALER_ARL_cfg, TALER_EXCHANGEDB_ALO_DEBIT diff --git a/src/auditordb/0002-auditor_wire_out_inconsistency.sql b/src/auditordb/0002-auditor_wire_out_inconsistency.sql index 484fd2ac6..d46c2b283 100644 --- a/src/auditordb/0002-auditor_wire_out_inconsistency.sql +++ b/src/auditordb/0002-auditor_wire_out_inconsistency.sql @@ -14,13 +14,14 @@ -- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> -- -SET search_path TO auditor; CREATE TABLE IF NOT EXISTS auditor_wire_out_inconsistency ( row_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE PRIMARY KEY, destination_account TEXT NOT NULL, - expected taler_amount, - claimed taler_amount, + diagnostic TEXT NOT NULL, + wire_out_serial_id INT8 NOT NULL, + expected taler_amount NOT NULL, + claimed taler_amount NOT NULL, suppressed BOOLEAN NOT NULL DEFAULT FALSE ); COMMENT ON TABLE auditor_wire_out_inconsistency diff --git a/src/auditordb/pg_get_wire_format_inconsistency.c b/src/auditordb/pg_get_wire_format_inconsistency.c index d94c851c8..333f9c9c0 100644 --- a/src/auditordb/pg_get_wire_format_inconsistency.c +++ b/src/auditordb/pg_get_wire_format_inconsistency.c @@ -13,14 +13,11 @@ You should have received a copy of the GNU General Public License along with TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ - - #include "platform.h" #include "taler_error_codes.h" #include "taler_dbevents.h" #include "taler_pq_lib.h" #include "pg_helper.h" - #include "pg_get_wire_format_inconsistency.h" @@ -69,17 +66,18 @@ wire_format_inconsistency_cb (void *cls, for (unsigned int i = 0; i < num_results; i++) { uint64_t serial_id; - struct TALER_AUDITORDB_WireFormatInconsistency dc; - struct GNUNET_PQ_ResultSpec rs[] = { - - GNUNET_PQ_result_spec_uint64 ("row_id", &serial_id), - - TALER_PQ_RESULT_SPEC_AMOUNT ("amount", &dc.amount), - GNUNET_PQ_result_spec_int64 ("wire_offset", &dc.wire_offset), - GNUNET_PQ_result_spec_auto_from_type ("diagnostic", &dc.diagnostic), - GNUNET_PQ_result_spec_bool ("suppressed", &dc.suppressed), + GNUNET_PQ_result_spec_uint64 ("row_id", + &serial_id), + TALER_PQ_RESULT_SPEC_AMOUNT ("amount", + &dc.amount), + GNUNET_PQ_result_spec_uint64 ("wire_offset", + &dc.wire_offset), + GNUNET_PQ_result_spec_auto_from_type ("diagnostic", + &dc.diagnostic), + GNUNET_PQ_result_spec_bool ("suppressed", + &dc.suppressed), GNUNET_PQ_result_spec_end @@ -95,9 +93,7 @@ wire_format_inconsistency_cb (void *cls, dcc->qs = GNUNET_DB_STATUS_HARD_ERROR; return; } - dcc->qs = i + 1; - rval = dcc->cb (dcc->cb_cls, serial_id, &dc); @@ -113,14 +109,12 @@ TAH_PG_get_wire_format_inconsistency ( void *cls, int64_t limit, uint64_t offset, - bool return_suppressed, // maybe not needed + bool return_suppressed, TALER_AUDITORDB_WireFormatInconsistencyCallback cb, void *cb_cls) { - - uint64_t plimit = (uint64_t) ((limit < 0) ? -limit : limit); - struct PostgresClosure *pg = cls; + uint64_t plimit = (uint64_t) ((limit < 0) ? -limit : limit); struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_uint64 (&offset), GNUNET_PQ_query_param_bool (return_suppressed), @@ -144,7 +138,7 @@ TAH_PG_get_wire_format_inconsistency ( " suppressed" " FROM auditor_wire_format_inconsistency" " WHERE (row_id < $1)" - " AND ($2 OR suppressed is false)" + " AND ($2 OR NOT suppressed)" " ORDER BY row_id DESC" " LIMIT $3" ); @@ -158,20 +152,18 @@ TAH_PG_get_wire_format_inconsistency ( " suppressed" " FROM auditor_wire_format_inconsistency" " WHERE (row_id > $1)" - " AND ($2 OR suppressed is false)" + " AND ($2 OR NOT suppressed)" " ORDER BY row_id ASC" " LIMIT $3" ); - qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - (limit > 0) - ? - "auditor_wire_format_inconsistency_get_asc" - : - "auditor_wire_format_inconsistency_get_desc", - params, - &wire_format_inconsistency_cb, - &dcc); - + qs = GNUNET_PQ_eval_prepared_multi_select ( + pg->conn, + (limit > 0) + ? "auditor_wire_format_inconsistency_get_asc" + : "auditor_wire_format_inconsistency_get_desc", + params, + &wire_format_inconsistency_cb, + &dcc); if (qs > 0) return dcc.qs; GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs); diff --git a/src/auditordb/pg_get_wire_out_inconsistency.c b/src/auditordb/pg_get_wire_out_inconsistency.c index d885a2fa1..b875f6051 100644 --- a/src/auditordb/pg_get_wire_out_inconsistency.c +++ b/src/auditordb/pg_get_wire_out_inconsistency.c @@ -68,21 +68,22 @@ wire_out_inconsistency_cb (void *cls, for (unsigned int i = 0; i < num_results; i++) { - uint64_t serial_id; - struct TALER_AUDITORDB_WireOutInconsistency dc; - struct GNUNET_PQ_ResultSpec rs[] = { - - GNUNET_PQ_result_spec_uint64 ("row_id", &serial_id), - + GNUNET_PQ_result_spec_uint64 ("row_id", + &dc.row_id), GNUNET_PQ_result_spec_string ("destination_account", &dc.destination_account), - TALER_PQ_RESULT_SPEC_AMOUNT ("expected", &dc.expected), - TALER_PQ_RESULT_SPEC_AMOUNT ("claimed", &dc.claimed), - GNUNET_PQ_result_spec_bool ("suppressed", &dc.suppressed), - - + GNUNET_PQ_result_spec_string ("diagnostic", + &dc.diagnostic), + GNUNET_PQ_result_spec_uint64 ("wire_out_row_id", + &dc.wire_out_row_id), + TALER_PQ_RESULT_SPEC_AMOUNT ("expected", + &dc.expected), + TALER_PQ_RESULT_SPEC_AMOUNT ("claimed", + &dc.claimed), + GNUNET_PQ_result_spec_bool ("suppressed", + &dc.suppressed), GNUNET_PQ_result_spec_end }; enum GNUNET_GenericReturnValue rval; @@ -96,11 +97,8 @@ wire_out_inconsistency_cb (void *cls, dcc->qs = GNUNET_DB_STATUS_HARD_ERROR; return; } - dcc->qs = i + 1; - rval = dcc->cb (dcc->cb_cls, - serial_id, &dc); GNUNET_PQ_cleanup_result (rs); if (GNUNET_OK != rval) @@ -114,14 +112,12 @@ TAH_PG_get_wire_out_inconsistency ( void *cls, int64_t limit, uint64_t offset, - bool return_suppressed, // maybe not needed + bool return_suppressed, TALER_AUDITORDB_WireOutInconsistencyCallback cb, void *cb_cls) { - - uint64_t plimit = (uint64_t) ((limit < 0) ? -limit : limit); - struct PostgresClosure *pg = cls; + uint64_t plimit = (uint64_t) ((limit < 0) ? -limit : limit); struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_uint64 (&offset), GNUNET_PQ_query_param_bool (return_suppressed), @@ -138,41 +134,43 @@ TAH_PG_get_wire_out_inconsistency ( PREPARE (pg, "auditor_wire_out_inconsistency_get_desc", "SELECT" - " row_id," - " destination_account," - " expected," - " claimed," - " suppressed" + " row_id" + ",destination_account" + ",diagnostic" + ",wire_out_row_id" + ",expected" + ",claimed" + ",suppressed" " FROM auditor_wire_out_inconsistency" " WHERE (row_id < $1)" - " AND ($2 OR suppressed is false)" + " AND ($2 OR NOT suppressed)" " ORDER BY row_id DESC" " LIMIT $3" ); PREPARE (pg, "auditor_wire_out_inconsistency_get_asc", "SELECT" - " row_id," - " destination_account," - " expected," - " claimed," - " suppressed" + " row_id" + ",destination_account" + ",diagnostic" + ",wire_out_row_id" + ",expected" + ",claimed" + ",suppressed" " FROM auditor_wire_out_inconsistency" " WHERE (row_id > $1)" - " AND ($2 OR suppressed is false)" + " AND ($2 OR NOT suppressed)" " ORDER BY row_id ASC" " LIMIT $3" ); - qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - (limit > 0) - ? - "auditor_wire_out_inconsistency_get_asc" - : - "auditor_wire_out_inconsistency_get_desc", - params, - &wire_out_inconsistency_cb, - &dcc); - + qs = GNUNET_PQ_eval_prepared_multi_select ( + pg->conn, + (limit > 0) + ? "auditor_wire_out_inconsistency_get_asc" + : "auditor_wire_out_inconsistency_get_desc", + params, + &wire_out_inconsistency_cb, + &dcc); if (qs > 0) return dcc.qs; GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs); diff --git a/src/auditordb/pg_insert_wire_format_inconsistency.c b/src/auditordb/pg_insert_wire_format_inconsistency.c index c4b5b16dc..73fe677b4 100644 --- a/src/auditordb/pg_insert_wire_format_inconsistency.c +++ b/src/auditordb/pg_insert_wire_format_inconsistency.c @@ -13,14 +13,12 @@ You should have received a copy of the GNU General Public License along with TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ - - #include "platform.h" #include "taler_pq_lib.h" #include "pg_helper.h" - #include "pg_insert_wire_format_inconsistency.h" + enum GNUNET_DB_QueryStatus TAH_PG_insert_wire_format_inconsistency ( void *cls, @@ -30,17 +28,15 @@ TAH_PG_insert_wire_format_inconsistency ( struct GNUNET_PQ_QueryParam params[] = { TALER_PQ_query_param_amount (pg->conn, &dc->amount), - GNUNET_PQ_query_param_int64 (&dc->wire_offset), + GNUNET_PQ_query_param_uint64 (&dc->wire_offset), GNUNET_PQ_query_param_auto_from_type (&dc->diagnostic), - - GNUNET_PQ_query_param_end }; PREPARE (pg, "auditor_wire_format_inconsistency_insert", "INSERT INTO auditor_wire_format_inconsistency " - "( row_id," + "(row_id," " amount," " wire_offset," " diagnostic" diff --git a/src/auditordb/pg_insert_wire_out_inconsistency.c b/src/auditordb/pg_insert_wire_out_inconsistency.c index 828aa3293..2e9354644 100644 --- a/src/auditordb/pg_insert_wire_out_inconsistency.c +++ b/src/auditordb/pg_insert_wire_out_inconsistency.c @@ -28,22 +28,25 @@ TAH_PG_insert_wire_out_inconsistency ( { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (dc->destination_account), - TALER_PQ_query_param_amount (pg->conn, &dc->expected), - TALER_PQ_query_param_amount (pg->conn, &dc->claimed), - - + GNUNET_PQ_query_param_string (dc->diagnostic), + GNUNET_PQ_query_param_uint64 (&dc->wire_out_row_id), + TALER_PQ_query_param_amount (pg->conn, + &dc->expected), + TALER_PQ_query_param_amount (pg->conn, + &dc->claimed), GNUNET_PQ_query_param_end }; PREPARE (pg, "auditor_wire_out_inconsistency_insert", "INSERT INTO auditor_wire_out_inconsistency " - "( destination_account," - " expected," - " claimed" - ") VALUES ($1,$2,$3);" + "(destination_account" + ",diagnostic" + ",wire_out_serial_id" + ",expected" + ",claimed" + ") VALUES ($1,$2,$3,$4,$5);" ); return GNUNET_PQ_eval_prepared_non_select (pg->conn, "auditor_wire_out_inconsistency_insert", diff --git a/src/include/taler_auditordb_plugin.h b/src/include/taler_auditordb_plugin.h index 430fad9d0..1dd26a8ce 100644 --- a/src/include/taler_auditordb_plugin.h +++ b/src/include/taler_auditordb_plugin.h @@ -781,7 +781,7 @@ struct TALER_AUDITORDB_WireFormatInconsistency { uint64_t row_id; struct TALER_Amount amount; - int64_t wire_offset; + uint64_t wire_offset; char *diagnostic; bool suppressed; @@ -791,7 +791,8 @@ struct TALER_AUDITORDB_WireOutInconsistency { uint64_t row_id; char *destination_account; - char *diagnostic; // FIXME: new + char *diagnostic; + uint64_t wire_out_row_id; struct TALER_Amount expected; struct TALER_Amount claimed; bool suppressed; @@ -951,7 +952,6 @@ typedef enum GNUNET_GenericReturnValue typedef enum GNUNET_GenericReturnValue (*TALER_AUDITORDB_WireOutInconsistencyCallback)( void *cls, - uint64_t serial_id, const struct TALER_AUDITORDB_WireOutInconsistency *dc); typedef enum GNUNET_GenericReturnValue |