exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

commit 97666f86c43df5573e278aa64f6476a419fbd0d1
parent 2fdf4d18f2227880385cff40fd107e7b467a4271
Author: Christian Grothoff <christian@grothoff.org>
Date:   Thu,  6 Aug 2026 17:48:37 +0200

age commitment can be NULL as per schema, handle it nicely with purses

Diffstat:
Msrc/exchange/taler-exchange-httpd_post-purses-PURSE_PUB-create.c | 14++++++++++----
Msrc/exchange/taler-exchange-httpd_post-purses-PURSE_PUB-deposit.c | 14++++++++++----
Msrc/exchangedb/get_purse_deposit.c | 7+++++--
Msrc/exchangedb/test_regressions.c | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/include/exchange-database/get_purse_deposit.h | 3+++
5 files changed, 111 insertions(+), 10 deletions(-)

diff --git a/src/exchange/taler-exchange-httpd_post-purses-PURSE_PUB-create.c b/src/exchange/taler-exchange-httpd_post-purses-PURSE_PUB-create.c @@ -266,7 +266,9 @@ create_transaction (void *cls, struct TALER_Amount amount; struct TALER_CoinSpendSignatureP coin_sig; struct TALER_DenominationHashP h_denom_pub; - struct TALER_AgeCommitmentHashP phac; + struct TALER_AgeCommitmentHashP hac; + const struct TALER_AgeCommitmentHashP *phac = NULL; + bool no_age_commitment; char *partner_url = NULL; TALER_EXCHANGEDB_rollback (TEH_pg); @@ -275,7 +277,8 @@ create_transaction (void *cls, &coin->cpi.coin_pub, &amount, &h_denom_pub, - &phac, + &hac, + &no_age_commitment, &coin_sig, &partner_url); if (qs < 0) @@ -289,6 +292,8 @@ create_transaction (void *cls, "get purse deposit"); return GNUNET_DB_STATUS_HARD_ERROR; } + if (! no_age_commitment) + phac = &hac; *mhd_ret = TALER_MHD_REPLY_JSON_PACK ( @@ -302,8 +307,9 @@ create_transaction (void *cls, &coin_sig), GNUNET_JSON_pack_data_auto ("h_denom_pub", &h_denom_pub), - GNUNET_JSON_pack_data_auto ("h_age_restrictions", - &phac), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_data_auto ("h_age_restrictions", + phac)), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("partner_url", partner_url)), diff --git a/src/exchange/taler-exchange-httpd_post-purses-PURSE_PUB-deposit.c b/src/exchange/taler-exchange-httpd_post-purses-PURSE_PUB-deposit.c @@ -227,7 +227,9 @@ deposit_transaction (void *cls, struct TALER_Amount amount; struct TALER_CoinSpendSignatureP coin_sig; struct TALER_DenominationHashP h_denom_pub; - struct TALER_AgeCommitmentHashP phac; + struct TALER_AgeCommitmentHashP hac; + const struct TALER_AgeCommitmentHashP *phac = NULL; + bool no_age_commitment; char *partner_url = NULL; TALER_EXCHANGEDB_rollback (TEH_pg); @@ -236,7 +238,8 @@ deposit_transaction (void *cls, &coin->cpi.coin_pub, &amount, &h_denom_pub, - &phac, + &hac, + &no_age_commitment, &coin_sig, &partner_url); if (qs < 0) @@ -250,6 +253,8 @@ deposit_transaction (void *cls, "get purse deposit"); return GNUNET_DB_STATUS_HARD_ERROR; } + if (! no_age_commitment) + phac = &hac; *mhd_ret = TALER_MHD_REPLY_JSON_PACK ( @@ -261,8 +266,9 @@ deposit_transaction (void *cls, &coin->cpi.coin_pub), GNUNET_JSON_pack_data_auto ("h_denom_pub", &h_denom_pub), - GNUNET_JSON_pack_data_auto ("h_age_commitment", - &phac), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_data_auto ("h_age_commitment", + phac)), GNUNET_JSON_pack_data_auto ("coin_sig", &coin_sig), GNUNET_JSON_pack_allow_null ( diff --git a/src/exchangedb/get_purse_deposit.c b/src/exchangedb/get_purse_deposit.c @@ -30,6 +30,7 @@ TALER_EXCHANGEDB_get_purse_deposit ( struct TALER_Amount *amount, struct TALER_DenominationHashP *h_denom_pub, struct TALER_AgeCommitmentHashP *phac, + bool *no_age_commitment, struct TALER_CoinSpendSignatureP *coin_sig, char **partner_url) { @@ -42,8 +43,10 @@ TALER_EXCHANGEDB_get_purse_deposit ( struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash", h_denom_pub), - GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash", - phac), + GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash", + phac), + no_age_commitment), GNUNET_PQ_result_spec_auto_from_type ("coin_sig", coin_sig), TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", diff --git a/src/exchangedb/test_regressions.c b/src/exchangedb/test_regressions.c @@ -32,6 +32,7 @@ #include "exchange-database/commit.h" #include "exchange-database/rollback.h" #include "exchange-database/do_reserve_open.h" +#include "exchange-database/get_purse_deposit.h" /** @@ -494,6 +495,86 @@ check_reserve_open_int4_overflows (void) /** + * E-5: `known_coins.age_commitment_hash` is NULL for every coin without an + * age commitment -- the common case -- but the result spec read it without + * GNUNET_PQ_result_spec_allow_null(), so building the 409 conflict proof for + * POST /purses/$PURSE_PUB/deposit failed with an HTTP 500 instead. + */ +static int +check_purse_deposit_without_age_commitment (void) +{ + struct TALER_PurseContractPublicKeyP purse_pub; + struct TALER_CoinSpendPublicKeyP coin_pub; + struct TALER_Amount amount; + struct TALER_DenominationHashP h_denom_pub; + struct TALER_AgeCommitmentHashP hac; + struct TALER_CoinSpendSignatureP coin_sig; + bool no_age_commitment; + char *partner_url = NULL; + char *sql; + char *p_hex; + char *c_hex; + enum GNUNET_GenericReturnValue ok; + + memset (&purse_pub, + 0x55, + sizeof (purse_pub)); + memset (&coin_pub, + 0x56, + sizeof (coin_pub)); + p_hex = to_hex (&purse_pub, + sizeof (purse_pub)); + c_hex = to_hex (&coin_pub, + sizeof (coin_pub)); + GNUNET_asprintf ( + &sql, + "INSERT INTO denominations" + " (denom_pub_hash,denom_type,age_mask,denom_pub,master_sig" + " ,valid_from,expire_withdraw,expire_deposit,expire_legal" + " ,coin,fee_withdraw,fee_deposit,fee_refresh,fee_refund)" + " VALUES (decode(repeat('e5',64),'hex'),1,0,decode('00','hex')" + " ,decode(repeat('00',64),'hex'),0,0,0,0" + " ,ROW(1,0)::taler_amount,ROW(0,0)::taler_amount" + " ,ROW(0,0)::taler_amount,ROW(0,0)::taler_amount" + " ,ROW(0,0)::taler_amount);" + /* age_commitment_hash deliberately left NULL */ + "INSERT INTO known_coins" + " (denominations_serial,coin_pub,denom_sig,remaining)" + " VALUES ((SELECT denominations_serial FROM denominations" + " WHERE denom_pub_hash=decode(repeat('e5',64),'hex'))" + " ,decode('%s','hex'),decode('00','hex')" + " ,ROW(0,0)::taler_amount);" + "INSERT INTO purse_deposits" + " (purse_pub,coin_pub,amount_with_fee,coin_sig)" + " VALUES (decode('%s','hex'),decode('%s','hex')" + " ,ROW(1,0)::taler_amount,decode(repeat('e5',64),'hex'));", + c_hex, + p_hex, + c_hex); + ok = exec_sql (sql); + GNUNET_free (sql); + GNUNET_free (p_hex); + GNUNET_free (c_hex); + FAILIF (GNUNET_OK != ok); + + /* Before the fix: HARD_ERROR from the failed NULL extraction. */ + FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != + TALER_EXCHANGEDB_get_purse_deposit (pg, + &purse_pub, + &coin_pub, + &amount, + &h_denom_pub, + &hac, + &no_age_commitment, + &coin_sig, + &partner_url)); + GNUNET_free (partner_url); + FAILIF (! no_age_commitment); + return 0; +} + + +/** * All checks we know about. */ static const struct @@ -509,6 +590,8 @@ static const struct &check_reserve_open_never_expires }, { "reserve-open-int4-overflows", &check_reserve_open_int4_overflows }, + { "purse-deposit-without-age-commitment", + &check_purse_deposit_without_age_commitment }, { NULL, NULL } }; diff --git a/src/include/exchange-database/get_purse_deposit.h b/src/include/exchange-database/get_purse_deposit.h @@ -36,6 +36,8 @@ * @param[out] amount set fraction of the coin's value that was deposited (with fee) * @param[out] h_denom_pub set to hash of denomination of the coin * @param[out] phac set to hash of age restriction on the coin + * @param[out] no_age_commitment set to true if the coin has no age + * restriction, in which case @a phac is left unchanged * @param[out] coin_sig set to signature affirming the operation * @param[out] partner_url set to the URL of the partner exchange, or NULL for ourselves, must be freed by caller * @return transaction status code @@ -49,6 +51,7 @@ TALER_EXCHANGEDB_get_purse_deposit (struct TALER_EXCHANGEDB_PostgresContext *pg, struct TALER_Amount *amount, struct TALER_DenominationHashP *h_denom_pub, struct TALER_AgeCommitmentHashP *phac, + bool *no_age_commitment, struct TALER_CoinSpendSignatureP *coin_sig, char **partner_url);