Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2022-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 exchangedb/do_insert_known_coins.c
18 : * @brief Implementation of the do_insert_known_coins function for Postgres
19 : * @author Christian Grothoff
20 : * @author Özgür Kesim
21 : */
22 : #include "platform.h"
23 : #include "taler/taler_error_codes.h"
24 : #include "taler/taler_dbevents.h"
25 : #include "exchangedb_lib.h"
26 : #include "taler/taler_pq_lib.h"
27 : #include "exchange-database/do_insert_known_coins.h"
28 : #include "helper.h"
29 :
30 :
31 : /**
32 : * Closure for #known_coins_cb().
33 : */
34 : struct KnownCoinsContext
35 : {
36 : /**
37 : * The coins we asked about.
38 : */
39 : const struct TALER_CoinPublicInfo *const *coins;
40 :
41 : /**
42 : * Where to put the per-coin outcome.
43 : */
44 : struct TALER_EXCHANGEDB_CoinKnownResult *results;
45 :
46 : /**
47 : * Number of entries in @e coins and @e results.
48 : */
49 : unsigned int num_coins;
50 :
51 : /**
52 : * Number of rows we have processed so far.
53 : */
54 : unsigned int num_rows;
55 :
56 : /**
57 : * Set to true if a row could not be processed.
58 : */
59 : bool failed;
60 : };
61 :
62 :
63 : /**
64 : * Function called with one row per coin. Fills the
65 : * result entry at the coin's input position.
66 : *
67 : * @param cls a `struct KnownCoinsContext *`
68 : * @param result the result
69 : * @param num_results number of rows in @a result
70 : */
71 : static void
72 298 : known_coins_cb (void *cls,
73 : PGresult *result,
74 : unsigned int num_results)
75 : {
76 298 : struct KnownCoinsContext *kcc = cls;
77 :
78 612 : for (unsigned int i = 0; i < num_results; i++)
79 : {
80 : uint64_t idx;
81 : bool existed;
82 : uint64_t known_coin_id;
83 : bool no_known_coin_id;
84 : struct TALER_DenominationHashP h_denom_pub;
85 : bool no_denom_pub_hash;
86 : struct TALER_AgeCommitmentHashP h_age_commitment;
87 : bool no_age_commitment_hash;
88 316 : struct GNUNET_PQ_ResultSpec rs[] = {
89 316 : GNUNET_PQ_result_spec_uint64 ("out_idx",
90 : &idx),
91 316 : GNUNET_PQ_result_spec_bool ("out_existed",
92 : &existed),
93 316 : GNUNET_PQ_result_spec_allow_null (
94 : GNUNET_PQ_result_spec_uint64 ("out_known_coin_id",
95 : &known_coin_id),
96 : &no_known_coin_id),
97 316 : GNUNET_PQ_result_spec_allow_null (
98 : GNUNET_PQ_result_spec_auto_from_type ("out_denom_pub_hash",
99 : &h_denom_pub),
100 : &no_denom_pub_hash),
101 316 : GNUNET_PQ_result_spec_allow_null (
102 : GNUNET_PQ_result_spec_auto_from_type ("out_age_commitment_hash",
103 : &h_age_commitment),
104 : &no_age_commitment_hash),
105 : GNUNET_PQ_result_spec_end
106 : };
107 : const struct TALER_CoinPublicInfo *coin;
108 : struct TALER_EXCHANGEDB_CoinKnownResult *res;
109 :
110 316 : if (GNUNET_OK !=
111 316 : GNUNET_PQ_extract_result (result,
112 : rs,
113 : i))
114 : {
115 0 : GNUNET_break (0);
116 0 : kcc->failed = true;
117 2 : return;
118 : }
119 : /* out_idx is the 1-based ordinality of the input arrays */
120 316 : if ( (0 == idx) ||
121 316 : (idx > kcc->num_coins) )
122 : {
123 0 : GNUNET_break (0);
124 0 : kcc->failed = true;
125 0 : return;
126 : }
127 316 : coin = kcc->coins[idx - 1];
128 316 : res = &kcc->results[idx - 1];
129 316 : if (no_known_coin_id)
130 : {
131 : /* neither inserted nor found: the denomination is unknown */
132 2 : GNUNET_break (0);
133 2 : kcc->failed = true;
134 2 : return;
135 : }
136 314 : res->known_coin_id = known_coin_id;
137 314 : kcc->num_rows++;
138 314 : if (! existed)
139 : {
140 : /* The row was inserted by this very statement, which the
141 : query cannot see yet: what is stored is what we passed. */
142 174 : res->status = TALER_EXCHANGEDB_CKS_ADDED;
143 174 : res->h_denom_pub = coin->denom_pub_hash;
144 174 : res->no_age_commitment = coin->no_age_commitment;
145 174 : if (! coin->no_age_commitment)
146 46 : res->h_age_commitment = coin->h_age_commitment;
147 185 : continue;
148 : }
149 140 : res->no_age_commitment = no_age_commitment_hash;
150 140 : if (! no_denom_pub_hash)
151 140 : res->h_denom_pub = h_denom_pub;
152 140 : if (! no_age_commitment_hash)
153 29 : res->h_age_commitment = h_age_commitment;
154 140 : if ( (! no_denom_pub_hash) &&
155 140 : (0 != GNUNET_memcmp (&h_denom_pub,
156 : &coin->denom_pub_hash)) )
157 : {
158 6 : GNUNET_break_op (0);
159 6 : res->status = TALER_EXCHANGEDB_CKS_DENOM_CONFLICT;
160 6 : continue;
161 : }
162 134 : if (no_age_commitment_hash != coin->no_age_commitment)
163 : {
164 2 : GNUNET_break_op (0);
165 2 : res->status = no_age_commitment_hash
166 2 : ? TALER_EXCHANGEDB_CKS_AGE_CONFLICT_EXPECTED_NULL
167 2 : : TALER_EXCHANGEDB_CKS_AGE_CONFLICT_EXPECTED_NON_NULL;
168 2 : continue;
169 : }
170 132 : if ( (! no_age_commitment_hash) &&
171 28 : (0 != GNUNET_memcmp (&h_age_commitment,
172 : &coin->h_age_commitment)) )
173 : {
174 3 : GNUNET_break_op (0);
175 3 : res->status = TALER_EXCHANGEDB_CKS_AGE_CONFLICT_VALUE_DIFFERS;
176 3 : continue;
177 : }
178 129 : res->status = TALER_EXCHANGEDB_CKS_PRESENT;
179 : }
180 : }
181 :
182 :
183 : enum GNUNET_DB_QueryStatus
184 300 : TALER_EXCHANGEDB_do_insert_known_coins (
185 : struct TALER_EXCHANGEDB_PostgresContext *pg,
186 : unsigned int num_coins,
187 : const struct TALER_CoinPublicInfo *const coins[static num_coins],
188 : struct TALER_EXCHANGEDB_CoinKnownResult results[static num_coins])
189 300 : {
190 300 : struct TALER_CoinSpendPublicKeyP coin_pubs[num_coins];
191 300 : struct TALER_DenominationHashP denom_pub_hashes[num_coins];
192 300 : bool has_age_commitments[num_coins];
193 300 : struct TALER_AgeCommitmentHashP age_commitment_hashes[num_coins];
194 300 : struct TALER_DenominationSignature denom_sigs[num_coins];
195 300 : struct GNUNET_PQ_QueryParam params[] = {
196 300 : GNUNET_PQ_query_param_array_auto_from_type (num_coins,
197 : coin_pubs,
198 : pg->conn),
199 300 : GNUNET_PQ_query_param_array_auto_from_type (num_coins,
200 : denom_pub_hashes,
201 : pg->conn),
202 300 : GNUNET_PQ_query_param_array_bool (num_coins,
203 : has_age_commitments,
204 : pg->conn),
205 300 : GNUNET_PQ_query_param_array_auto_from_type (num_coins,
206 : age_commitment_hashes,
207 : pg->conn),
208 300 : TALER_PQ_query_param_array_denom_sig (num_coins,
209 : denom_sigs,
210 : pg->conn),
211 : GNUNET_PQ_query_param_end
212 : };
213 300 : struct KnownCoinsContext kcc = {
214 : .coins = coins,
215 : .results = results,
216 : .num_coins = num_coins
217 : };
218 : enum GNUNET_DB_QueryStatus qs;
219 :
220 300 : GNUNET_assert (0 < num_coins);
221 : /* Gather the columns; the arrays cannot carry NULL elements, so coins
222 : without age commitment get a zero hash and a false flag. */
223 624 : for (unsigned int i = 0; i < num_coins; i++)
224 : {
225 326 : const struct TALER_CoinPublicInfo *coin = coins[i];
226 :
227 326 : coin_pubs[i] = coin->coin_pub;
228 326 : denom_pub_hashes[i] = coin->denom_pub_hash;
229 326 : has_age_commitments[i] = ! coin->no_age_commitment;
230 326 : if (coin->no_age_commitment)
231 248 : memset (&age_commitment_hashes[i],
232 : 0,
233 : sizeof (age_commitment_hashes[i]));
234 : else
235 78 : age_commitment_hashes[i] = coin->h_age_commitment;
236 326 : denom_sigs[i] = coin->denom_sig;
237 : /* A single INSERT cannot handle the same key twice. */
238 371 : for (unsigned int j = 0; j < i; j++)
239 : {
240 47 : if (0 == GNUNET_memcmp (&coin_pubs[i],
241 : &coin_pubs[j]))
242 : {
243 2 : GNUNET_break (0);
244 2 : GNUNET_PQ_cleanup_query_params_closures (params);
245 2 : return GNUNET_DB_STATUS_HARD_ERROR;
246 : }
247 : }
248 : }
249 298 : memset (results,
250 : 0,
251 : num_coins * sizeof (*results));
252 298 : PREPARE (pg,
253 : "do_insert_known_coins",
254 : "SELECT"
255 : " out_idx"
256 : ",out_existed"
257 : ",out_known_coin_id"
258 : ",out_denom_pub_hash"
259 : ",out_age_commitment_hash"
260 : " FROM exchange_do_insert_known_coins"
261 : " ($1, $2, $3, $4, $5);");
262 298 : qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
263 : "do_insert_known_coins",
264 : params,
265 : &known_coins_cb,
266 : &kcc);
267 298 : GNUNET_PQ_cleanup_query_params_closures (params);
268 298 : if (0 > qs)
269 0 : return qs;
270 298 : if ( (kcc.failed) ||
271 296 : (kcc.num_rows != num_coins) )
272 : {
273 : /* an unknown denomination or a malformed reply */
274 2 : GNUNET_break (0);
275 2 : return GNUNET_DB_STATUS_HARD_ERROR;
276 : }
277 296 : return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
278 : }
|