merchant

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

commit 953c4a1b5e873d06acd33fc1fde1c24bd78aff0d
parent c73967523e2a17682e03ade6e96cabe5dd7ff4b3
Author: Christian Grothoff <christian@grothoff.org>
Date:   Mon, 29 Jun 2026 00:05:21 +0200

try to fix merchant-tokenfamilies test

Diffstat:
Msrc/backend/taler-merchant-httpd_post-orders-ORDER_ID-pay.c | 166++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
1 file changed, 110 insertions(+), 56 deletions(-)

diff --git a/src/backend/taler-merchant-httpd_post-orders-ORDER_ID-pay.c b/src/backend/taler-merchant-httpd_post-orders-ORDER_ID-pay.c @@ -344,8 +344,7 @@ struct SignedOutputToken { /** - * Index of the output token that produced - * this blindly signed token. + * Index of the output token that produced this blindly signed token. */ unsigned int output_index; @@ -481,7 +480,12 @@ struct PayContext /** * Array with @e output_tokens_len signed tokens returned in - * the response to the wallet. + * the response to the wallet. This array combines both the + * token family-signed outputs and the donation authority + * outputs. Each output has a field ``output_index`` + * which matches the index into the choice's outputs array. + * The Donau outputs are those where the `output_index` matches + * the @e validate_tokens.donau_output_index. */ struct SignedOutputToken *output_tokens; @@ -1916,6 +1920,8 @@ build_token_sigs (struct PayContext *pc) GNUNET_assert (NULL != token_sigs); for (unsigned int i = 0; i < pc->output_tokens_len; i++) { + if (NULL == pc->output_tokens[i].sig.signature) + continue; /* must be optional TF and wallet did not provide it */ GNUNET_assert (0 == json_array_append_new ( token_sigs, @@ -2099,6 +2105,8 @@ phase_final_output_token_processing (struct PayContext *pc) i < pc->output_tokens_len; i++) { + if (NULL == pc->output_tokens[i].sig.signature) + continue; /* must have been optional and not provided by wallet */ qs = TALER_MERCHANTDB_insert_order_blinded_sigs ( TMH_db, pc->order_id, @@ -2178,25 +2186,36 @@ add_donation_receipt_outputs ( size_t num_blinded_sigs, struct DONAU_BlindedDonationUnitSignature *blinded_sigs) { + unsigned int i; int donau_output_index = pc->validate_tokens.donau_output_index; GNUNET_assert (pc->parse_wallet_data.num_bkps == num_blinded_sigs); - GNUNET_assert (donau_output_index >= 0); - for (unsigned int i = 0; i<pc->output_tokens_len; i++) + /* Find position where donau tokens start in output_tokens */ + for (i = 0; i<pc->output_tokens_len; i++) { - struct SignedOutputToken *sot + const struct SignedOutputToken *sot = &pc->output_tokens[i]; /* Only look at actual donau tokens. */ - if (sot->output_index != donau_output_index) - continue; + if (sot->output_index == donau_output_index) + break; + } - sot->sig.signature = GNUNET_CRYPTO_blind_sig_incref (blinded_sigs[i]. - blinded_sig); - sot->h_issue.hash = pc->parse_wallet_data.bkps[i].h_donation_unit_pub.hash; + /* copy donau signatures into output array */ + for (unsigned int j=0; j<pc->parse_wallet_data.num_bkps; j++) + { + struct SignedOutputToken *sot; + + GNUNET_assert (i + j < pc->output_tokens_len); + GNUNET_assert (sot->output_index == donau_output_index); + sot = &pc->output_tokens[i + j]; + sot->sig.signature = GNUNET_CRYPTO_blind_sig_incref ( + blinded_sigs[j].blinded_sig); + sot->h_issue.hash + = pc->parse_wallet_data.bkps[j].h_donation_unit_pub.hash; } return GNUNET_OK; } @@ -3107,11 +3126,12 @@ phase_execute_pay_transaction (struct PayContext *pc) "Incrementing %u money pots by %s\n", pc->compute_money_pots.num_pots, TALER_amount2s (&pc->compute_money_pots.increments[0])); - qs = TALER_MERCHANTDB_increment_money_pots (TMH_db, - instance_id, - pc->compute_money_pots.num_pots, - pc->compute_money_pots.pots, - pc->compute_money_pots.increments); + qs = TALER_MERCHANTDB_increment_money_pots ( + TMH_db, + instance_id, + pc->compute_money_pots.num_pots, + pc->compute_money_pots.pots, + pc->compute_money_pots.increments); switch (qs) { case GNUNET_DB_STATUS_SOFT_ERROR: @@ -3121,10 +3141,11 @@ phase_execute_pay_transaction (struct PayContext *pc) /* Always report on hard error as well to enable diagnostics */ TALER_MERCHANTDB_rollback (TMH_db); pay_end (pc, - TALER_MHD_reply_with_error (pc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_STORE_FAILED, - "increment_money_pots")); + TALER_MHD_reply_with_error ( + pc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_STORE_FAILED, + "increment_money_pots")); return; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: /* strange */ @@ -3134,13 +3155,9 @@ phase_execute_pay_transaction (struct PayContext *pc) /* Good, proceed! */ break; } - } - - } - { const struct TALER_MERCHANT_ContractChoice *choice = &pc->check_contract.contract_terms->pc->details.v1 @@ -3154,7 +3171,6 @@ phase_execute_pay_transaction (struct PayContext *pc) 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: @@ -3175,20 +3191,24 @@ phase_execute_pay_transaction (struct PayContext *pc) &pc->output_tokens[i]; enum GNUNET_DB_QueryStatus qs; - qs = TALER_MERCHANTDB_insert_issued_token (TMH_db, - &pc->check_contract.h_contract_terms, - &output->h_issue, - &output->sig); + if (NULL == output->sig.signature) + continue; /* must have been optional and not provided by wallet */ + qs = TALER_MERCHANTDB_insert_issued_token ( + TMH_db, + &pc->check_contract.h_contract_terms, + &output->h_issue, + &output->sig); switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: TALER_MERCHANTDB_rollback (TMH_db); GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); pay_end (pc, - TALER_MHD_reply_with_error (pc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_STORE_FAILED, - "insert output token")); + TALER_MHD_reply_with_error ( + pc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_STORE_FAILED, + "insert output token")); return; case GNUNET_DB_STATUS_SOFT_ERROR: /* Serialization failure, retry */ @@ -3198,10 +3218,11 @@ phase_execute_pay_transaction (struct PayContext *pc) /* UNIQUE constraint violation, meaning this token was already used. */ TALER_MERCHANTDB_rollback (TMH_db); pay_end (pc, - TALER_MHD_reply_with_error (pc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_STORE_FAILED, - "duplicate output token")); + TALER_MHD_reply_with_error ( + pc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_STORE_FAILED, + "duplicate output token")); return; case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: break; @@ -3461,7 +3482,8 @@ test_tfk_mandatory (enum TALER_MERCHANTDB_TokenFamilyKind tfk) * @param mandatory true if the token must exist, if false * and the client did not provide an envelope, that's OK and * we just also skimp on the signature - * @param index offset in the token envelope array (from other families) + * @param wallet_index starting offset in the token envelopes array + * @param output_index starting offset into the output_tokens array * @param expected_num number of tokens of this type that we should create * @return #GNUNET_NO on failure * #GNUNET_OK on success @@ -3472,23 +3494,41 @@ sign_token_envelopes ( const struct TALER_MERCHANT_ContractTokenFamilyKey *key, const struct TALER_TokenIssuePrivateKey *priv, bool mandatory, - unsigned int index, + unsigned int wallet_index, + unsigned int output_index, unsigned int expected_num) { unsigned int num_signed = 0; for (unsigned int j = 0; j<expected_num; j++) { - unsigned int pos = index + j; + unsigned int wallet_pos = wallet_index + j; + unsigned int output_pos = output_index + j; const struct TokenEnvelope *env - = &pc->parse_wallet_data.token_envelopes[pos]; + = &pc->parse_wallet_data.token_envelopes[wallet_pos]; struct SignedOutputToken *output - = &pc->output_tokens[pos]; + = &pc->output_tokens[output_pos]; + + if (wallet_pos >= pc->parse_wallet_data.token_envelopes_cnt) + { + if (! mandatory) + return GNUNET_OK; /* wallet input too short, we can live with it */ - if ( (pos >= pc->parse_wallet_data.token_envelopes_cnt) || - (pos >= pc->output_tokens_len) ) + /* mandatory token families require a token envelope, and + the wallet did not provide enough of them */ + GNUNET_break_op (0); + pay_end (pc, + TALER_MHD_reply_with_error ( + pc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "Token envelope for mandatory token family missing")); + return GNUNET_NO; + } + if (output_pos >= pc->output_tokens_len) { - GNUNET_assert (0); /* this should not happen */ // FIXME: but it does! see merchant-tokenfamilies test! + GNUNET_assert (0); /* this should not happen, we *computed* + output_tokens_len to be big enough! */ return GNUNET_NO; } if (NULL == env->blinded_token.blinded_pub) @@ -3575,13 +3615,17 @@ find_family (const struct PayContext *pc, * Looks up the token family, loads the matching private key, * and signs the corresponding token envelopes from the wallet. * - * @param pc context for the pay request + * @param[in,out] pc context for the pay request + * @param wallet_index start index of this output in the + * ``parse_wallet_data.token_envelopes`` array * @param output contract output we need to process - * @param output_index index of this output in the contract's outputs array + * @param output_index start index of this output in the + * ``output_tokens`` array of @a pc * @return #GNUNET_OK on success, #GNUNET_NO if an error was encountered */ static enum GNUNET_GenericReturnValue handle_output_token (struct PayContext *pc, + unsigned int wallet_index, const struct TALER_MERCHANT_ContractOutput *output, unsigned int output_index) { @@ -3701,12 +3745,14 @@ handle_output_token (struct PayContext *pc, * is mandatory or optional. (Simplified logic here: adapt as needed.) */ mandatory = test_tfk_mandatory (details.token_family.kind); /* Actually sign the number of token envelopes specified in 'count'. - * 'output_index' is the offset into the parse_wallet_data arrays. */ + * 'output_index' is the offset into the output_tokens while + * 'wallet_index' is the offset into parse_wallet_data.token_envelopes */ if (GNUNET_OK != sign_token_envelopes (pc, key, &details.priv, mandatory, + wallet_index, output_index, output->details.token.count)) { @@ -3720,8 +3766,8 @@ handle_output_token (struct PayContext *pc, /** - * Handle checks for contract output of type TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT. - * For now, this does nothing and simply returns #GNUNET_OK. + * Handle checks for contract output of type + * #TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT. * * @param pc context for the pay request * @param output the contract output describing the donation receipt requirement @@ -3889,6 +3935,7 @@ phase_validate_tokens (struct PayContext *pc) = &pc->check_contract.contract_terms->pc->details.v1.choices[ pc->parse_wallet_data.choice_index]; unsigned int output_off; + unsigned int wallet_off; unsigned int cnt; pc->validate_tokens.max_fee = selected->max_fee; @@ -4016,11 +4063,12 @@ phase_validate_tokens (struct PayContext *pc) struct SignedOutputToken); /* calculate pc->output_tokens[].output_index */ - output_off = 0; + output_off = 0; /* index into output_tokens */ for (unsigned int i = 0; i<selected->outputs_len; i++) { const struct TALER_MERCHANT_ContractOutput *output = &selected->outputs[i]; + cnt = count_output_tokens (pc, output); for (unsigned int j = 0; j<cnt; j++) @@ -4029,7 +4077,8 @@ phase_validate_tokens (struct PayContext *pc) } /* compute non-donau outputs */ - output_off = 0; + output_off = 0; /* index into output_tokens */ + wallet_off = 0; /* index into parse_wallet_data.token_envelopes */ for (unsigned int i = 0; i<selected->outputs_len; i++) { const struct TALER_MERCHANT_ContractOutput *output @@ -4046,6 +4095,7 @@ phase_validate_tokens (struct PayContext *pc) <= pc->output_tokens_len); if (GNUNET_OK != handle_output_token (pc, + wallet_off, output, output_off)) { @@ -4053,6 +4103,7 @@ phase_validate_tokens (struct PayContext *pc) return; } output_off += cnt; + wallet_off += cnt; break; case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT: if ( (0 != pc->parse_wallet_data.num_bkps) && @@ -4064,6 +4115,8 @@ phase_validate_tokens (struct PayContext *pc) return; } output_off += pc->parse_wallet_data.num_bkps; + /* Note: wallet_off NOT increased, as bkps are + separate from parse_wallet_data.token_envelopes */ continue; } /* switch on output token */ } /* for all output token types */ @@ -5161,10 +5214,11 @@ phase_parse_pay (struct PayContext *pc) { GNUNET_break_op (0); pay_end (pc, - TALER_MHD_reply_with_error (pc->connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "duplicate token in list")); + TALER_MHD_reply_with_error ( + pc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "duplicate token in list")); return; } }