commit 0acdec7b3de4e2019660dbbfa25a19f3c90a2b38
parent 4eb89e723fcc79de51a88ad889495407db9300bb
Author: Christian Grothoff <christian@grothoff.org>
Date: Tue, 13 Oct 2026 00:33:52 +0200
prevent full-table scan by selecting also on reserve_pub
Diffstat:
10 files changed, 72 insertions(+), 25 deletions(-)
diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c
@@ -1765,7 +1765,8 @@ handle_mhd_request (void *cls,
.handler.post = &handle_post_reserves,
.nargs = 2
},
- /* reveal-withdraw operation, introduced with v26 */
+ /* reveal-withdraw operation, introduced with v26;
+ reserve_pub required in the body since v42 */
{
.url = "reveal-withdraw",
.method = MHD_HTTP_METHOD_POST,
diff --git a/src/exchange/taler-exchange-httpd_post-recoup-withdraw.c b/src/exchange/taler-exchange-httpd_post-recoup-withdraw.c
@@ -361,6 +361,7 @@ phase_lookup_operation (struct RecoupWithdrawContext *wc)
while (0 < max_retries--)
{
qs = TALER_EXCHANGEDB_get_withdraw (TEH_pg,
+ &wc->request.reserve_pub,
&wc->request.planchets_h,
&wc->withdraw);
if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
@@ -385,15 +386,8 @@ phase_lookup_operation (struct RecoupWithdrawContext *wc)
return;
}
wc->have_withdraw = true;
- if (0 !=
- GNUNET_memcmp (&wc->withdraw.reserve_pub,
- &wc->request.reserve_pub))
- {
- GNUNET_break_op (0);
- SET_ERROR (wc,
- RECOUP_WITHDRAW_ERROR_WITHDRAW_NOT_FOUND);
- return;
- }
+ /* The lookup is keyed on (reserve_pub, planchets_h), so the
+ withdraw found belongs to the reserve given in the request. */
if (wc->withdraw.num_coins != wc->request.num_coins)
{
GNUNET_break_op (0);
diff --git a/src/exchange/taler-exchange-httpd_post-reveal-withdraw.c b/src/exchange/taler-exchange-httpd_post-reveal-withdraw.c
@@ -38,6 +38,11 @@ struct WithdrawRevealContext
{
/**
+ * Public key of the reserve the original withdraw was made from.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
* Commitment for the withdraw operation, previously called by the
* client.
*/
@@ -50,7 +55,7 @@ struct WithdrawRevealContext
/**
* The data from the original withdraw. Will be retrieved from
- * the DB via @a wch.
+ * the DB via @e reserve_pub and @e planchets_h.
*/
struct TALER_EXCHANGEDB_Withdraw withdraw;
};
@@ -136,6 +141,7 @@ parse_withdraw_reveal_json (
* Otherwise, it queues an appropriate MHD response.
*
* @param connection The HTTP connection to the client
+ * @param reserve_pub Reserve the original withdraw request was made from
* @param planchets_h Original commitment value sent with the withdraw request
* @param[out] withdraw Data from the original withdraw request
* @param[out] result In the error cases, a response will be queued with MHD and this will be the result.
@@ -145,6 +151,7 @@ parse_withdraw_reveal_json (
static enum GNUNET_GenericReturnValue
find_original_withdraw (
struct MHD_Connection *connection,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
const struct TALER_HashBlindedPlanchetsP *planchets_h,
struct TALER_EXCHANGEDB_Withdraw *withdraw,
enum MHD_Result *result)
@@ -154,6 +161,7 @@ find_original_withdraw (
for (unsigned int try = 0; try < 3; try++)
{
qs = TALER_EXCHANGEDB_get_withdraw (TEH_pg,
+ reserve_pub,
planchets_h,
withdraw);
switch (qs)
@@ -615,6 +623,8 @@ TEH_handler_reveal_withdraw (
struct WithdrawRevealContext actx = {0};
const json_t *j_disclosed_batch_seeds;
struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("reserve_pub",
+ &actx.reserve_pub),
GNUNET_JSON_spec_fixed_auto ("planchets_h",
&actx.planchets_h),
GNUNET_JSON_spec_array_const ("disclosed_batch_seeds",
@@ -646,6 +656,7 @@ TEH_handler_reveal_withdraw (
if (GNUNET_OK !=
find_original_withdraw (
rc->connection,
+ &actx.reserve_pub,
&actx.planchets_h,
&actx.withdraw,
&result))
diff --git a/src/exchange/taler-exchange-httpd_post-withdraw.c b/src/exchange/taler-exchange-httpd_post-withdraw.c
@@ -383,6 +383,7 @@ withdraw_is_idempotent (
{
qs = TALER_EXCHANGEDB_get_withdraw (
TEH_pg,
+ &wc->request.withdraw.reserve_pub,
&wc->request.withdraw.planchets_h,
&wc->request.withdraw_idem);
if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
diff --git a/src/exchangedb/get_withdraw.c b/src/exchangedb/get_withdraw.c
@@ -26,11 +26,13 @@
enum GNUNET_DB_QueryStatus
TALER_EXCHANGEDB_get_withdraw (
struct TALER_EXCHANGEDB_PostgresContext *pg,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
const struct TALER_HashBlindedPlanchetsP *wch,
struct TALER_EXCHANGEDB_Withdraw *wd)
{
enum GNUNET_DB_QueryStatus ret;
struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (reserve_pub),
GNUNET_PQ_query_param_auto_from_type (wch),
GNUNET_PQ_query_param_end
};
@@ -117,7 +119,8 @@ TALER_EXCHANGEDB_get_withdraw (
",denom_sigs"
",denom_serials"
" FROM withdraw"
- " WHERE planchets_h=$1;");
+ " WHERE reserve_pub=$1"
+ " AND planchets_h=$2;");
ret = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"get_withdraw",
params,
diff --git a/src/exchangedb/test_withdraw.c b/src/exchangedb/test_withdraw.c
@@ -386,6 +386,7 @@ check_unknown_reserve (struct TALER_EXCHANGEDB_PostgresContext *pg)
FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
TALER_EXCHANGEDB_get_withdraw (pg,
+ &reserve_pub,
&h,
&got));
}
@@ -494,12 +495,13 @@ check_withdraw (struct TALER_EXCHANGEDB_PostgresContext *pg)
&expect_left),
free_withdraw (&wd));
- /* the request can be looked up by its planchet hash */
+ /* the request can be looked up by its reserve and planchet hash */
memset (&got,
0,
sizeof (got));
FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
TALER_EXCHANGEDB_get_withdraw (pg,
+ &reserve_pub,
&wd.planchets_h,
&got),
free_withdraw (&wd));
@@ -526,6 +528,24 @@ check_withdraw (struct TALER_EXCHANGEDB_PostgresContext *pg)
free_withdraw (&got); free_withdraw (&wd));
free_withdraw (&got);
+ /* the planchet hash is only unique per reserve: another reserve does
+ not see this request */
+ {
+ struct TALER_ReservePublicKeyP other_pub;
+
+ TDB_FILL (other_pub,
+ 99);
+ memset (&got,
+ 0,
+ sizeof (got));
+ FAILIF_C (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ TALER_EXCHANGEDB_get_withdraw (pg,
+ &other_pub,
+ &wd.planchets_h,
+ &got),
+ free_withdraw (&wd));
+ }
+
/* a replay of the same request is idempotent and does not debit again */
FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
run_withdraw (pg,
diff --git a/src/include/exchange-database/get_withdraw.h b/src/include/exchange-database/get_withdraw.h
@@ -26,20 +26,25 @@
/**
- * Locate the response for a withdraw request under a hash that uniquely
- * identifies the withdraw operation. Used to ensure idempotency of the
- * request.
- *
+ * Locate the response for a withdraw request under its primary key, the
+ * reserve and the hash over the blinded planchets. Used to ensure
+ * idempotency of the request, for /reveal-withdraw and for
+ * /recoup-withdraw. The same @a wch may legitimately exist under
+ * several reserves, so the lookup must always include the reserve.
+ *
* Primary test table: `withdraw` (see test_withdraw.c).
*
- * @param pg the database context
- * @param wch hash over all hashes of blinded planchets that uniquely identifies the withdraw operation
- * @param[out] wd corresponding details of the previous withdraw request if an entry was found
- * @return statement execution status
- */
+ * @param pg the database context
+ * @param reserve_pub public key of the reserve the coins were withdrawn from
+ * @param wch hash over all hashes of blinded planchets that uniquely identifies the withdraw operation of @a reserve_pub
+ * @param[out] wd corresponding details of the previous withdraw request if an entry was found
+ * @return statement execution status
+ */
enum GNUNET_DB_QueryStatus
-TALER_EXCHANGEDB_get_withdraw (struct TALER_EXCHANGEDB_PostgresContext *pg,
- const struct TALER_HashBlindedPlanchetsP *wch,
- struct TALER_EXCHANGEDB_Withdraw *wd);
+TALER_EXCHANGEDB_get_withdraw (
+ struct TALER_EXCHANGEDB_PostgresContext *pg,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_HashBlindedPlanchetsP *wch,
+ struct TALER_EXCHANGEDB_Withdraw *wd);
#endif
diff --git a/src/include/taler/exchange/post-reveal-withdraw.h b/src/include/taler/exchange/post-reveal-withdraw.h
@@ -44,6 +44,7 @@ struct TALER_EXCHANGE_PostRevealWithdrawHandle;
* @param curl_ctx The curl context
* @param exchange_url The base url of the exchange
* @param num_coins Number of coin signatures to expect from the reveal
+ * @param reserve_pub The reserve the previous call to withdraw was made from
* @param h_planchets The commitment from the previous call to withdraw
* @param seeds TALER_CNC_KAPPA-1 tuple of seeds to reveal
* @return handle to operation, NULL on error
@@ -53,6 +54,7 @@ TALER_EXCHANGE_post_reveal_withdraw_create (
struct GNUNET_CURL_Context *curl_ctx,
const char *exchange_url,
size_t num_coins,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
const struct TALER_HashBlindedPlanchetsP *h_planchets,
const struct TALER_RevealWithdrawMasterSeedsP *seeds);
diff --git a/src/lib/exchange_api_post-reveal-withdraw.c b/src/lib/exchange_api_post-reveal-withdraw.c
@@ -40,6 +40,11 @@
struct TALER_EXCHANGE_PostRevealWithdrawHandle
{
/**
+ * The reserve the previous call to withdraw was made from
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
* The commitment from the previous call to withdraw
*/
struct TALER_HashBlindedPlanchetsP planchets_h;
@@ -287,6 +292,8 @@ perform_protocol (
json_t *j_request_body;
j_request_body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ &wrh->reserve_pub),
GNUNET_JSON_pack_data_auto ("planchets_h",
&wrh->planchets_h),
GNUNET_JSON_pack_array_steal ("disclosed_batch_seeds",
@@ -324,6 +331,7 @@ TALER_EXCHANGE_post_reveal_withdraw_create (
struct GNUNET_CURL_Context *curl_ctx,
const char *exchange_url,
size_t num_coins,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
const struct TALER_HashBlindedPlanchetsP *h_planchets,
const struct TALER_RevealWithdrawMasterSeedsP *seeds)
{
@@ -333,6 +341,7 @@ TALER_EXCHANGE_post_reveal_withdraw_create (
wrh->curl_ctx = curl_ctx;
wrh->exchange_url = GNUNET_strdup (exchange_url);
wrh->num_coins = num_coins;
+ wrh->reserve_pub = *reserve_pub;
wrh->planchets_h = *h_planchets;
wrh->seeds = *seeds;
return wrh;
diff --git a/src/testing/testing_api_cmd_age_withdraw.c b/src/testing/testing_api_cmd_age_withdraw.c
@@ -750,6 +750,7 @@ age_reveal_withdraw_run (
TALER_TESTING_interpreter_get_context (is),
TALER_TESTING_get_exchange_url (is),
aws->num_coins,
+ &aws->reserve_pub,
&aws->planchets_h,
&revealed_seeds);
GNUNET_assert (NULL != awrs->handle);