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:
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);
/**