do_fountain_withdraw.c (4786B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2026 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU General Public License for more details. 12 13 You should have received a copy of the GNU General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file src/backenddb/do_fountain_withdraw.c 18 * @brief Implementation of the do_fountain_withdraw function for Postgres 19 * @author Bohdan Potuzhnyi 20 */ 21 #include "platform.h" 22 #include <taler/taler_pq_lib.h> 23 #include "merchant-database/do_fountain_withdraw.h" 24 #include "helper.h" 25 26 27 enum GNUNET_DB_QueryStatus 28 TALER_MERCHANTDB_do_fountain_withdraw ( 29 struct TALER_MERCHANTDB_PostgresContext *pg, 30 const char *instance_id, 31 uint64_t fountain_serial, 32 unsigned int num_entries, 33 const uint64_t token_family_serials[static num_entries], 34 const struct GNUNET_TIME_Timestamp slot_starts[static num_entries], 35 const uint64_t num_requested[static num_entries], 36 unsigned int *failed_index, 37 bool *no_grant, 38 bool *exceeded) 39 { 40 unsigned int order[GNUNET_NZL (num_entries)]; 41 uint64_t s_families[GNUNET_NZL (num_entries)]; 42 uint64_t s_slots[GNUNET_NZL (num_entries)]; 43 uint64_t s_counts[GNUNET_NZL (num_entries)]; 44 uint32_t sql_failed_index; 45 struct GNUNET_PQ_QueryParam params[] = { 46 GNUNET_PQ_query_param_uint64 (&fountain_serial), 47 GNUNET_PQ_query_param_array_uint64 (num_entries, 48 s_families, 49 pg->conn), 50 GNUNET_PQ_query_param_array_uint64 (num_entries, 51 s_slots, 52 pg->conn), 53 GNUNET_PQ_query_param_array_uint64 (num_entries, 54 s_counts, 55 pg->conn), 56 GNUNET_PQ_query_param_end 57 }; 58 struct GNUNET_PQ_ResultSpec rs[] = { 59 GNUNET_PQ_result_spec_uint32 ("out_failed_index", 60 &sql_failed_index), 61 GNUNET_PQ_result_spec_bool ("out_no_grant", 62 no_grant), 63 GNUNET_PQ_result_spec_bool ("out_exceeded", 64 exceeded), 65 GNUNET_PQ_result_spec_end 66 }; 67 enum GNUNET_DB_QueryStatus qs; 68 69 *failed_index = num_entries; 70 *no_grant = false; 71 *exceeded = false; 72 /* Sort entries by (token_family_serial, slot_start) so that 73 concurrent withdrawals acquire the counter row locks in a 74 deterministic order and cannot deadlock. Insertion sort: the 75 number of entries is bounded by the request size cap. */ 76 for (unsigned int i = 0; i < num_entries; i++) 77 order[i] = i; 78 for (unsigned int i = 1; i < num_entries; i++) 79 { 80 unsigned int cur = order[i]; 81 unsigned int j = i; 82 83 while ( (j > 0) && 84 ( (token_family_serials[order[j - 1]] > 85 token_family_serials[cur]) || 86 ( (token_family_serials[order[j - 1]] == 87 token_family_serials[cur]) && 88 (slot_starts[order[j - 1]].abs_time.abs_value_us > 89 slot_starts[cur].abs_time.abs_value_us) ) ) ) 90 { 91 order[j] = order[j - 1]; 92 j--; 93 } 94 order[j] = cur; 95 } 96 for (unsigned int i = 0; i < num_entries; i++) 97 { 98 s_families[i] = token_family_serials[order[i]]; 99 s_slots[i] = slot_starts[order[i]].abs_time.abs_value_us; 100 s_counts[i] = num_requested[order[i]]; 101 } 102 GNUNET_assert (NULL != pg->current_merchant_id); 103 GNUNET_assert (0 == strcmp (instance_id, 104 pg->current_merchant_id)); 105 TMH_PQ_prepare_anon (pg, 106 "SELECT" 107 " out_failed_index" 108 " ,out_no_grant" 109 " ,out_exceeded" 110 " FROM merchant_do_fountain_withdraw" 111 " ($1, $2, $3, $4);"); 112 qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 113 "", 114 params, 115 rs); 116 GNUNET_PQ_cleanup_query_params_closures (params); 117 if (qs < 0) 118 return qs; 119 if (0 != sql_failed_index) 120 { 121 /* Map the 1-based index into the sorted arrays back to the 122 caller's entry order. */ 123 GNUNET_assert (sql_failed_index <= num_entries); 124 *failed_index = order[sql_failed_index - 1]; 125 } 126 return qs; 127 }