commit a5919709416909e9c193489cfca56013fa3958f2
parent fe8d74937835ffa0c29ea13838b78839008578ba
Author: Christian Grothoff <grothoff@gnunet.org>
Date: Fri, 23 Jan 2026 11:04:54 +0900
implement better error reporting for #10907
Diffstat:
2 files changed, 147 insertions(+), 48 deletions(-)
diff --git a/src/backend/taler-merchant-httpd_private-post-orders.c b/src/backend/taler-merchant-httpd_private-post-orders.c
@@ -78,7 +78,7 @@
* refuses a forced download.
*/
#define MAX_KEYS_WAIT \
- GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 2500)
+ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 2500)
/**
* Generate the base URL for the given merchant instance.
@@ -585,6 +585,12 @@ struct OrderContext
struct GNUNET_SCHEDULER_Task *wakeup_task;
/**
+ * Array of reasons why a particular exchange may be
+ * limited or not be eligible.
+ */
+ json_t *exchange_rejections;
+
+ /**
* Did we previously force reloading of /keys from
* all exchanges? Set to 'true' to prevent us from
* doing it again (and again...).
@@ -985,6 +991,11 @@ clean_order (void *cls)
json_decref (oc->select_wire_method.exchanges);
oc->select_wire_method.exchanges = NULL;
}
+ if (NULL != oc->set_exchanges.exchange_rejections)
+ {
+ json_decref (oc->set_exchanges.exchange_rejections);
+ oc->set_exchanges.exchange_rejections = NULL;
+ }
switch (oc->parse_order.version)
{
case TALER_MERCHANT_CONTRACT_VERSION_0:
@@ -1297,6 +1308,34 @@ execute_transaction (struct OrderContext *oc)
/**
+ * The request was successful, generate the #MHD_HTTP_OK response.
+ *
+ * @param[in,out] oc context to update
+ * @param claim_token claim token to use, NULL if none
+ */
+static void
+yield_success_response (struct OrderContext *oc,
+ const struct TALER_ClaimTokenP *claim_token)
+{
+ MHD_RESULT ret;
+
+ ret = TALER_MHD_REPLY_JSON_PACK (
+ oc->connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_string ("order_id",
+ oc->parse_order.order_id),
+ GNUNET_JSON_pack_timestamp ("pay_deadline",
+ oc->parse_order.pay_deadline),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_data_auto (
+ "token",
+ claim_token)));
+ finalize_order (oc,
+ ret);
+}
+
+
+/**
* Transform an order into a proposal and store it in the
* database. Write the resulting proposal or an error message
* of a MHD connection.
@@ -1357,25 +1396,10 @@ phase_execute_order (struct OrderContext *oc)
/* DB transaction succeeded, check for idempotent */
if (oc->execute_order.idempotent)
{
- // FIXME: duplicated code...
- MHD_RESULT ret;
-
- ret = TALER_MHD_REPLY_JSON_PACK (
- oc->connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_string ("order_id",
- oc->parse_order.order_id),
- GNUNET_JSON_pack_timestamp ("pay_deadline",
- oc->parse_order.pay_deadline),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_data_varsize (
- "token",
- GNUNET_is_zero (&oc->execute_order.token)
- ? NULL
- : &oc->execute_order.token,
- sizeof (oc->execute_order.token))));
- finalize_order (oc,
- ret);
+ yield_success_response (oc,
+ GNUNET_is_zero (&oc->execute_order.token)
+ ? NULL
+ : &oc->execute_order.token);
return;
}
if (oc->execute_order.conflict)
@@ -1505,28 +1529,10 @@ phase_execute_order (struct OrderContext *oc)
/* Everything in-stock, generate positive response */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Order creation succeeded\n");
-
- {
- // FIXME: duplicated code...
- MHD_RESULT ret;
-
- ret = TALER_MHD_REPLY_JSON_PACK (
- oc->connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_string ("order_id",
- oc->parse_order.order_id),
- GNUNET_JSON_pack_timestamp ("pay_deadline",
- oc->parse_order.pay_deadline),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_data_varsize (
- "token",
- GNUNET_is_zero (&oc->parse_request.claim_token)
- ? NULL
- : &oc->parse_request.claim_token,
- sizeof (oc->parse_request.claim_token))));
- finalize_order (oc,
- ret);
- }
+ yield_success_response (oc,
+ GNUNET_is_zero (&oc->parse_request.claim_token)
+ ? NULL
+ : &oc->parse_request.claim_token);
}
@@ -2647,12 +2653,20 @@ phase_select_wire_method (struct OrderContext *oc)
if (NULL == best)
{
+ MHD_RESULT mret;
+
/* We actually do not have ANY workable exchange(s) */
- reply_with_error (
- oc,
- MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
- TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_AMOUNT_EXCEEDS_LEGAL_LIMITS,
- NULL);
+ mret = TALER_MHD_reply_json_steal (
+ oc->connection,
+ GNUNET_JSON_PACK (
+ TALER_JSON_pack_ec (
+ TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_AMOUNT_EXCEEDS_LEGAL_LIMITS),
+ GNUNET_JSON_pack_array_steal (
+ "exchange_rejections",
+ oc->set_exchanges.exchange_rejections)),
+ MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS);
+ finalize_order (oc,
+ mret);
return;
}
@@ -2825,6 +2839,34 @@ notify_kyc_required (const struct OrderContext *oc,
/**
+ * Add a reason why a particular exchange was rejected to our
+ * response data.
+ *
+ * @param[in,out] oc order context to update
+ * @param exchange_url exchange this is about
+ * @param ec error code to set for the exchange
+ */
+static void
+add_rejection (struct OrderContext *oc,
+ const char *exchange_url,
+ enum TALER_ErrorCode ec)
+{
+ if (NULL == oc->set_exchanges.exchange_rejections)
+ {
+ oc->set_exchanges.exchange_rejections = json_array ();
+ GNUNET_assert (NULL != oc->set_exchanges.exchange_rejections);
+ }
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ oc->set_exchanges.exchange_rejections,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("exchange_url",
+ exchange_url),
+ TALER_JSON_pack_ec (ec))));
+}
+
+
+/**
* Checks the limits that apply for this @a exchange and
* the @a wmc and if the exchange is acceptable at all, adds it
* to the list of exchanges for the @a wmc.
@@ -2847,7 +2889,9 @@ get_acceptable (struct OrderContext *oc,
enum TMH_ExchangeStatus res;
struct TALER_Amount max_amount;
- for (unsigned int i = 0; i<oc->add_payment_details.num_max_choice_limits; i++)
+ for (unsigned int i = 0;
+ i<oc->add_payment_details.num_max_choice_limits;
+ i++)
{
struct TALER_Amount *val = &oc->add_payment_details.max_choice_limits[i];
@@ -2861,6 +2905,13 @@ get_acceptable (struct OrderContext *oc,
if (NULL == max_needed)
{
/* exchange currency not relevant for any of our choices, skip it */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Exchange %s with currency `%s' is not applicable to this order\n",
+ exchange_url,
+ TMH_EXCHANGES_get_currency (exchange));
+ add_rejection (oc,
+ exchange_url,
+ TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH);
return false;
}
@@ -2890,6 +2941,9 @@ get_acceptable (struct OrderContext *oc,
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Exchange %s deposit limit is zero, skipping it\n",
exchange_url);
+ add_rejection (oc,
+ exchange_url,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_LEGALLY_REFUSED);
return false;
}
switch (res)
@@ -3004,6 +3058,9 @@ keys_cb (
"Failed to download %skeys\n",
rx->url);
oc->set_exchanges.promising_exchange = true;
+ add_rejection (oc,
+ rx->url,
+ TALER_EC_MERCHANT_GENERIC_EXCHANGE_KEYS_FAILURE);
goto cleanup;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -3016,10 +3073,17 @@ keys_cb (
struct TALER_FullPayto full_payto = keys->accounts[j].fpayto_uri;
char *wire_method = TALER_payto_get_method (full_payto.full_payto);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Exchange `%s' has wire method `%s'\n",
+ rx->url,
+ wire_method);
for (struct WireMethodCandidate *wmc = oc->add_payment_details.wmc_head;
NULL != wmc;
wmc = wmc->next)
{
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Order could use wire method `%s'\n",
+ wmc->wm->wire_method);
if (0 == strcmp (wmc->wm->wire_method,
wire_method) )
{
@@ -3031,6 +3095,21 @@ keys_cb (
}
GNUNET_free (wire_method);
}
+ if ( (! applicable) &&
+ (! oc->set_exchanges.forced_reload) )
+ {
+ /* Checks for 'forced_reload' to not log the error *again*
+ if we forced a re-load and are encountering the
+ applicability error a 2nd time */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Exchange `%s' %u wire methods are not applicable to this order\n",
+ rx->url,
+ keys->accounts_len);
+ add_rejection (oc,
+ rx->url,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_WIRE_METHOD_UNSUPPORTED)
+ ;
+ }
if (applicable &&
settings->use_stefan)
update_stefan (oc,
diff --git a/src/backend/taler-merchant-kyccheck.c b/src/backend/taler-merchant-kyccheck.c
@@ -1089,6 +1089,8 @@ find_accounts (void *cls)
if (qs < 0)
{
GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
return;
}
for (struct Account *a = a_head;
@@ -1128,6 +1130,8 @@ account_changed (void *cls,
(void) extra_size;
if (NULL != account_task)
return;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received account change notification: reloading accounts\n");
account_task
= GNUNET_SCHEDULER_add_now (&find_accounts,
NULL);
@@ -1155,6 +1159,8 @@ find_keys (const char *exchange_url)
if (qs < 0)
{
GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
return;
}
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
@@ -1232,11 +1238,15 @@ keys_changed (void *cls,
(0 == extra_size) )
{
GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
return;
}
if ('\0' != url[extra_size - 1])
{
GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
return;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -1270,11 +1280,15 @@ rule_triggered (void *cls,
(0 == extra_size) )
{
GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
return;
}
if ('\0' != text[extra_size - 1])
{
GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
return;
}
space = memchr (extra,
@@ -1283,6 +1297,8 @@ rule_triggered (void *cls,
if (NULL == space)
{
GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
return;
}
if (GNUNET_OK !=
@@ -1292,12 +1308,16 @@ rule_triggered (void *cls,
sizeof (h_wire)))
{
GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
return;
}
exchange_url = &space[1];
if (! TALER_is_web_url (exchange_url))
{
GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
return;
}