exchange

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

commit 0d3bf26b491a5f97ff8c0f291f56b47370484db6
parent b82f45cd64f87ab7be99096bac9196e830f9a9dc
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sat, 10 Oct 2026 21:23:29 +0200

testing: run every auditor helper in test_auditor_api and fail on findings (TEST-4)

TALER_TESTING_cmd_exec_auditor() still exec'd the long-gone taler-auditor,
so test_auditor_api had all three calls commented out.  The command now
runs each taler-helper-auditor-* once in test mode (-t) with -i directly
against the exchange database, then queries the /monitoring/ reports of
the taler-auditor-httpd that taler-unified-setup.sh -a keeps running for
the whole test, and fails if any of them is non-empty.  The wire helpers
get -I, as the old command did.

Diffstat:
Msrc/include/taler/taler_testing_lib.h | 6+++++-
Msrc/testing/test_auditor_api-cs.conf | 11+++++++++++
Msrc/testing/test_auditor_api-rsa.conf | 11+++++++++++
Msrc/testing/test_auditor_api.c | 8++++----
Msrc/testing/testing_api_cmd_auditor_exec_auditor.c | 445+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
5 files changed, 413 insertions(+), 68 deletions(-)

diff --git a/src/include/taler/taler_testing_lib.h b/src/include/taler/taler_testing_lib.h @@ -847,7 +847,11 @@ TALER_TESTING_cmd_transfer_retry (struct TALER_TESTING_Command cmd); /** - * Make the "exec-auditor" CMD. + * Make the "exec-auditor" CMD. Runs every taler-helper-auditor-* + * program once in test mode directly against the exchange database, and + * then fails unless the taler-auditor-httpd at the auditor's BASE_URL + * reports no findings. The auditor (and the bank, for the wire helpers) + * must be running. * * @param label command label. * @param config_filename configuration filename. diff --git a/src/testing/test_auditor_api-cs.conf b/src/testing/test_auditor_api-cs.conf @@ -5,3 +5,14 @@ [exchange] ENABLE_KYC = NO + +# The test only uses exchange-account-2. exchange-account-1 is the same +# bank account, and exchange-account-3 one the bank does not have; the +# auditor would compare the bank's history against both of them. +[exchange-account-1] +ENABLE_CREDIT = NO +ENABLE_DEBIT = NO + +[exchange-account-3] +ENABLE_CREDIT = NO +ENABLE_DEBIT = NO diff --git a/src/testing/test_auditor_api-rsa.conf b/src/testing/test_auditor_api-rsa.conf @@ -5,3 +5,14 @@ [exchange] ENABLE_KYC = NO + +# The test only uses exchange-account-2. exchange-account-1 is the same +# bank account, and exchange-account-3 one the bank does not have; the +# auditor would compare the bank's history against both of them. +[exchange-account-1] +ENABLE_CREDIT = NO +ENABLE_DEBIT = NO + +[exchange-account-3] +ENABLE_CREDIT = NO +ENABLE_DEBIT = NO diff --git a/src/testing/test_auditor_api.c b/src/testing/test_auditor_api.c @@ -80,7 +80,7 @@ static struct TALER_TESTING_Credentials cred; cred.user42_payto) /** - * Run the taler-auditor. + * Run all auditor helpers and check that the auditor has no findings. * * @param label label to use for the command. */ @@ -635,7 +635,7 @@ run (void *cls, 1, "EUR:0.99", MHD_HTTP_NO_CONTENT), - // CMD_RUN_AUDITOR ("massive-auditor"), + CMD_RUN_AUDITOR ("massive-auditor"), TALER_TESTING_cmd_end () }; @@ -659,7 +659,7 @@ run (void *cls, true), TALER_TESTING_cmd_exec_auditor_offline ("auditor-offline", config_file), - // CMD_RUN_AUDITOR ("virgin-auditor"), + CMD_RUN_AUDITOR ("virgin-auditor"), TALER_TESTING_cmd_batch ("massive-deposit-confirms", massive_deposit_confirms), TALER_TESTING_cmd_batch ("withdraw", @@ -676,7 +676,7 @@ run (void *cls, refund), TALER_TESTING_cmd_batch ("recoup", recoup), - // CMD_RUN_AUDITOR ("normal-auditor"), + CMD_RUN_AUDITOR ("normal-auditor"), TALER_TESTING_cmd_end () }; diff --git a/src/testing/testing_api_cmd_auditor_exec_auditor.c b/src/testing/testing_api_cmd_auditor_exec_auditor.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2018 Taler Systems SA + Copyright (C) 2018, 2026 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 @@ -18,37 +18,343 @@ */ /** * @file testing/testing_api_cmd_auditor_exec_auditor.c - * @brief run the taler-auditor command + * @brief run all taler-helper-auditor-* programs and check that + * taler-auditor-httpd reports no findings * @author Marcello Stanisci * @author Christian Grothoff */ -#include "platform.h" /* UNNECESSARY? */ +#include "platform.h" #include "taler/taler_json_lib.h" #include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_signatures.h" /* UNNECESSARY? */ #include "taler/taler_testing_lib.h" /** + * Auditor helpers to run, in this order. Each is run in test mode + * ("-t"), so it audits everything the exchange database holds so far + * and then exits. + */ +static const struct +{ + /** + * Name of the helper binary. + */ + const char *name; + + /** + * True if the helper reads the bank's history of the exchange's + * accounts. Test configurations may list accounts the bank does + * not have, so those helpers are told to skip them ("-I"). + */ + bool wire; +} helpers[] = { + { "taler-helper-auditor-aggregation", false }, + { "taler-helper-auditor-aml", false }, + { "taler-helper-auditor-coins", false }, + { "taler-helper-auditor-deposits", false }, + { "taler-helper-auditor-purses", false }, + { "taler-helper-auditor-reserves", false }, + { "taler-helper-auditor-transfer", false }, + { "taler-helper-auditor-wire-credit", true }, + { "taler-helper-auditor-wire-debit", true }, + { NULL, false } +}; + + +/** + * /monitoring/ reports of taler-auditor-httpd that must be empty after a + * correct exchange was audited. Not listed are the balances, progress + * and per-reserve/per-purse summaries, which are not findings, and + * "pending-deposits" and "aml-holds", which list payouts the exchange has + * not made *yet* (deposits before their wire deadline, transfers held + * for KYC) and thus depend on when the test last ran the aggregator. + */ +static const char *const reports[] = { + "amount-arithmetic-inconsistencies", + "bad-sig-losses", + "closure-lags", + "coin-inconsistencies", + "denomination-key-validity-withdraw-inconsistencies", + "denominations-without-sigs", + "deposit-confirmations", + "early-aggregations", + "emergencies", + "emergencies-by-count", + "fee-time-inconsistencies", + "kycauth-in-inconsistencies", + "misattribution-in-inconsistencies", + "purse-not-closed-inconsistencies", + "reserve-balance-insufficient-inconsistencies", + "reserve-balance-summary-wrong-inconsistencies", + "reserve-in-inconsistencies", + "reserve-not-closed-inconsistencies", + "row-inconsistencies", + "row-minor-inconsistencies", + "wire-format-inconsistencies", + "wire-out-inconsistencies", + NULL +}; + + +/** * State for a "auditor" CMD. */ struct AuditorState { /** - * Process for the "auditor" command. + * Our interpreter. */ - struct GNUNET_Process *auditor_proc; + struct TALER_TESTING_Interpreter *is; + + /** + * Process of the helper currently running. + */ + struct GNUNET_Process *helper_proc; + + /** + * Handle to wait for @e helper_proc to terminate. + */ + struct GNUNET_ChildWaitHandle *cwh; + + /** + * Task to start the next helper. + */ + struct GNUNET_SCHEDULER_Task *task; + + /** + * Request for a report, or NULL. + */ + struct GNUNET_CURL_Job *job; + + /** + * Headers to send with each request for a report. + */ + struct curl_slist *job_headers; /** * Configuration file used by the command. */ const char *config_filename; + + /** + * Base URL of the auditor, from @e config_filename. + */ + char *auditor_url; + + /** + * Index into #helpers of the helper currently running. + */ + unsigned int helper_off; + + /** + * Index into #reports of the report currently requested. + */ + unsigned int report_off; + + /** + * Number of reports that were not empty. + */ + unsigned int findings; }; /** - * Run the command; calls the `taler-auditor` program. + * Request the next report from #reports, or finish the command + * if all of them were checked. + * + * @param as our state + */ +static void +check_next_report (struct AuditorState *as); + + +/** + * Called with a report from the auditor. + * + * @param cls our `struct AuditorState` + * @param response_code HTTP status returned by the auditor + * @param response response body, NULL if not in JSON + */ +static void +report_cb (void *cls, + long response_code, + const void *response) +{ + struct AuditorState *as = cls; + const json_t *reply = response; + const char *report = reports[as->report_off]; + const json_t *records; + + as->job = NULL; + if (MHD_HTTP_OK != response_code) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Auditor returned HTTP status %u for report `%s'\n", + (unsigned int) response_code, + report); + TALER_TESTING_interpreter_fail (as->is); + return; + } + records = json_object_get (reply, + "records"); + if (! json_is_array (records)) + { + GNUNET_break_op (0); + TALER_TESTING_interpreter_fail (as->is); + return; + } + if (0 != json_array_size (records)) + { + char *s; + + s = json_dumps (records, + JSON_COMPACT); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Auditor reports %u %s: %s\n", + (unsigned int) json_array_size (records), + report, + s); + free (s); + as->findings++; + } + as->report_off++; + check_next_report (as); +} + + +static void +check_next_report (struct AuditorState *as) +{ + CURL *eh; + char *url; + + if (NULL == reports[as->report_off]) + { + if (0 != as->findings) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Auditor has findings in %u reports\n", + as->findings); + TALER_TESTING_interpreter_fail (as->is); + return; + } + TALER_TESTING_interpreter_next (as->is); + return; + } + GNUNET_asprintf (&url, + "%smonitoring/%s", + as->auditor_url, + reports[as->report_off]); + eh = curl_easy_init (); + GNUNET_assert (NULL != eh); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_URL, + url)); + GNUNET_free (url); + as->job = GNUNET_CURL_job_add2 (TALER_TESTING_interpreter_get_context ( + as->is), + eh, + as->job_headers, + &report_cb, + as); + if (NULL == as->job) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (as->is); + return; + } +} + + +/** + * Start the next helper from #helpers, or start checking + * the reports if all helpers have run. + * + * @param cls our `struct AuditorState` + */ +static void +run_next_helper (void *cls); + + +/** + * Called when the helper we started has terminated. + * + * @param cls our `struct AuditorState` + * @param type how the helper terminated + * @param code exit code of the helper + */ +static void +helper_done (void *cls, + enum GNUNET_OS_ProcessStatusType type, + long unsigned int code) +{ + struct AuditorState *as = cls; + + as->cwh = NULL; + GNUNET_process_destroy (as->helper_proc); + as->helper_proc = NULL; + if ( (GNUNET_OS_PROCESS_EXITED != type) || + (0 != code) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "%s failed (status %d, code %lu)\n", + helpers[as->helper_off].name, + (int) type, + code); + TALER_TESTING_interpreter_fail (as->is); + return; + } + as->helper_off++; + /* Not directly: GNUNET_wait_child() must not be called from + within a GNUNET_ChildCompletedCallback. */ + as->task = GNUNET_SCHEDULER_add_now (&run_next_helper, + as); +} + + +static void +run_next_helper (void *cls) +{ + struct AuditorState *as = cls; + const char *helper = helpers[as->helper_off].name; + + as->task = NULL; + if (NULL == helper) + { + check_next_report (as); + return; + } + as->helper_proc = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR); + if (GNUNET_OK != + GNUNET_process_run_command_va (as->helper_proc, + helper, + helper, + "-c", as->config_filename, + "-L", "INFO", + "-i", /* we audit the exchange DB directly */ + "-t", /* exit when done */ + helpers[as->helper_off].wire + ? "-I" /* ignore unknown accounts */ + : NULL, + NULL)) + { + GNUNET_break (0); + GNUNET_process_destroy (as->helper_proc); + as->helper_proc = NULL; + TALER_TESTING_interpreter_fail (as->is); + return; + } + as->cwh = GNUNET_wait_child (as->helper_proc, + &helper_done, + as); +} + + +/** + * Run the command; runs all auditor helpers against the exchange + * database and then asks the auditor for its findings. * * @param cls closure. * @param cmd the commaind being run. @@ -59,28 +365,53 @@ auditor_run (void *cls, const struct TALER_TESTING_Command *cmd, struct TALER_TESTING_Interpreter *is) { - struct AuditorState *ks = cls; + struct AuditorState *as = cls; + struct GNUNET_CONFIGURATION_Handle *cfg; (void) cmd; - // FIXME: taler-auditor is no more, run the individual - // helpers here instead, or re-introduce the taler-auditor - // command to run all helpers! - ks->auditor_proc = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR); + as->is = is; + cfg = GNUNET_CONFIGURATION_create (TALER_AUDITOR_project_data ()); if (GNUNET_OK != - GNUNET_process_run_command_va (ks->auditor_proc, - "taler-auditor", - "taler-auditor", - "-c", ks->config_filename, - "-I", - NULL)) + GNUNET_CONFIGURATION_load (cfg, + as->config_filename)) { GNUNET_break (0); - GNUNET_process_destroy (ks->auditor_proc); - ks->auditor_proc = NULL; + GNUNET_CONFIGURATION_destroy (cfg); + TALER_TESTING_interpreter_fail (is); + return; + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "auditor", + "BASE_URL", + &as->auditor_url)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "auditor", + "BASE_URL"); + GNUNET_CONFIGURATION_destroy (cfg); TALER_TESTING_interpreter_fail (is); return; } - TALER_TESTING_wait_for_sigchld (is); + GNUNET_CONFIGURATION_destroy (cfg); + { + const char *tok = getenv ("TALER_AUDITOR_ACCESS_TOKEN"); + + /* taler-auditor-httpd requires the token iff it is set */ + if (NULL != tok) + { + char *hdr; + + GNUNET_asprintf (&hdr, + "%s: Bearer secret-token:%s", + MHD_HTTP_HEADER_AUTHORIZATION, + tok); + as->job_headers = curl_slist_append (as->job_headers, + hdr); + GNUNET_free (hdr); + } + } + run_next_helper (as); } @@ -95,50 +426,39 @@ static void auditor_cleanup (void *cls, const struct TALER_TESTING_Command *cmd) { - struct AuditorState *ks = cls; + struct AuditorState *as = cls; (void) cmd; - if (NULL != ks->auditor_proc) + if (NULL != as->task) + { + GNUNET_SCHEDULER_cancel (as->task); + as->task = NULL; + } + if (NULL != as->cwh) + { + GNUNET_wait_child_cancel (as->cwh); + as->cwh = NULL; + } + if (NULL != as->helper_proc) { GNUNET_break (GNUNET_OK == - GNUNET_process_kill (ks->auditor_proc, + GNUNET_process_kill (as->helper_proc, SIGKILL)); - GNUNET_process_wait (ks->auditor_proc, + GNUNET_process_wait (as->helper_proc, true, NULL, NULL); - GNUNET_process_destroy (ks->auditor_proc); - ks->auditor_proc = NULL; + GNUNET_process_destroy (as->helper_proc); + as->helper_proc = NULL; } - GNUNET_free (ks); -} - - -/** - * Offer "auditor" CMD internal data to other commands. - * - * @param cls closure. - * @param[out] ret result - * @param trait name of the trait. - * @param index index number of the object to offer. - * @return #GNUNET_OK on success. - */ -static enum GNUNET_GenericReturnValue -auditor_traits (void *cls, - const void **ret, - const char *trait, - unsigned int index) -{ - struct AuditorState *ks = cls; - struct TALER_TESTING_Trait traits[] = { - TALER_TESTING_make_trait_process (&ks->auditor_proc), - TALER_TESTING_trait_end () - }; - - return TALER_TESTING_get_trait (traits, - ret, - trait, - index); + if (NULL != as->job) + { + GNUNET_CURL_job_cancel (as->job); + as->job = NULL; + } + curl_slist_free_all (as->job_headers); + GNUNET_free (as->auditor_url); + GNUNET_free (as); } @@ -146,17 +466,16 @@ struct TALER_TESTING_Command TALER_TESTING_cmd_exec_auditor (const char *label, const char *config_filename) { - struct AuditorState *ks; + struct AuditorState *as; - ks = GNUNET_new (struct AuditorState); - ks->config_filename = config_filename; + as = GNUNET_new (struct AuditorState); + as->config_filename = config_filename; { struct TALER_TESTING_Command cmd = { - .cls = ks, + .cls = as, .label = label, .run = &auditor_run, - .cleanup = &auditor_cleanup, - .traits = &auditor_traits + .cleanup = &auditor_cleanup }; return cmd; @@ -164,4 +483,4 @@ TALER_TESTING_cmd_exec_auditor (const char *label, } -/* end of testing_auditor_api_cmd_exec_auditor.c */ +/* end of testing_api_cmd_auditor_exec_auditor.c */