Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2022 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/batch_ensure_coin_known.c
18 : * @brief Implementation of the batch_ensure_coin_known function for Postgres
19 : * @author Christian Grothoff
20 : *
21 : * FIXME-#9373: use the array support for postgres to simplify this code!
22 : *
23 : */
24 : #include "taler/taler_exchangedb_lib.h"
25 : #include "taler/taler_pq_lib.h"
26 : #include "exchange-database/batch_ensure_coin_known.h"
27 : #include "helper.h"
28 :
29 :
30 : static enum GNUNET_DB_QueryStatus
31 0 : insert1 (struct TALER_EXCHANGEDB_PostgresContext *pg,
32 : const struct TALER_CoinPublicInfo coin[1],
33 : struct TALER_EXCHANGEDB_CoinInfo result[1])
34 : {
35 : enum GNUNET_DB_QueryStatus qs;
36 0 : bool is_denom_pub_hash_null = false;
37 0 : bool is_age_hash_null = false;
38 0 : struct GNUNET_PQ_QueryParam params[] = {
39 0 : GNUNET_PQ_query_param_auto_from_type (&coin[0].coin_pub),
40 0 : GNUNET_PQ_query_param_auto_from_type (&coin[0].denom_pub_hash),
41 0 : GNUNET_PQ_query_param_auto_from_type (&coin[0].h_age_commitment),
42 0 : TALER_PQ_query_param_denom_sig (&coin[0].denom_sig),
43 : GNUNET_PQ_query_param_end
44 : };
45 0 : struct GNUNET_PQ_ResultSpec rs[] = {
46 0 : GNUNET_PQ_result_spec_bool ("existed",
47 : &result[0].existed),
48 0 : GNUNET_PQ_result_spec_uint64 ("known_coin_id",
49 : &result[0].known_coin_id),
50 0 : GNUNET_PQ_result_spec_allow_null (
51 0 : GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
52 : &result[0].denom_hash),
53 : &is_denom_pub_hash_null),
54 0 : GNUNET_PQ_result_spec_allow_null (
55 0 : GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
56 : &result[0].h_age_commitment),
57 : &is_age_hash_null),
58 : GNUNET_PQ_result_spec_end
59 : };
60 :
61 0 : PREPARE (pg,
62 : "batch1_known_coin",
63 : "SELECT"
64 : " existed1 AS existed"
65 : ",known_coin_id1 AS known_coin_id"
66 : ",denom_pub_hash1 AS denom_hash"
67 : ",age_commitment_hash1 AS h_age_commitment"
68 : " FROM exchange_do_batch1_known_coin"
69 : " ($1, $2, $3, $4);"
70 : );
71 0 : qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
72 : "batch1_known_coin",
73 : params,
74 : rs);
75 0 : switch (qs)
76 : {
77 0 : case GNUNET_DB_STATUS_HARD_ERROR:
78 0 : GNUNET_break (0);
79 0 : return qs;
80 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
81 0 : return qs;
82 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
83 0 : GNUNET_break (0); /* should be impossible */
84 0 : return GNUNET_DB_STATUS_HARD_ERROR;
85 0 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
86 0 : break; /* continued below */
87 : }
88 :
89 0 : if ( (! is_denom_pub_hash_null) &&
90 0 : (0 != GNUNET_memcmp (&result[0].denom_hash,
91 : &coin->denom_pub_hash)) )
92 : {
93 0 : GNUNET_break_op (0);
94 0 : result[0].denom_conflict = true;
95 : }
96 :
97 0 : if ( (! is_denom_pub_hash_null) &&
98 0 : (0 != GNUNET_memcmp (&result[0].denom_hash,
99 : &coin[0].denom_pub_hash)) )
100 : {
101 0 : GNUNET_break_op (0);
102 0 : result[0].denom_conflict = true;
103 : }
104 :
105 0 : result[0].age_conflict = TALER_AgeCommitmentHashP_NoConflict;
106 :
107 0 : if (is_age_hash_null != coin[0].no_age_commitment)
108 : {
109 0 : if (is_age_hash_null)
110 : {
111 0 : GNUNET_break_op (0);
112 0 : result[0].age_conflict = TALER_AgeCommitmentHashP_NullExpected;
113 : }
114 : else
115 : {
116 0 : GNUNET_break_op (0);
117 0 : result[0].age_conflict = TALER_AgeCommitmentHashP_ValueExpected;
118 : }
119 : }
120 0 : else if ( (! is_age_hash_null) &&
121 0 : (0 != GNUNET_memcmp (&result[0].h_age_commitment,
122 : &coin[0].h_age_commitment)) )
123 : {
124 0 : GNUNET_break_op (0);
125 0 : result[0].age_conflict = TALER_AgeCommitmentHashP_ValueDiffers;
126 : }
127 :
128 0 : return qs;
129 : }
130 :
131 :
132 : static enum GNUNET_DB_QueryStatus
133 0 : insert2 (struct TALER_EXCHANGEDB_PostgresContext *pg,
134 : const struct TALER_CoinPublicInfo coin[2],
135 : struct TALER_EXCHANGEDB_CoinInfo result[2])
136 : {
137 : enum GNUNET_DB_QueryStatus qs;
138 0 : bool is_denom_pub_hash_null[2] = {false, false};
139 0 : bool is_age_hash_null[2] = {false, false};
140 0 : struct GNUNET_PQ_QueryParam params[] = {
141 0 : GNUNET_PQ_query_param_auto_from_type (&coin[0].coin_pub),
142 0 : GNUNET_PQ_query_param_auto_from_type (&coin[0].denom_pub_hash),
143 0 : GNUNET_PQ_query_param_auto_from_type (&coin[0].h_age_commitment),
144 0 : TALER_PQ_query_param_denom_sig (&coin[0].denom_sig),
145 :
146 0 : GNUNET_PQ_query_param_auto_from_type (&coin[1].coin_pub),
147 0 : GNUNET_PQ_query_param_auto_from_type (&coin[1].denom_pub_hash),
148 0 : GNUNET_PQ_query_param_auto_from_type (&coin[1].h_age_commitment),
149 0 : TALER_PQ_query_param_denom_sig (&coin[0].denom_sig),
150 : GNUNET_PQ_query_param_end
151 : };
152 0 : struct GNUNET_PQ_ResultSpec rs[] = {
153 0 : GNUNET_PQ_result_spec_bool ("existed",
154 : &result[0].existed),
155 0 : GNUNET_PQ_result_spec_uint64 ("known_coin_id",
156 : &result[0].known_coin_id),
157 0 : GNUNET_PQ_result_spec_allow_null (
158 0 : GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
159 : &result[0].denom_hash),
160 : &is_denom_pub_hash_null[0]),
161 0 : GNUNET_PQ_result_spec_allow_null (
162 0 : GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
163 : &result[0].h_age_commitment),
164 : &is_age_hash_null[0]),
165 0 : GNUNET_PQ_result_spec_bool ("existed2",
166 0 : &result[1].existed),
167 0 : GNUNET_PQ_result_spec_uint64 ("known_coin_id2",
168 0 : &result[1].known_coin_id),
169 0 : GNUNET_PQ_result_spec_allow_null (
170 0 : GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash2",
171 : &result[1].denom_hash),
172 : &is_denom_pub_hash_null[1]),
173 0 : GNUNET_PQ_result_spec_allow_null (
174 0 : GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash2",
175 : &result[1].h_age_commitment),
176 : &is_age_hash_null[1]),
177 : GNUNET_PQ_result_spec_end
178 : };
179 :
180 0 : PREPARE (pg,
181 : "batch2_known_coin",
182 : "SELECT"
183 : " existed1 AS existed"
184 : ",known_coin_id1 AS known_coin_id"
185 : ",denom_pub_hash1 AS denom_hash"
186 : ",age_commitment_hash1 AS h_age_commitment"
187 : ",existed2 AS existed2"
188 : ",known_coin_id2 AS known_coin_id2"
189 : ",denom_pub_hash2 AS denom_hash2"
190 : ",age_commitment_hash2 AS h_age_commitment2"
191 : " FROM exchange_do_batch2_known_coin"
192 : " ($1, $2, $3, $4, $5, $6, $7, $8);"
193 : );
194 0 : qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
195 : "batch2_known_coin",
196 : params,
197 : rs);
198 0 : switch (qs)
199 : {
200 0 : case GNUNET_DB_STATUS_HARD_ERROR:
201 0 : GNUNET_break (0);
202 0 : return qs;
203 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
204 0 : return qs;
205 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
206 0 : GNUNET_break (0); /* should be impossible */
207 0 : return GNUNET_DB_STATUS_HARD_ERROR;
208 0 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
209 0 : break; /* continued below */
210 : }
211 :
212 0 : for (int i = 0; i < 2; i++)
213 : {
214 0 : if ( (! is_denom_pub_hash_null[i]) &&
215 0 : (0 != GNUNET_memcmp (&result[i].denom_hash,
216 : &coin[i].denom_pub_hash)) )
217 : {
218 0 : GNUNET_break_op (0);
219 0 : result[i].denom_conflict = true;
220 : }
221 :
222 0 : result[i].age_conflict = TALER_AgeCommitmentHashP_NoConflict;
223 :
224 0 : if (is_age_hash_null[i] != coin[i].no_age_commitment)
225 : {
226 0 : if (is_age_hash_null[i])
227 : {
228 0 : GNUNET_break_op (0);
229 0 : result[i].age_conflict = TALER_AgeCommitmentHashP_NullExpected;
230 : }
231 : else
232 : {
233 0 : GNUNET_break_op (0);
234 0 : result[i].age_conflict = TALER_AgeCommitmentHashP_ValueExpected;
235 : }
236 : }
237 0 : else if ( (! is_age_hash_null[i]) &&
238 0 : (0 != GNUNET_memcmp (&result[i].h_age_commitment,
239 : &coin[i].h_age_commitment)) )
240 : {
241 0 : GNUNET_break_op (0);
242 0 : result[i].age_conflict = TALER_AgeCommitmentHashP_ValueDiffers;
243 : }
244 : }
245 :
246 0 : return qs;
247 : }
248 :
249 :
250 : static enum GNUNET_DB_QueryStatus
251 0 : insert4 (struct TALER_EXCHANGEDB_PostgresContext *pg,
252 : const struct TALER_CoinPublicInfo coin[4],
253 : struct TALER_EXCHANGEDB_CoinInfo result[4])
254 : {
255 : enum GNUNET_DB_QueryStatus qs;
256 0 : bool is_denom_pub_hash_null[4] = {false, false, false, false};
257 0 : bool is_age_hash_null[4] = {false, false, false, false};
258 0 : struct GNUNET_PQ_QueryParam params[] = {
259 0 : GNUNET_PQ_query_param_auto_from_type (&coin[0].coin_pub),
260 0 : GNUNET_PQ_query_param_auto_from_type (&coin[0].denom_pub_hash),
261 0 : GNUNET_PQ_query_param_auto_from_type (&coin[0].h_age_commitment),
262 0 : TALER_PQ_query_param_denom_sig (&coin[0].denom_sig),
263 :
264 0 : GNUNET_PQ_query_param_auto_from_type (&coin[1].coin_pub),
265 0 : GNUNET_PQ_query_param_auto_from_type (&coin[1].denom_pub_hash),
266 0 : GNUNET_PQ_query_param_auto_from_type (&coin[1].h_age_commitment),
267 0 : TALER_PQ_query_param_denom_sig (&coin[0].denom_sig),
268 :
269 0 : GNUNET_PQ_query_param_auto_from_type (&coin[2].coin_pub),
270 0 : GNUNET_PQ_query_param_auto_from_type (&coin[2].denom_pub_hash),
271 0 : GNUNET_PQ_query_param_auto_from_type (&coin[2].h_age_commitment),
272 0 : TALER_PQ_query_param_denom_sig (&coin[2].denom_sig),
273 :
274 0 : GNUNET_PQ_query_param_auto_from_type (&coin[3].coin_pub),
275 0 : GNUNET_PQ_query_param_auto_from_type (&coin[3].denom_pub_hash),
276 0 : GNUNET_PQ_query_param_auto_from_type (&coin[3].h_age_commitment),
277 0 : TALER_PQ_query_param_denom_sig (&coin[3].denom_sig),
278 : GNUNET_PQ_query_param_end
279 : };
280 0 : struct GNUNET_PQ_ResultSpec rs[] = {
281 0 : GNUNET_PQ_result_spec_bool ("existed",
282 : &result[0].existed),
283 0 : GNUNET_PQ_result_spec_uint64 ("known_coin_id",
284 : &result[0].known_coin_id),
285 0 : GNUNET_PQ_result_spec_allow_null (
286 0 : GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
287 : &result[0].denom_hash),
288 : &is_denom_pub_hash_null[0]),
289 0 : GNUNET_PQ_result_spec_allow_null (
290 0 : GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
291 : &result[0].h_age_commitment),
292 : &is_age_hash_null[0]),
293 0 : GNUNET_PQ_result_spec_bool ("existed2",
294 0 : &result[1].existed),
295 0 : GNUNET_PQ_result_spec_uint64 ("known_coin_id2",
296 0 : &result[1].known_coin_id),
297 0 : GNUNET_PQ_result_spec_allow_null (
298 0 : GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash2",
299 : &result[1].denom_hash),
300 : &is_denom_pub_hash_null[1]),
301 0 : GNUNET_PQ_result_spec_allow_null (
302 0 : GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash2",
303 : &result[1].h_age_commitment),
304 : &is_age_hash_null[1]),
305 0 : GNUNET_PQ_result_spec_bool ("existed3",
306 0 : &result[2].existed),
307 0 : GNUNET_PQ_result_spec_uint64 ("known_coin_id3",
308 0 : &result[2].known_coin_id),
309 0 : GNUNET_PQ_result_spec_allow_null (
310 0 : GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash3",
311 : &result[2].denom_hash),
312 : &is_denom_pub_hash_null[2]),
313 0 : GNUNET_PQ_result_spec_allow_null (
314 0 : GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash3",
315 : &result[2].h_age_commitment),
316 : &is_age_hash_null[2]),
317 0 : GNUNET_PQ_result_spec_bool ("existed4",
318 0 : &result[3].existed),
319 0 : GNUNET_PQ_result_spec_uint64 ("known_coin_id4",
320 0 : &result[3].known_coin_id),
321 0 : GNUNET_PQ_result_spec_allow_null (
322 0 : GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash4",
323 : &result[3].denom_hash),
324 : &is_denom_pub_hash_null[3]),
325 0 : GNUNET_PQ_result_spec_allow_null (
326 0 : GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash4",
327 : &result[3].h_age_commitment),
328 : &is_age_hash_null[3]),
329 : GNUNET_PQ_result_spec_end
330 : };
331 :
332 0 : PREPARE (pg,
333 : "batch4_known_coin",
334 : "SELECT"
335 : " existed1 AS existed"
336 : ",known_coin_id1 AS known_coin_id"
337 : ",denom_pub_hash1 AS denom_hash"
338 : ",age_commitment_hash1 AS h_age_commitment"
339 : ",existed2 AS existed2"
340 : ",known_coin_id2 AS known_coin_id2"
341 : ",denom_pub_hash2 AS denom_hash2"
342 : ",age_commitment_hash2 AS h_age_commitment2"
343 : ",existed3 AS existed3"
344 : ",known_coin_id3 AS known_coin_id3"
345 : ",denom_pub_hash3 AS denom_hash3"
346 : ",age_commitment_hash3 AS h_age_commitment3"
347 : ",existed4 AS existed4"
348 : ",known_coin_id4 AS known_coin_id4"
349 : ",denom_pub_hash4 AS denom_hash4"
350 : ",age_commitment_hash4 AS h_age_commitment4"
351 : " FROM exchange_do_batch2_known_coin"
352 : " ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16);"
353 : );
354 0 : qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
355 : "batch4_known_coin",
356 : params,
357 : rs);
358 0 : switch (qs)
359 : {
360 0 : case GNUNET_DB_STATUS_HARD_ERROR:
361 0 : GNUNET_break (0);
362 0 : return qs;
363 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
364 0 : return qs;
365 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
366 0 : GNUNET_break (0); /* should be impossible */
367 0 : return GNUNET_DB_STATUS_HARD_ERROR;
368 0 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
369 0 : break; /* continued below */
370 : }
371 :
372 0 : for (int i = 0; i < 4; i++)
373 : {
374 0 : if ( (! is_denom_pub_hash_null[i]) &&
375 0 : (0 != GNUNET_memcmp (&result[i].denom_hash,
376 : &coin[i].denom_pub_hash)) )
377 : {
378 0 : GNUNET_break_op (0);
379 0 : result[i].denom_conflict = true;
380 : }
381 :
382 0 : result[i].age_conflict = TALER_AgeCommitmentHashP_NoConflict;
383 :
384 0 : if (is_age_hash_null[i] != coin[i].no_age_commitment)
385 : {
386 0 : if (is_age_hash_null[i])
387 : {
388 0 : GNUNET_break_op (0);
389 0 : result[i].age_conflict = TALER_AgeCommitmentHashP_NullExpected;
390 : }
391 : else
392 : {
393 0 : GNUNET_break_op (0);
394 0 : result[i].age_conflict = TALER_AgeCommitmentHashP_ValueExpected;
395 : }
396 : }
397 0 : else if ( (! is_age_hash_null[i]) &&
398 0 : (0 != GNUNET_memcmp (&result[i].h_age_commitment,
399 : &coin[i].h_age_commitment)) )
400 : {
401 0 : GNUNET_break_op (0);
402 0 : result[i].age_conflict = TALER_AgeCommitmentHashP_ValueDiffers;
403 : }
404 : }
405 :
406 0 : return qs;
407 : }
408 :
409 :
410 : enum GNUNET_DB_QueryStatus
411 0 : TALER_EXCHANGEDB_batch_ensure_coin_known (
412 : struct TALER_EXCHANGEDB_PostgresContext *pg,
413 : const struct TALER_CoinPublicInfo *coin,
414 : struct TALER_EXCHANGEDB_CoinInfo *result,
415 : unsigned int coin_length,
416 : unsigned int batch_size)
417 : {
418 0 : enum GNUNET_DB_QueryStatus qs = 0;
419 0 : unsigned int i = 0;
420 :
421 0 : while ( (qs >= 0) &&
422 : (i < coin_length) )
423 : {
424 0 : unsigned int bs = GNUNET_MIN (batch_size,
425 : coin_length - i);
426 0 : if (bs >= 4)
427 : {
428 0 : qs = insert4 (pg,
429 0 : &coin[i],
430 0 : &result[i]);
431 0 : i += 4;
432 0 : continue;
433 : }
434 0 : switch (bs)
435 : {
436 0 : case 3:
437 : case 2:
438 0 : qs = insert2 (pg,
439 0 : &coin[i],
440 0 : &result[i]);
441 0 : i += 2;
442 0 : break;
443 0 : case 1:
444 0 : qs = insert1 (pg,
445 0 : &coin[i],
446 0 : &result[i]);
447 0 : i += 1;
448 0 : break;
449 0 : case 0:
450 0 : GNUNET_assert (0);
451 : break;
452 : }
453 : } /* end while */
454 0 : if (qs < 0)
455 0 : return qs;
456 0 : return i;
457 : }
|