merchant

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

commit 44b048c200843a3206216f461752f87225cac06c
parent 24a97957451a6580ddf3d533834c4088414adcfc
Author: Florian Dold <florian@dold.me>
Date:   Tue,  7 Oct 2025 18:22:09 +0200

fix output token handling

There was a major confusion between output token and signed output
tokens. One output token generates zero or more signed output tokens
depending on the wallet data.

Diffstat:
Msrc/backend/taler-merchant-httpd_post-orders-ID-pay.c | 109++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
1 file changed, 66 insertions(+), 43 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 @@ -316,6 +316,12 @@ struct SignedOutputToken { /** + * Index of the output token that produced + * this blindly signed token. + */ + unsigned int output_index; + + /** * Blinded token use public keys waiting to be signed. */ struct TALER_BlindedTokenIssueSignature sig; @@ -672,6 +678,12 @@ struct PayContext */ struct TALER_Amount brutto; + /** + * Index of the donau output in the list of tokens. + * Set to -1 if no donau output exists. + */ + int donau_output_index; + } validate_tokens; /** @@ -2017,52 +2029,21 @@ add_donation_receipt_outputs ( size_t num_blinded_sigs, struct DONAU_BlindedDonationUnitSignature *blinded_sigs) { - const struct TALER_MERCHANT_ContractChoice *choice - = &pc->check_contract.contract_terms->details.v1.choices[ - pc->parse_wallet_data.choice_index]; - unsigned int off = 0; + int donau_output_index = pc->validate_tokens.donau_output_index; GNUNET_assert (pc->parse_wallet_data.num_bkps == num_blinded_sigs); - for (unsigned int i = 0; i < choice->outputs_len; i++) - { - const struct TALER_MERCHANT_ContractOutput *output - = &choice->outputs[i]; + GNUNET_assert (donau_output_index >= 0); - switch (output->type) - { - case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID: - GNUNET_assert (0); - break; - case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN: - GNUNET_assert (off + output->details.token.count >= off); - off += output->details.token.count; - continue; - case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT: - break; -#if FUTURE - case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_COIN: - GNUNET_break (0); - pay_end (pc, - TALER_MHD_reply_with_error ( - pc->connection, - MHD_HTTP_NOT_IMPLEMENTED, - TALER_EC_MERCHANT_GENERIC_FEATURE_NOT_AVAILABLE, - "token type not yet supported")); - return GNUNET_SYSERR; -#endif - } - /* must have been the donau case we care about */ - break; - } - - GNUNET_assert (off + num_blinded_sigs >= off); - GNUNET_assert (off + num_blinded_sigs <= pc->output_tokens_len); for (unsigned int i = 0; i<pc->output_tokens_len; i++) { struct SignedOutputToken *sot - = &pc->output_tokens[off + i]; + = &pc->output_tokens[i]; + + /* Only look at actual donau tokens. */ + if (sot->output_index != donau_output_index) + continue; // FIXME: in the future, use incref here once // we change libdonau to do decref! @@ -2831,7 +2812,14 @@ phase_execute_pay_transaction (struct PayContext *pc) for (size_t i = 0; i<pc->output_tokens_len; i++) { - switch (choice->outputs[i].type) + unsigned int output_index; + enum TALER_MERCHANT_ContractOutputType type; + + output_index = pc->output_tokens[i].output_index; + GNUNET_assert (output_index < choice->outputs_len); + type = choice->outputs[output_index].type; + + switch (type) { case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID: /* Well, good luck getting here */ @@ -2843,7 +2831,7 @@ phase_execute_pay_transaction (struct PayContext *pc) "invalid output type")); break; case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT: - /* We skip outputs of donation receipts here, as they are handled in the + /* We skip output tokens of donation receipts here, as they are handled in the * phase_final_output_token_processing() callback from donau */ break; case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN: @@ -3486,6 +3474,9 @@ handle_output_donation_receipt ( static void phase_validate_tokens (struct PayContext *pc) { + /* We haven't seen a donau output yet. */ + pc->validate_tokens.donau_output_index = -1; + switch (pc->check_contract.contract_terms->version) { case TALER_MERCHANT_CONTRACT_VERSION_0: @@ -3503,7 +3494,6 @@ phase_validate_tokens (struct PayContext *pc) pc->parse_wallet_data.choice_index]; unsigned int output_off; unsigned int cnt; - bool donau_seen = false; pc->validate_tokens.max_fee = selected->max_fee; pc->validate_tokens.brutto = selected->amount; @@ -3593,7 +3583,7 @@ phase_validate_tokens (struct PayContext *pc) break; case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT: /* check that this output type appears at most once */ - if (donau_seen) + if (pc->validate_tokens.donau_output_index >= 0) { /* This should have been prevented when the contract was initially created */ @@ -3606,7 +3596,7 @@ phase_validate_tokens (struct PayContext *pc) "two donau output sets in same contract")); return; } - donau_seen = true; + pc->validate_tokens.donau_output_index = i; #ifdef HAVE_DONAU_DONAU_SERVICE_H if (output_off + pc->parse_wallet_data.num_bkps < output_off) { @@ -3625,11 +3615,40 @@ phase_validate_tokens (struct PayContext *pc) } } + pc->output_tokens_len = output_off; pc->output_tokens = GNUNET_new_array (pc->output_tokens_len, struct SignedOutputToken); + /* calculate pc->output_tokens[].output_index */ + output_off = 0; + for (unsigned int i = 0; i<selected->outputs_len; i++) + { + 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; + } + for (unsigned int j = 0; j<cnt; j++) + pc->output_tokens[output_off + j].output_index = i; + } + /* compute non-donau outputs */ output_off = 0; for (unsigned int i = 0; i<selected->outputs_len; i++) @@ -4910,6 +4929,10 @@ TMH_post_orders_ID_pay (const struct TMH_RequestHandler *rh, return MHD_YES; case PP_END_NO: return MHD_NO; + default: + /* should not be reachable */ + GNUNET_assert (0); + return MHD_NO; } switch (pc->suspended) {