commit c926288c9c36394efda1007c7d130cad86832c13
parent ac8357c5a6a45c114214395a1684dd306ade1c10
Author: Florian Dold <florian@dold.me>
Date: Thu, 16 Oct 2025 18:33:34 +0200
fix idempotent pay requests with tokens
Diffstat:
1 file changed, 62 insertions(+), 18 deletions(-)
diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
@@ -464,6 +464,13 @@ struct PayContext
unsigned int output_index_gen;
/**
+ * Counter used to generate the output index in append_output_token_sig().
+ *
+ * Counts the generated tokens _within_ the current output_index_gen.
+ */
+ unsigned int output_token_cnt;
+
+ /**
* HTTP status code to use for the reply, i.e 200 for "OK".
* Special value UINT_MAX is used to indicate hard errors
* (no reply, return #MHD_NO).
@@ -3484,6 +3491,37 @@ handle_output_donation_receipt (
/**
+ * Count tokens produced by an output.
+ *
+ * @param pc pay context
+ * @param output output to consider
+ * @returns number of output tokens
+ */
+static unsigned int
+count_output_tokens (const struct PayContext *pc,
+ const struct TALER_MERCHANT_ContractOutput *output)
+{
+ switch (output->type)
+ {
+ case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID:
+ GNUNET_assert (0);
+ break;
+ case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
+ return output->details.token.count;
+ case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
+#ifdef HAVE_DONAU_DONAU_SERVICE_H
+ return pc->parse_wallet_data.num_bkps;
+#else
+ return 0;
+#endif
+ break;
+ }
+ /* Not reached. */
+ GNUNET_assert (0);
+}
+
+
+/**
* Validate tokens and token envelopes. First, we check if all tokens listed
* in the 'inputs' array of the selected choice are present in the 'tokens'
* array of the request. Then, we validate the signatures of each provided
@@ -3648,23 +3686,8 @@ phase_validate_tokens (struct PayContext *pc)
const struct TALER_MERCHANT_ContractOutput *output
= &selected->outputs[i];
- cnt = 0;
- switch (output->type)
- {
- case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID:
- GNUNET_assert (0);
- break;
- case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
- cnt = output->details.token.count;
- output_off += cnt;
- break;
- case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
-#ifdef HAVE_DONAU_DONAU_SERVICE_H
- cnt = pc->parse_wallet_data.num_bkps;
- output_off += pc->parse_wallet_data.num_bkps;
-#endif
- break;
- }
+ output_off += count_output_tokens (pc,
+ output);
for (unsigned int j = 0; j<cnt; j++)
pc->output_tokens[output_off + j].output_index = i;
}
@@ -3850,13 +3873,34 @@ append_output_token_sig (void *cls,
{
struct PayContext *pc = cls;
struct SignedOutputToken out;
+ struct TALER_MERCHANT_ContractChoice *choice;
+ const struct TALER_MERCHANT_ContractOutput *output;
+ unsigned int cnt;
+
+ GNUNET_assert (TALER_MERCHANT_CONTRACT_VERSION_1 ==
+ pc->check_contract.contract_terms->version);
- out.output_index = pc->output_index_gen++;
+ choice = &pc->check_contract.contract_terms->details.v1
+ .choices[pc->parse_wallet_data.choice_index];
+ output = &choice->outputs[pc->output_index_gen];
+ cnt = count_output_tokens (pc,
+ output);
+
+ out.output_index = pc->output_index_gen;
out.h_issue.hash = *h_issue;
out.sig.signature = sig;
GNUNET_array_append (pc->output_tokens,
pc->output_tokens_len,
out);
+
+
+ /* Go to next output once we've output all tokens for the current one. */
+ pc->output_token_cnt++;
+ if (pc->output_token_cnt >= cnt)
+ {
+ pc->output_token_cnt = 0;
+ pc->output_index_gen++;
+ }
}