merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

commit a05edcc23ab32bdc85afe5baab4cae2b3d7b1b42
parent a4b9be0cd05b50cf3f817b6b4c4ea41c5714dc00
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sat,  7 Feb 2026 20:56:20 +0100

track more data on taler-mechant-exchangekeyupdate failure reasons (#10988)

Diffstat:
Msrc/backend/taler-merchant-depositcheck.c | 3++-
Msrc/backend/taler-merchant-exchangekeyupdate.c | 50++++++++++++++++++++++++++++++++++++++++++++++----
Msrc/backend/taler-merchant-httpd_exchanges.c | 3++-
Msrc/backend/taler-merchant-kyccheck.c | 3++-
Msrc/backend/taler-merchant-reconciliation.c | 3++-
Msrc/backenddb/merchant-0030.sql | 3++-
Msrc/backenddb/pg_insert_exchange_keys.c | 36++++++++++++++++++++++++++++--------
Msrc/backenddb/pg_insert_exchange_keys.h | 8+++++++-
Msrc/backenddb/pg_select_exchange_keys.c | 18+++++++++---------
Msrc/include/taler_merchantdb_plugin.h | 8+++++++-
10 files changed, 107 insertions(+), 28 deletions(-)

diff --git a/src/backend/taler-merchant-depositcheck.c b/src/backend/taler-merchant-depositcheck.c @@ -997,7 +997,8 @@ update_exchange_keys (void *cls, GNUNET_SCHEDULER_shutdown (); return; } - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) || + (NULL == keys) ) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "No keys yet for `%s'\n", diff --git a/src/backend/taler-merchant-exchangekeyupdate.c b/src/backend/taler-merchant-exchangekeyupdate.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2023, 2024 Taler Systems SA + Copyright (C) 2023, 2024, 2026 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software @@ -281,6 +281,36 @@ add_restriction (json_t *restrictions, /** + * The /keys download from @e failed with @a http_status and @a ec. + * Record the failure in the database. + * + * @param e exchange that failed + * @param http_status HTTP status returned + * @param ec Taler error code + */ +static void +fail_keys (const struct Exchange *e, + unsigned int http_status, + enum TALER_ErrorCode ec) +{ + enum GNUNET_DB_QueryStatus qs; + + qs = db_plugin->insert_exchange_keys ( + db_plugin->cls, + e->exchange_url, + NULL, + GNUNET_TIME_relative_to_absolute (e->retry_delay), + http_status, + ec); + if (0 > qs) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + return; + } +} + + +/** * Update our information in the database about the * /keys of an exchange. Run inside of a database * transaction scope that will re-try and/or commit @@ -319,8 +349,11 @@ insert_keys_data (const struct TALER_EXCHANGE_Keys *keys, } qs = db_plugin->insert_exchange_keys (db_plugin->cls, + keys->exchange_url, keys, - first_retry); + first_retry, + MHD_HTTP_OK, + TALER_EC_NONE); if (0 > qs) { GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); @@ -523,6 +556,8 @@ cert_cb ( struct GNUNET_TIME_Absolute first_retry; e->conn = NULL; + e->retry_delay + = GNUNET_TIME_STD_BACKOFF (e->retry_delay); switch (kr->hr.http_status) { case MHD_HTTP_OK: @@ -536,6 +571,9 @@ cert_cb ( e->exchange_url, keys->currency, e->currency); + fail_keys (e, + MHD_HTTP_OK, + TALER_EC_GENERIC_CURRENCY_MISMATCH); TALER_EXCHANGE_keys_decref (keys); break; } @@ -545,6 +583,9 @@ cert_cb ( GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Master public key in %skeys response does not match. Ignoring response.\n", e->exchange_url); + fail_keys (e, + MHD_HTTP_OK, + TALER_EC_MERCHANT_GENERIC_EXCHANGE_MASTER_KEY_MISMATCH); TALER_EXCHANGE_keys_decref (keys); break; } @@ -577,11 +618,12 @@ cert_cb ( return; default: GNUNET_break (NULL == keys); + fail_keys (e, + kr->hr.http_status, + TALER_EC_MERCHANT_GENERIC_EXCHANGE_KEYS_FAILURE); break; } /* Try again (soon-ish) */ - e->retry_delay - = GNUNET_TIME_STD_BACKOFF (e->retry_delay); n = GNUNET_TIME_absolute_max ( e->first_retry, GNUNET_TIME_relative_to_absolute (e->retry_delay)); diff --git a/src/backend/taler-merchant-httpd_exchanges.c b/src/backend/taler-merchant-httpd_exchanges.c @@ -910,7 +910,8 @@ reload_exchange_keys (struct TMH_Exchange *exchange) GNUNET_break (0); return; } - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) || + (NULL == keys) ) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "No keys yet for `%s'\n", diff --git a/src/backend/taler-merchant-kyccheck.c b/src/backend/taler-merchant-kyccheck.c @@ -1149,7 +1149,8 @@ find_keys (const char *exchange_url) GNUNET_SCHEDULER_shutdown (); return; } - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) || + (NULL == keys) ) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "No %s/keys yet!\n", diff --git a/src/backend/taler-merchant-reconciliation.c b/src/backend/taler-merchant-reconciliation.c @@ -332,7 +332,8 @@ sync_keys (struct Exchange *e) GNUNET_break (0); return; } - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) || + (NULL == keys) ) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Cannot launch inquiries at `%s': lacking /keys response\n", diff --git a/src/backenddb/merchant-0030.sql b/src/backenddb/merchant-0030.sql @@ -50,7 +50,8 @@ COMMENT ON INDEX merchant_unclaim_signatures_by_expiration ALTER TABLE merchant_exchange_keys ADD COLUMN exchange_http_status INT4 DEFAULT(0) - ,ADD COLUMN exchange_ec_code INT4 DEFAULT(0); + ,ADD COLUMN exchange_ec_code INT4 DEFAULT(0) + ,ALTER COLUMN keys_json DROP NOT NULL; COMMENT ON COLUMN merchant_exchange_keys.exchange_http_status IS 'Last HTTP status code from trying to fetch /keys; 0 for timeout or not attempted yet'; diff --git a/src/backenddb/pg_insert_exchange_keys.c b/src/backenddb/pg_insert_exchange_keys.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2022 Taler Systems SA + Copyright (C) 2022, 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 by the Free Software @@ -29,16 +29,29 @@ enum GNUNET_DB_QueryStatus TMH_PG_insert_exchange_keys ( void *cls, + const char *exchange_url, const struct TALER_EXCHANGE_Keys *keys, - struct GNUNET_TIME_Absolute first_retry) + struct GNUNET_TIME_Absolute first_retry, + uint32_t http_status, + enum TALER_ErrorCode ec) { struct PostgresClosure *pg = cls; - json_t *jkeys = TALER_EXCHANGE_keys_to_json (keys); + uint32_t ec32 = (uint32_t) ec; + json_t *jkeys = (NULL == keys) + ? NULL + : TALER_EXCHANGE_keys_to_json (keys); + struct GNUNET_TIME_Timestamp ldid = (NULL == keys) + ? GNUNET_TIME_UNIT_ZERO_TS + : keys->last_denom_issue_date; struct GNUNET_PQ_QueryParam params[] = { - TALER_PQ_query_param_json (jkeys), + keys == NULL + ? GNUNET_PQ_query_param_null () + : TALER_PQ_query_param_json (jkeys), GNUNET_PQ_query_param_absolute_time (&first_retry), - GNUNET_PQ_query_param_timestamp (&keys->last_denom_issue_date), - GNUNET_PQ_query_param_string (keys->exchange_url), + GNUNET_PQ_query_param_timestamp (&ldid), + GNUNET_PQ_query_param_string (exchange_url), + GNUNET_PQ_query_param_uint32 (&http_status), + GNUNET_PQ_query_param_uint32 (&ec32), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; @@ -51,13 +64,20 @@ TMH_PG_insert_exchange_keys ( ",first_retry" ",expiration_time" ",exchange_url" - ") VALUES ($1::TEXT::JSONB, $2, $3, $4);"); + ",exchange_http_status" + ",exchange_ec_code" + ") VALUES (" + " $1::TEXT::JSONB, $2, $3, $4, $5, $6" + ");"); PREPARE (pg, "update_exchange_keys", "UPDATE merchant_exchange_keys SET" - " keys_json=$1::TEXT::JSONB" + /* preserve old keys if new ones failed to download */ + " keys_json=COALESCE($1::TEXT::JSONB,keys_json)" ",first_retry=$2" ",expiration_time=$3" + ",exchange_http_status=$5" + ",exchange_ec_code=$6" " WHERE" " exchange_url=$4;"); qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, diff --git a/src/backenddb/pg_insert_exchange_keys.h b/src/backenddb/pg_insert_exchange_keys.h @@ -30,14 +30,20 @@ * Insert or update @a keys into the database. * * @param cls plugin closure + * @param exchange_url base URL of the exchange * @param keys data to store * @param first_retry earliest we may retry fetching the keys + * @param http_status latest HTTP status of the exchange + * @param ec latest error code from fetching /keys * @return transaction status */ enum GNUNET_DB_QueryStatus TMH_PG_insert_exchange_keys ( void *cls, + const char *exchange_url, const struct TALER_EXCHANGE_Keys *keys, - struct GNUNET_TIME_Absolute first_retry); + struct GNUNET_TIME_Absolute first_retry, + uint32_t http_status, + enum TALER_ErrorCode ec); #endif diff --git a/src/backenddb/pg_select_exchange_keys.c b/src/backenddb/pg_select_exchange_keys.c @@ -37,16 +37,19 @@ TMH_PG_select_exchange_keys (void *cls, GNUNET_PQ_query_param_string (exchange_url), GNUNET_PQ_query_param_end }; - json_t *jkeys; + json_t *jkeys = NULL; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_absolute_time ("first_retry", first_retry), - TALER_PQ_result_spec_json ("keys_json", - &jkeys), + GNUNET_PQ_result_spec_allow_null ( + TALER_PQ_result_spec_json ("keys_json", + &jkeys), + NULL), GNUNET_PQ_result_spec_end }; enum GNUNET_DB_QueryStatus qs; + *keys = NULL; check_connection (pg); PREPARE (pg, "select_exchange_keys", @@ -61,13 +64,10 @@ TMH_PG_select_exchange_keys (void *cls, rs); if (qs <= 0) return qs; - *keys = TALER_EXCHANGE_keys_from_json (jkeys); - json_decref (jkeys); - if (NULL == *keys) + if (NULL != jkeys) { - /* malformed /keys in cache, maybe format change. Just ignore */ - GNUNET_break (0); - return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; + *keys = TALER_EXCHANGE_keys_from_json (jkeys); + json_decref (jkeys); } return qs; } diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h @@ -4708,14 +4708,20 @@ struct TALER_MERCHANTDB_Plugin * Insert or update @a keys into the database. * * @param cls plugin closure + * @param exchange_url base URL of the exchange * @param keys data to store * @param first_retry earliest we may retry fetching the keys + * @param http_status latest HTTP status of the exchange + * @param ec latest error code from fetching /keys * @return transaction status */ enum GNUNET_DB_QueryStatus (*insert_exchange_keys)(void *cls, + const char *exchange_url, const struct TALER_EXCHANGE_Keys *keys, - struct GNUNET_TIME_Absolute first_retry); + struct GNUNET_TIME_Absolute first_retry, + uint32_t http_status, + enum TALER_ErrorCode ec); /**