commit f05990eb80339bb5f5137af0c4b84eeeb942044a
parent 03cb0a556a7856247689bb698ca9fb0a7f2f021d
Author: Florian Dold <dold@taler.net>
Date: Thu, 27 Aug 2026 23:29:47 +0200
exchange /keys: document withdrawal denomination filter
Diffstat:
1 file changed, 23 insertions(+), 7 deletions(-)
diff --git a/src/exchange/taler-exchange-httpd_get-keys.c b/src/exchange/taler-exchange-httpd_get-keys.c
@@ -218,7 +218,8 @@ struct TEH_KeyStateHandle
struct KeysResponseData *withdraw_krd_array;
/**
- * Length of @e withdraw_krd_array. Either zero or one.
+ * Length of @e withdraw_krd_array. The filtered response is not
+ * cherry-picked, so this is either zero or one.
*/
unsigned int withdraw_krd_array_length;
@@ -246,13 +247,14 @@ struct TEH_KeyStateHandle
struct GNUNET_TIME_Relative rekey_frequency;
/**
- * When does our online signing key expire and we
- * thus need to re-generate this response?
+ * When the key state must next be rebuilt due to signing key expiration or
+ * a change in response contents.
*/
struct GNUNET_TIME_Timestamp signature_expires;
/**
* When the membership of the withdrawal-focused response next changes.
+ * Used as its HTTP expiration time.
*/
struct GNUNET_TIME_Timestamp withdraw_expiration;
@@ -1785,7 +1787,8 @@ get_withdraw_family_key (const struct TEH_DenominationKey *dk,
/**
- * Find the earliest future key start for every denomination family.
+ * Find the next withdrawable denomination of every family. This requires a
+ * separate pass because denomination map iteration is unordered.
*
* @param cls a `struct FindWithdrawFamiliesContext *`
* @param h_denom_pub denomination hash, unused
@@ -1833,7 +1836,7 @@ find_withdraw_family_cb (void *cls,
/**
- * Mark current denominations and each family's earliest future denomination.
+ * Mark the current and next withdrawable denomination of every family.
*
* @param cls a `struct SelectWithdrawDenominationsContext *`
* @param h_denom_pub denomination hash, unused
@@ -1887,6 +1890,7 @@ select_withdraw_denomination_cb (void *cls,
return GNUNET_OK;
dk->withdraw_relevant = true;
ctx->selected++;
+ /* Rebuild the response and expire client caches at the first rotation. */
ctx->ksh->withdraw_expiration = GNUNET_TIME_timestamp_min (
ctx->ksh->withdraw_expiration,
transition);
@@ -1918,7 +1922,9 @@ free_withdraw_family (void *cls,
/**
- * Select denominations for `denom_filter=withdraw`.
+ * Select denominations for `denom_filter=withdraw`. The filtered query
+ * returns each family's current and next withdrawable denomination, covering
+ * the next key rotation while omitting expired and later keys.
*
* @param[in,out] ksh key state containing the denominations
* @param now exchange time to use for selection
@@ -1958,7 +1964,8 @@ select_withdraw_denominations (struct TEH_KeyStateHandle *ksh,
/**
* Build auditor information containing signatures only for denominations in
* the withdrawal-focused response. Auditor identity metadata is retained
- * even when none of its denomination signatures is selected.
+ * even without selected signatures, so filtering only changes denomination
+ * coverage.
*
* @param ksh key state with full auditor information
* @return newly allocated JSON array
@@ -2010,6 +2017,7 @@ build_withdraw_auditors (const struct TEH_KeyStateHandle *ksh)
json_array_append (filtered_keys,
denom_sig));
}
+ /* Keep the auditor data for the unfiltered response unchanged. */
filtered_auditor = json_deep_copy (auditor);
GNUNET_assert (NULL != filtered_auditor);
GNUNET_assert (0 ==
@@ -2043,6 +2051,7 @@ setup_response_headers (struct TEH_KeyStateHandle *ksh,
MHD_add_response_header (response,
MHD_HTTP_HEADER_CONTENT_TYPE,
"application/json"));
+ /* A fixed max-age would override the filtered response's exact expiration. */
GNUNET_break (MHD_YES ==
MHD_add_response_header (response,
MHD_HTTP_HEADER_CACHE_CONTROL,
@@ -2567,6 +2576,8 @@ compute_msig_hash (struct SignatureContext *sig_ctx,
/**
* Add one denomination to a grouped denomination response.
+ * Denominations with equal cipher, value, fees and age mask share group
+ * metadata. The signature context records their resulting array positions.
*
* @param[in,out] denominations_by_group map used to find existing groups
* @param[in,out] grouped_denominations JSON array of denomination groups
@@ -2799,6 +2810,7 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
GNUNET_CONTAINER_multihashmap_create (1024,
GNUNET_NO /* NO, because keys are only on the stack */
);
+ /* Filtering changes group and denomination offsets. */
withdraw_denominations_by_group
= GNUNET_CONTAINER_multihashmap_create (128,
false);
@@ -2850,6 +2862,7 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
&has_age_restricted_denomination);
if (dk->withdraw_relevant)
{
+ /* Newest-first iteration leaves the oldest selected issue date. */
withdraw_list_issue_date = dk->meta.start;
append_denomination (withdraw_denominations_by_group,
withdraw_denominations,
@@ -2895,12 +2908,14 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
GNUNET_TIME_timestamp2s (last_cherry_pick_date));
goto CLEANUP;
}
+ /* An empty selection is reported as missing keys, not a valid response. */
if (0 != withdraw_denomination_count)
{
struct GNUNET_HashCode withdraw_hc;
GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
withdraw_list_issue_date.abs_time));
+ /* Filtering changes group offsets and therefore the signature hash. */
compute_msig_hash (&withdraw_sig_ctx,
&withdraw_hc);
if (GNUNET_OK !=
@@ -3429,6 +3444,7 @@ TEH_keys_get_handler (struct TEH_RequestContext *rc,
have_cherrypick = MHD_lookup_connection_value (rc->connection,
MHD_GET_ARGUMENT_KIND,
"last_issue_date");
+ /* The withdrawal filter has one response for the current key state. */
if (withdraw_filter &&
(NULL != have_cherrypick))
return TALER_MHD_reply_with_error (