exchange

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

commit 1f66a0f72638f071dd1a30cc617db87f8b8dabef
parent 9ff5cad379154ed45845d02890428362a1a4b2d3
Author: Özgür Kesim <oec@codeblau.de>
Date:   Mon, 14 Sep 2026 20:47:05 +0200

exchangedb: recoup procedures return the credited amount

The batch recoup responses report the amount credited per coin,
so exchange_do_recoup_to_reserve() and exchange_do_recoup_to_coin()
now return it (the residual value they credit, or the recorded amount on
an idempotent replay), and do_recoup()/do_recoup_refresh() expose it.
The table and history tests assert it.

Diffstat:
Msrc/exchangedb/do_recoup.c | 21++++++++++++++++++++-
Msrc/exchangedb/do_recoup.sql | 14+++++++++++---
Msrc/exchangedb/do_recoup_refresh.c | 20+++++++++++++++++++-
Msrc/exchangedb/do_recoup_refresh.sql | 14+++++++++++---
Msrc/exchangedb/test_coin_history.c | 7+++++++
Msrc/exchangedb/test_recoup.c | 6++++++
Msrc/exchangedb/test_recoup_refresh.c | 6++++++
Msrc/exchangedb/test_reserve_history.c | 5+++++
Msrc/include/exchange-database/do_recoup.h | 2++
Msrc/include/exchange-database/do_recoup_refresh.h | 2++
10 files changed, 89 insertions(+), 8 deletions(-)

diff --git a/src/exchangedb/do_recoup.c b/src/exchangedb/do_recoup.c @@ -19,6 +19,7 @@ * @author Christian Grothoff */ #include "taler/taler_pq_lib.h" +#include "taler/taler_pq_lib.h" #include "exchange-database/do_recoup.h" #include "helper.h" @@ -34,6 +35,7 @@ TALER_EXCHANGEDB_do_recoup ( uint64_t known_coin_id, const struct TALER_CoinSpendSignatureP *coin_sig, struct GNUNET_TIME_Timestamp *recoup_timestamp, + struct TALER_Amount *recoup_amount, bool *recoup_ok, bool *internal_failure) { @@ -55,11 +57,17 @@ TALER_EXCHANGEDB_do_recoup ( GNUNET_PQ_query_param_end }; bool is_null; + bool amount_is_null; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_allow_null ( GNUNET_PQ_result_spec_timestamp ("recoup_timestamp", recoup_timestamp), &is_null), + GNUNET_PQ_result_spec_allow_null ( + TALER_PQ_result_spec_amount ("recoup_amount", + pg->currency, + recoup_amount), + &amount_is_null), GNUNET_PQ_result_spec_bool ("recoup_ok", recoup_ok), GNUNET_PQ_result_spec_bool ("internal_failure", @@ -72,12 +80,23 @@ TALER_EXCHANGEDB_do_recoup ( "do_recoup", "SELECT " " out_recoup_timestamp AS recoup_timestamp" + ",out_recoup_amount AS recoup_amount" ",out_recoup_ok AS recoup_ok" ",out_internal_failure AS internal_failure" " FROM exchange_do_recoup_to_reserve" " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10);"); - return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + { + enum GNUNET_DB_QueryStatus qs; + + qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "do_recoup", params, rs); + if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) || + amount_is_null) + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (pg->currency, + recoup_amount)); + return qs; + } } diff --git a/src/exchangedb/do_recoup.sql b/src/exchangedb/do_recoup.sql @@ -31,7 +31,8 @@ CREATE FUNCTION exchange_do_recoup_to_reserve( IN in_recoup_timestamp INT8, OUT out_recoup_ok BOOLEAN, OUT out_internal_failure BOOLEAN, - OUT out_recoup_timestamp INT8) + OUT out_recoup_timestamp INT8, + OUT out_recoup_amount taler_amount) LANGUAGE plpgsql AS $$ DECLARE @@ -73,15 +74,21 @@ THEN -- withdraw operation and position counts, a recoup of the same -- coin for another operation means the coin has nothing left. SELECT - recoup_timestamp + recoup_timestamp + ,amount INTO - out_recoup_timestamp + rval FROM exchange.recoup WHERE coin_pub=in_coin_pub AND withdraw_id=in_withdraw_id AND coin_index=in_coin_index; out_recoup_ok=FOUND; + IF FOUND + THEN + out_recoup_timestamp=rval.recoup_timestamp; + out_recoup_amount=rval.amount; + END IF; RETURN; END IF; @@ -153,6 +160,7 @@ VALUES -- Normal end, everything is fine. out_recoup_ok=TRUE; out_recoup_timestamp=in_recoup_timestamp; +out_recoup_amount=tmp; END $$; diff --git a/src/exchangedb/do_recoup_refresh.c b/src/exchangedb/do_recoup_refresh.c @@ -34,6 +34,7 @@ TALER_EXCHANGEDB_do_recoup_refresh ( uint64_t known_coin_id, const struct TALER_CoinSpendSignatureP *coin_sig, struct GNUNET_TIME_Timestamp *recoup_timestamp, + struct TALER_Amount *recoup_amount, bool *recoup_ok, bool *internal_failure) { @@ -49,11 +50,17 @@ TALER_EXCHANGEDB_do_recoup_refresh ( GNUNET_PQ_query_param_end }; bool is_null; + bool amount_is_null; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_allow_null ( GNUNET_PQ_result_spec_timestamp ("recoup_timestamp", recoup_timestamp), &is_null), + GNUNET_PQ_result_spec_allow_null ( + TALER_PQ_result_spec_amount ("recoup_amount", + pg->currency, + recoup_amount), + &amount_is_null), GNUNET_PQ_result_spec_bool ("recoup_ok", recoup_ok), GNUNET_PQ_result_spec_bool ("internal_failure", @@ -66,13 +73,24 @@ TALER_EXCHANGEDB_do_recoup_refresh ( "do_recoup_refresh", "SELECT " " out_recoup_timestamp AS recoup_timestamp" + ",out_recoup_amount AS recoup_amount" ",out_recoup_ok AS recoup_ok" ",out_internal_failure AS internal_failure" " FROM exchange_do_recoup_to_coin" " ($1,$2,$3,$4,$5,$6,$7,$8);"); - return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + { + enum GNUNET_DB_QueryStatus qs; + + qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "do_recoup_refresh", params, rs); + if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) || + amount_is_null) + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (pg->currency, + recoup_amount)); + return qs; + } } diff --git a/src/exchangedb/do_recoup_refresh.sql b/src/exchangedb/do_recoup_refresh.sql @@ -26,7 +26,8 @@ CREATE FUNCTION exchange_do_recoup_to_coin( IN in_recoup_timestamp INT8, OUT out_recoup_ok BOOLEAN, OUT out_internal_failure BOOLEAN, - OUT out_recoup_timestamp INT8) + OUT out_recoup_timestamp INT8, + OUT out_recoup_amount taler_amount) LANGUAGE plpgsql AS $$ DECLARE @@ -65,14 +66,20 @@ THEN -- refresh operation and position counts, a recoup of the same -- coin for another operation means the coin has nothing left. SELECT - recoup_timestamp + recoup_timestamp + ,amount INTO - out_recoup_timestamp + rval FROM recoup_refresh WHERE coin_pub=in_coin_pub AND refresh_id=in_refresh_id AND coin_index=in_coin_index; out_recoup_ok=FOUND; + IF FOUND + THEN + out_recoup_timestamp=rval.recoup_timestamp; + out_recoup_amount=rval.amount; + END IF; RETURN; END IF; @@ -132,6 +139,7 @@ VALUES -- Normal end, everything is fine. out_recoup_ok=TRUE; out_recoup_timestamp=in_recoup_timestamp; +out_recoup_amount=tmp; END $$; diff --git a/src/exchangedb/test_coin_history.c b/src/exchangedb/test_coin_history.c @@ -637,6 +637,7 @@ check_recoup (struct TALER_EXCHANGEDB_PostgresContext *pg) struct TALER_CoinSpendSignatureP coin_sig; union GNUNET_CRYPTO_BlindingSecretP coin_bks; struct GNUNET_TIME_Timestamp recoup_timestamp; + struct TALER_Amount recoup_amount; struct TALER_Amount balance; struct TALER_Amount zero = TDB_amount ("0"); struct TALER_DenominationHashP h_denom_pub; @@ -662,6 +663,7 @@ check_recoup (struct TALER_EXCHANGEDB_PostgresContext *pg) known_coin_id, &coin_sig, &recoup_timestamp, + &recoup_amount, &recoup_ok, &internal_failure)); FAILIF (internal_failure); @@ -783,6 +785,7 @@ check_recoup_refresh (struct TALER_EXCHANGEDB_PostgresContext *pg) struct TALER_CoinSpendSignatureP coin_sig; union GNUNET_CRYPTO_BlindingSecretP coin_bks; struct GNUNET_TIME_Timestamp recoup_timestamp; + struct TALER_Amount recoup_amount; struct TALER_Amount balance; struct TALER_Amount zero = TDB_amount ("0"); struct TALER_Amount credited = TDB_amount ("0.1"); @@ -839,11 +842,15 @@ check_recoup_refresh (struct TALER_EXCHANGEDB_PostgresContext *pg) fresh_known_coin_id, &coin_sig, &recoup_timestamp, + &recoup_amount, &recoup_ok, &internal_failure), TDB_coin_free (&fresh_coin)); FAILIF_C (internal_failure || ! recoup_ok, TDB_coin_free (&fresh_coin)); + FAILIF_C (0 != TALER_amount_cmp (&recoup_amount, + &credited), + TDB_coin_free (&fresh_coin)); /* the old coin is credited and sees the receiver entry */ FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != diff --git a/src/exchangedb/test_recoup.c b/src/exchangedb/test_recoup.c @@ -155,6 +155,11 @@ struct RecoupStatus * When the recoup happened. */ struct GNUNET_TIME_Timestamp recoup_timestamp; + + /** + * Amount the recoup credited. + */ + struct TALER_Amount recoup_amount; }; @@ -201,6 +206,7 @@ run_recoup (struct TALER_EXCHANGEDB_PostgresContext *pg, known_coin_id, &coin_sig, &st->recoup_timestamp, + &st->recoup_amount, &st->recoup_ok, &st->internal_failure); } diff --git a/src/exchangedb/test_recoup_refresh.c b/src/exchangedb/test_recoup_refresh.c @@ -208,6 +208,11 @@ struct RecoupStatus * When the recoup happened. */ struct GNUNET_TIME_Timestamp recoup_timestamp; + + /** + * Amount the recoup credited. + */ + struct TALER_Amount recoup_amount; }; @@ -254,6 +259,7 @@ run_recoup (struct TALER_EXCHANGEDB_PostgresContext *pg, known_coin_id, &coin_sig, &st->recoup_timestamp, + &st->recoup_amount, &st->recoup_ok, &st->internal_failure); } diff --git a/src/exchangedb/test_reserve_history.c b/src/exchangedb/test_reserve_history.c @@ -311,6 +311,7 @@ check_recoup (struct TALER_EXCHANGEDB_PostgresContext *pg) struct TALER_CoinSpendSignatureP coin_sig; union GNUNET_CRYPTO_BlindingSecretP coin_bks; struct GNUNET_TIME_Timestamp recoup_timestamp; + struct TALER_Amount recoup_amount; struct TALER_EXCHANGEDB_ReserveHistory *rh = NULL; struct TALER_Amount balance; struct TALER_Amount expect_value = TDB_amount ("5"); @@ -361,11 +362,15 @@ check_recoup (struct TALER_EXCHANGEDB_PostgresContext *pg) known_coin_id, &coin_sig, &recoup_timestamp, + &recoup_amount, &recoup_ok, &internal_failure), TDB_coin_free (&coin); TDB_denom_free (&denom)); FAILIF_C (internal_failure || ! recoup_ok, TDB_coin_free (&coin); TDB_denom_free (&denom)); + FAILIF_C (0 != TALER_amount_cmp (&recoup_amount, + &expect_value), + TDB_coin_free (&coin); TDB_denom_free (&denom)); FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != TALER_EXCHANGEDB_get_reserve_history (pg, &reserve_pub, diff --git a/src/include/exchange-database/do_recoup.h b/src/include/exchange-database/do_recoup.h @@ -39,6 +39,7 @@ * @param known_coin_id row of the @a coin_pub in the known_coins table * @param coin_sig signature of the coin requesting the recoup * @param[in,out] recoup_timestamp recoup timestamp, set if recoup existed + * @param[out] recoup_amount set to the amount credited (zero if the recoup did not happen) * @param[out] recoup_ok set if the recoup succeeded (balance ok) * @param[out] internal_failure set on internal failures * @return query execution status @@ -53,6 +54,7 @@ TALER_EXCHANGEDB_do_recoup (struct TALER_EXCHANGEDB_PostgresContext *pg, uint64_t known_coin_id, const struct TALER_CoinSpendSignatureP *coin_sig, struct GNUNET_TIME_Timestamp *recoup_timestamp, + struct TALER_Amount *recoup_amount, bool *recoup_ok, bool *internal_failure); diff --git a/src/include/exchange-database/do_recoup_refresh.h b/src/include/exchange-database/do_recoup_refresh.h @@ -40,6 +40,7 @@ * @param known_coin_id row of the @a coin_pub in the known_coins table * @param coin_sig signature of the coin requesting the recoup * @param[in,out] recoup_timestamp recoup timestamp, set if recoup existed + * @param[out] recoup_amount set to the amount credited (zero if the recoup did not happen) * @param[out] recoup_ok set if the recoup-refresh succeeded (balance ok) * @param[out] internal_failure set on internal failures * @return query execution status @@ -62,6 +63,7 @@ TALER_EXCHANGEDB_do_recoup_refresh (struct TALER_CoinSpendSignatureP *coin_sig, struct GNUNET_TIME_Timestamp * recoup_timestamp, + struct TALER_Amount *recoup_amount, bool *recoup_ok, bool *internal_failure);