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:
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;
}
}