Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2022-2023 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 get_coin_transactions.c
18 : * @brief Low-level (statement-level) Postgres database access for the exchange
19 : * @author Christian Grothoff
20 : */
21 : #include "taler/taler_error_codes.h"
22 : #include "exchangedb_lib.h"
23 : #include "taler/taler_pq_lib.h"
24 : #include "exchange-database/get_coin_transactions.h"
25 : #include "helper.h"
26 : #include "exchange-database/start_read_committed.h"
27 : #include "exchange-database/commit.h"
28 : #include "exchange-database/rollback.h"
29 :
30 :
31 : /**
32 : * How often do we re-try when encountering DB serialization issues?
33 : * (We are read-only, so can only happen due to concurrent insert,
34 : * which should be very rare.)
35 : */
36 : #define RETRIES 3
37 :
38 : /**
39 : * Closure for callbacks called from #TALER_EXCHANGEDB_get_coin_transactions()
40 : */
41 : struct CoinHistoryContext
42 : {
43 : /**
44 : * Head of the coin's history list.
45 : */
46 : struct TALER_EXCHANGEDB_TransactionList *head;
47 :
48 : /**
49 : * Public key of the coin we are building the history for.
50 : */
51 : const struct TALER_CoinSpendPublicKeyP *coin_pub;
52 :
53 : /**
54 : * Plugin context.
55 : */
56 : struct TALER_EXCHANGEDB_PostgresContext *pg;
57 :
58 : /**
59 : * Our current offset in the coin history.
60 : */
61 : uint64_t chid;
62 :
63 : /**
64 : * Set to 'true' if the transaction failed.
65 : */
66 : bool failed;
67 :
68 : };
69 :
70 :
71 : /**
72 : * Function to be called with the results of a SELECT statement
73 : * that has returned @a num_results results.
74 : *
75 : * @param cls closure of type `struct CoinHistoryContext`
76 : * @param result the postgres result
77 : * @param num_results the number of results in @a result
78 : */
79 : static void
80 75 : add_coin_deposit (void *cls,
81 : PGresult *result,
82 : unsigned int num_results)
83 : {
84 75 : struct CoinHistoryContext *chc = cls;
85 75 : struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
86 :
87 150 : for (unsigned int i = 0; i < num_results; i++)
88 : {
89 : struct TALER_EXCHANGEDB_DepositListEntry *deposit;
90 : struct TALER_EXCHANGEDB_TransactionList *tl;
91 : uint64_t serial_id;
92 :
93 75 : deposit = GNUNET_new (struct TALER_EXCHANGEDB_DepositListEntry);
94 : {
95 75 : struct GNUNET_PQ_ResultSpec rs[] = {
96 75 : TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
97 : &deposit->amount_with_fee),
98 75 : TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
99 : &deposit->deposit_fee),
100 75 : GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
101 : &deposit->h_denom_pub),
102 75 : GNUNET_PQ_result_spec_allow_null (
103 75 : GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
104 : &deposit->h_age_commitment),
105 : &deposit->no_age_commitment),
106 75 : GNUNET_PQ_result_spec_allow_null (
107 75 : GNUNET_PQ_result_spec_auto_from_type ("wallet_data_hash",
108 : &deposit->wallet_data_hash),
109 : &deposit->no_wallet_data_hash),
110 75 : GNUNET_PQ_result_spec_timestamp ("wallet_timestamp",
111 : &deposit->timestamp),
112 75 : GNUNET_PQ_result_spec_timestamp ("refund_deadline",
113 : &deposit->refund_deadline),
114 75 : GNUNET_PQ_result_spec_timestamp ("wire_deadline",
115 : &deposit->wire_deadline),
116 75 : GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
117 : &deposit->merchant_pub),
118 75 : GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
119 : &deposit->h_contract_terms),
120 75 : GNUNET_PQ_result_spec_auto_from_type ("wire_salt",
121 : &deposit->wire_salt),
122 75 : GNUNET_PQ_result_spec_string ("payto_uri",
123 : &deposit->receiver_wire_account.full_payto
124 : ),
125 75 : GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
126 : &deposit->csig),
127 75 : GNUNET_PQ_result_spec_uint64 ("coin_deposit_serial_id",
128 : &serial_id),
129 75 : GNUNET_PQ_result_spec_auto_from_type ("done",
130 : &deposit->done),
131 : GNUNET_PQ_result_spec_end
132 : };
133 :
134 75 : if (GNUNET_OK !=
135 75 : GNUNET_PQ_extract_result (result,
136 : rs,
137 : i))
138 : {
139 0 : GNUNET_break (0);
140 0 : GNUNET_free (deposit);
141 0 : chc->failed = true;
142 0 : return;
143 : }
144 : }
145 75 : tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
146 75 : tl->next = chc->head;
147 75 : tl->type = TALER_EXCHANGEDB_TT_DEPOSIT;
148 75 : tl->details.deposit = deposit;
149 75 : tl->serial_id = serial_id;
150 75 : tl->coin_history_id = chc->chid;
151 75 : chc->head = tl;
152 : }
153 : }
154 :
155 :
156 : /**
157 : * Function to be called with the results of a SELECT statement
158 : * that has returned @a num_results results.
159 : *
160 : * @param cls closure of type `struct CoinHistoryContext`
161 : * @param result the postgres result
162 : * @param num_results the number of results in @a result
163 : */
164 : static void
165 9 : add_coin_purse_deposit (void *cls,
166 : PGresult *result,
167 : unsigned int num_results)
168 : {
169 9 : struct CoinHistoryContext *chc = cls;
170 9 : struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
171 :
172 18 : for (unsigned int i = 0; i < num_results; i++)
173 : {
174 : struct TALER_EXCHANGEDB_PurseDepositListEntry *deposit;
175 : struct TALER_EXCHANGEDB_TransactionList *tl;
176 : uint64_t serial_id;
177 :
178 9 : deposit = GNUNET_new (struct TALER_EXCHANGEDB_PurseDepositListEntry);
179 : {
180 : bool not_finished;
181 9 : struct GNUNET_PQ_ResultSpec rs[] = {
182 9 : TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
183 : &deposit->amount),
184 9 : TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
185 : &deposit->deposit_fee),
186 9 : GNUNET_PQ_result_spec_auto_from_type ("purse_pub",
187 : &deposit->purse_pub),
188 9 : GNUNET_PQ_result_spec_uint64 ("purse_deposit_serial_id",
189 : &serial_id),
190 9 : GNUNET_PQ_result_spec_allow_null (
191 : GNUNET_PQ_result_spec_string ("partner_base_url",
192 : &deposit->exchange_base_url),
193 : NULL),
194 9 : GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
195 : &deposit->coin_sig),
196 9 : GNUNET_PQ_result_spec_allow_null (
197 9 : GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
198 : &deposit->h_age_commitment),
199 : &deposit->no_age_commitment),
200 9 : GNUNET_PQ_result_spec_allow_null (
201 : GNUNET_PQ_result_spec_bool ("refunded",
202 : &deposit->refunded),
203 : ¬_finished),
204 9 : GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
205 : &deposit->h_denom_pub),
206 : GNUNET_PQ_result_spec_end
207 : };
208 :
209 9 : if (GNUNET_OK !=
210 9 : GNUNET_PQ_extract_result (result,
211 : rs,
212 : i))
213 : {
214 0 : GNUNET_break (0);
215 0 : GNUNET_free (deposit);
216 0 : chc->failed = true;
217 0 : return;
218 : }
219 9 : if (not_finished)
220 7 : deposit->refunded = false;
221 : /* double-check for all-zeros age commitment */
222 9 : if (! deposit->no_age_commitment)
223 : deposit->no_age_commitment
224 0 : = GNUNET_is_zero (&deposit->h_age_commitment);
225 : }
226 9 : tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
227 9 : tl->next = chc->head;
228 9 : tl->type = TALER_EXCHANGEDB_TT_PURSE_DEPOSIT;
229 9 : tl->details.purse_deposit = deposit;
230 9 : tl->serial_id = serial_id;
231 9 : tl->coin_history_id = chc->chid;
232 9 : chc->head = tl;
233 : }
234 : }
235 :
236 :
237 : /**
238 : * Function to be called with the results of a SELECT statement
239 : * that has returned @a num_results results.
240 : *
241 : * @param cls closure of type `struct CoinHistoryContext`
242 : * @param result the postgres result
243 : * @param num_results the number of results in @a result
244 : */
245 : static void
246 10 : add_coin_melt (void *cls,
247 : PGresult *result,
248 : unsigned int num_results)
249 : {
250 10 : struct CoinHistoryContext *chc = cls;
251 10 : struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
252 :
253 20 : for (unsigned int i = 0; i<num_results; i++)
254 : {
255 : struct TALER_EXCHANGEDB_MeltListEntry *melt;
256 : struct TALER_EXCHANGEDB_TransactionList *tl;
257 : uint64_t serial_id;
258 :
259 10 : melt = GNUNET_new (struct TALER_EXCHANGEDB_MeltListEntry);
260 : {
261 10 : struct TALER_DenominationHashP *denom_pub_hashes = NULL;
262 10 : struct GNUNET_PQ_ResultSpec rs[] = {
263 10 : GNUNET_PQ_result_spec_auto_from_type ("rc",
264 : &melt->rc),
265 : /* oldcoin_index not needed */
266 10 : GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
267 : &melt->h_denom_pub),
268 10 : GNUNET_PQ_result_spec_auto_from_type ("old_coin_sig",
269 : &melt->coin_sig),
270 10 : GNUNET_PQ_result_spec_auto_from_type ("refresh_seed",
271 : &melt->refresh_seed),
272 10 : TALER_PQ_result_spec_array_denom_hash (pg->conn,
273 : "denom_pub_hashes",
274 : &melt->num_coins,
275 : &denom_pub_hashes),
276 10 : GNUNET_PQ_result_spec_allow_null (
277 10 : GNUNET_PQ_result_spec_auto_from_type ("blinding_seed",
278 : &melt->blinding_seed),
279 : &melt->no_blinding_seed),
280 10 : TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
281 : &melt->amount_with_fee),
282 10 : TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh",
283 : &melt->melt_fee),
284 10 : GNUNET_PQ_result_spec_allow_null (
285 10 : GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
286 : &melt->h_age_commitment),
287 : &melt->no_age_commitment),
288 10 : GNUNET_PQ_result_spec_uint64 ("refresh_id",
289 : &serial_id),
290 : GNUNET_PQ_result_spec_end
291 : };
292 :
293 10 : if (GNUNET_OK !=
294 10 : GNUNET_PQ_extract_result (result,
295 : rs,
296 : i))
297 : {
298 0 : GNUNET_break (0);
299 0 : GNUNET_PQ_cleanup_result (rs);
300 0 : GNUNET_free (melt);
301 0 : chc->failed = true;
302 0 : return;
303 : }
304 10 : melt->denom_pub_hashes = denom_pub_hashes;
305 10 : denom_pub_hashes = NULL;
306 10 : GNUNET_PQ_cleanup_result (rs);
307 : }
308 10 : tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
309 10 : tl->next = chc->head;
310 10 : tl->type = TALER_EXCHANGEDB_TT_MELT;
311 10 : tl->details.melt = melt;
312 10 : tl->serial_id = serial_id;
313 10 : tl->coin_history_id = chc->chid;
314 10 : chc->head = tl;
315 : }
316 : }
317 :
318 :
319 : /**
320 : * Function to be called with the results of a SELECT statement
321 : * that has returned @a num_results results.
322 : *
323 : * @param cls closure of type `struct CoinHistoryContext`
324 : * @param result the postgres result
325 : * @param num_results the number of results in @a result
326 : */
327 : static void
328 13 : add_coin_refund (void *cls,
329 : PGresult *result,
330 : unsigned int num_results)
331 : {
332 13 : struct CoinHistoryContext *chc = cls;
333 13 : struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
334 :
335 26 : for (unsigned int i = 0; i<num_results; i++)
336 : {
337 : struct TALER_EXCHANGEDB_RefundListEntry *refund;
338 : struct TALER_EXCHANGEDB_TransactionList *tl;
339 : uint64_t serial_id;
340 :
341 13 : refund = GNUNET_new (struct TALER_EXCHANGEDB_RefundListEntry);
342 : {
343 13 : struct GNUNET_PQ_ResultSpec rs[] = {
344 13 : GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
345 : &refund->merchant_pub),
346 13 : GNUNET_PQ_result_spec_auto_from_type ("merchant_sig",
347 : &refund->merchant_sig),
348 13 : GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
349 : &refund->h_contract_terms),
350 13 : GNUNET_PQ_result_spec_uint64 ("rtransaction_id",
351 : &refund->rtransaction_id),
352 13 : TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
353 : &refund->refund_amount),
354 13 : TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
355 : &refund->refund_fee),
356 13 : GNUNET_PQ_result_spec_uint64 ("refund_serial_id",
357 : &serial_id),
358 : GNUNET_PQ_result_spec_end
359 : };
360 :
361 13 : if (GNUNET_OK !=
362 13 : GNUNET_PQ_extract_result (result,
363 : rs,
364 : i))
365 : {
366 0 : GNUNET_break (0);
367 0 : GNUNET_free (refund);
368 0 : chc->failed = true;
369 0 : return;
370 : }
371 : }
372 13 : tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
373 13 : tl->next = chc->head;
374 13 : tl->type = TALER_EXCHANGEDB_TT_REFUND;
375 13 : tl->details.refund = refund;
376 13 : tl->serial_id = serial_id;
377 13 : tl->coin_history_id = chc->chid;
378 13 : chc->head = tl;
379 : }
380 : }
381 :
382 :
383 : /**
384 : * Function to be called with the results of a SELECT statement
385 : * that has returned @a num_results results.
386 : *
387 : * @param cls closure of type `struct CoinHistoryContext`
388 : * @param result the postgres result
389 : * @param num_results the number of results in @a result
390 : */
391 : static void
392 0 : add_coin_purse_decision (void *cls,
393 : PGresult *result,
394 : unsigned int num_results)
395 : {
396 0 : struct CoinHistoryContext *chc = cls;
397 0 : struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
398 :
399 0 : for (unsigned int i = 0; i<num_results; i++)
400 : {
401 : struct TALER_EXCHANGEDB_PurseRefundListEntry *prefund;
402 : struct TALER_EXCHANGEDB_TransactionList *tl;
403 : uint64_t serial_id;
404 :
405 0 : prefund = GNUNET_new (struct TALER_EXCHANGEDB_PurseRefundListEntry);
406 : {
407 0 : struct GNUNET_PQ_ResultSpec rs[] = {
408 0 : GNUNET_PQ_result_spec_auto_from_type ("purse_pub",
409 : &prefund->purse_pub),
410 0 : TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
411 : &prefund->refund_amount),
412 0 : TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
413 : &prefund->refund_fee),
414 0 : GNUNET_PQ_result_spec_uint64 ("purse_decision_serial_id",
415 : &serial_id),
416 : GNUNET_PQ_result_spec_end
417 : };
418 :
419 0 : if (GNUNET_OK !=
420 0 : GNUNET_PQ_extract_result (result,
421 : rs,
422 : i))
423 : {
424 0 : GNUNET_break (0);
425 0 : GNUNET_free (prefund);
426 0 : chc->failed = true;
427 0 : return;
428 : }
429 : }
430 0 : tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
431 0 : tl->next = chc->head;
432 0 : tl->type = TALER_EXCHANGEDB_TT_PURSE_REFUND;
433 0 : tl->details.purse_refund = prefund;
434 0 : tl->serial_id = serial_id;
435 0 : tl->coin_history_id = chc->chid;
436 0 : chc->head = tl;
437 : }
438 : }
439 :
440 :
441 : /**
442 : * Function to be called with the results of a SELECT statement
443 : * that has returned @a num_results results.
444 : *
445 : * @param cls closure of type `struct CoinHistoryContext`
446 : * @param result the postgres result
447 : * @param num_results the number of results in @a result
448 : */
449 : static void
450 1 : add_old_coin_recoup (void *cls,
451 : PGresult *result,
452 : unsigned int num_results)
453 : {
454 1 : struct CoinHistoryContext *chc = cls;
455 1 : struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
456 :
457 2 : for (unsigned int i = 0; i<num_results; i++)
458 : {
459 : struct TALER_EXCHANGEDB_RecoupRefreshListEntry *recoup;
460 : struct TALER_EXCHANGEDB_TransactionList *tl;
461 : uint64_t serial_id;
462 :
463 1 : recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupRefreshListEntry);
464 : {
465 1 : struct GNUNET_PQ_ResultSpec rs[] = {
466 1 : GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
467 : &recoup->coin.coin_pub),
468 1 : GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
469 : &recoup->coin_sig),
470 1 : GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
471 : &recoup->coin_blind),
472 1 : TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
473 : &recoup->value),
474 1 : GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
475 : &recoup->timestamp),
476 1 : GNUNET_PQ_result_spec_uint32 ("coin_index",
477 : &recoup->coin_index),
478 1 : GNUNET_PQ_result_spec_auto_from_type ("rc",
479 : &recoup->rc),
480 1 : GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
481 : &recoup->coin.denom_pub_hash),
482 1 : TALER_PQ_result_spec_denom_sig ("denom_sig",
483 : &recoup->coin.denom_sig),
484 1 : GNUNET_PQ_result_spec_uint64 ("recoup_refresh_uuid",
485 : &serial_id),
486 : GNUNET_PQ_result_spec_end
487 : };
488 :
489 1 : if (GNUNET_OK !=
490 1 : GNUNET_PQ_extract_result (result,
491 : rs,
492 : i))
493 : {
494 0 : GNUNET_break (0);
495 0 : GNUNET_free (recoup);
496 0 : chc->failed = true;
497 0 : return;
498 : }
499 1 : recoup->old_coin_pub = *chc->coin_pub;
500 : }
501 1 : tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
502 1 : tl->next = chc->head;
503 1 : tl->type = TALER_EXCHANGEDB_TT_RECOUP_REFRESH_RECEIVER;
504 1 : tl->details.old_coin_recoup = recoup;
505 1 : tl->serial_id = serial_id;
506 1 : tl->coin_history_id = chc->chid;
507 1 : chc->head = tl;
508 : }
509 : }
510 :
511 :
512 : /**
513 : * Function to be called with the results of a SELECT statement
514 : * that has returned @a num_results results.
515 : *
516 : * @param cls closure of type `struct CoinHistoryContext`
517 : * @param result the postgres result
518 : * @param num_results the number of results in @a result
519 : */
520 : static void
521 11 : add_coin_recoup (void *cls,
522 : PGresult *result,
523 : unsigned int num_results)
524 : {
525 11 : struct CoinHistoryContext *chc = cls;
526 11 : struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
527 :
528 22 : for (unsigned int i = 0; i<num_results; i++)
529 : {
530 : struct TALER_EXCHANGEDB_RecoupListEntry *recoup;
531 : struct TALER_EXCHANGEDB_TransactionList *tl;
532 : uint64_t serial_id;
533 :
534 11 : recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupListEntry);
535 : {
536 11 : struct GNUNET_PQ_ResultSpec rs[] = {
537 11 : GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
538 : &recoup->reserve_pub),
539 11 : GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
540 : &recoup->coin_sig),
541 11 : GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
542 : &recoup->h_denom_pub),
543 11 : GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
544 : &recoup->coin_blind),
545 11 : TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
546 : &recoup->value),
547 11 : GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
548 : &recoup->timestamp),
549 11 : GNUNET_PQ_result_spec_uint32 ("coin_index",
550 : &recoup->coin_index),
551 11 : GNUNET_PQ_result_spec_auto_from_type ("planchets_h",
552 : &recoup->planchets_h),
553 11 : GNUNET_PQ_result_spec_uint64 ("recoup_uuid",
554 : &serial_id),
555 : GNUNET_PQ_result_spec_end
556 : };
557 :
558 11 : if (GNUNET_OK !=
559 11 : GNUNET_PQ_extract_result (result,
560 : rs,
561 : i))
562 : {
563 0 : GNUNET_break (0);
564 0 : GNUNET_free (recoup);
565 0 : chc->failed = true;
566 0 : return;
567 : }
568 : }
569 11 : tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
570 11 : tl->next = chc->head;
571 11 : tl->type = TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW;
572 11 : tl->details.recoup = recoup;
573 11 : tl->serial_id = serial_id;
574 11 : tl->coin_history_id = chc->chid;
575 11 : chc->head = tl;
576 : }
577 : }
578 :
579 :
580 : /**
581 : * Function to be called with the results of a SELECT statement
582 : * that has returned @a num_results results.
583 : *
584 : * @param cls closure of type `struct CoinHistoryContext`
585 : * @param result the postgres result
586 : * @param num_results the number of results in @a result
587 : */
588 : static void
589 1 : add_coin_recoup_refresh (void *cls,
590 : PGresult *result,
591 : unsigned int num_results)
592 : {
593 1 : struct CoinHistoryContext *chc = cls;
594 1 : struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
595 :
596 2 : for (unsigned int i = 0; i<num_results; i++)
597 : {
598 : struct TALER_EXCHANGEDB_RecoupRefreshListEntry *recoup;
599 : struct TALER_EXCHANGEDB_TransactionList *tl;
600 : uint64_t serial_id;
601 :
602 1 : recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupRefreshListEntry);
603 : {
604 1 : struct GNUNET_PQ_ResultSpec rs[] = {
605 1 : GNUNET_PQ_result_spec_auto_from_type ("old_coin_pub",
606 : &recoup->old_coin_pub),
607 1 : GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
608 : &recoup->coin_sig),
609 1 : GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
610 : &recoup->coin_blind),
611 1 : TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
612 : &recoup->value),
613 1 : GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
614 : &recoup->timestamp),
615 1 : GNUNET_PQ_result_spec_uint32 ("coin_index",
616 : &recoup->coin_index),
617 1 : GNUNET_PQ_result_spec_auto_from_type ("rc",
618 : &recoup->rc),
619 1 : GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
620 : &recoup->coin.denom_pub_hash),
621 1 : TALER_PQ_result_spec_denom_sig ("denom_sig",
622 : &recoup->coin.denom_sig),
623 1 : GNUNET_PQ_result_spec_uint64 ("recoup_refresh_uuid",
624 : &serial_id),
625 : GNUNET_PQ_result_spec_end
626 : };
627 :
628 1 : if (GNUNET_OK !=
629 1 : GNUNET_PQ_extract_result (result,
630 : rs,
631 : i))
632 : {
633 0 : GNUNET_break (0);
634 0 : GNUNET_free (recoup);
635 0 : chc->failed = true;
636 0 : return;
637 : }
638 1 : recoup->coin.coin_pub = *chc->coin_pub;
639 : }
640 1 : tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
641 1 : tl->next = chc->head;
642 1 : tl->type = TALER_EXCHANGEDB_TT_RECOUP_REFRESH;
643 1 : tl->details.recoup_refresh = recoup;
644 1 : tl->serial_id = serial_id;
645 1 : tl->coin_history_id = chc->chid;
646 1 : chc->head = tl;
647 : }
648 : }
649 :
650 :
651 : /**
652 : * Function to be called with the results of a SELECT statement
653 : * that has returned @a num_results results.
654 : *
655 : * @param cls closure of type `struct CoinHistoryContext`
656 : * @param result the postgres result
657 : * @param num_results the number of results in @a result
658 : */
659 : static void
660 6 : add_coin_reserve_open (void *cls,
661 : PGresult *result,
662 : unsigned int num_results)
663 : {
664 6 : struct CoinHistoryContext *chc = cls;
665 6 : struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
666 :
667 12 : for (unsigned int i = 0; i<num_results; i++)
668 : {
669 : struct TALER_EXCHANGEDB_ReserveOpenListEntry *role;
670 : struct TALER_EXCHANGEDB_TransactionList *tl;
671 : uint64_t serial_id;
672 :
673 6 : role = GNUNET_new (struct TALER_EXCHANGEDB_ReserveOpenListEntry);
674 : {
675 6 : struct GNUNET_PQ_ResultSpec rs[] = {
676 6 : GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
677 : &role->reserve_sig),
678 6 : GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
679 : &role->coin_sig),
680 6 : TALER_PQ_RESULT_SPEC_AMOUNT ("contribution",
681 : &role->coin_contribution),
682 6 : GNUNET_PQ_result_spec_allow_null (
683 6 : GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
684 : &role->h_age_commitment),
685 : &role->no_age_commitment),
686 6 : GNUNET_PQ_result_spec_uint64 ("reserve_open_deposit_uuid",
687 : &serial_id),
688 : GNUNET_PQ_result_spec_end
689 : };
690 :
691 6 : if (GNUNET_OK !=
692 6 : GNUNET_PQ_extract_result (result,
693 : rs,
694 : i))
695 : {
696 0 : GNUNET_break (0);
697 0 : GNUNET_free (role);
698 0 : chc->failed = true;
699 0 : return;
700 : }
701 : }
702 6 : tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
703 6 : tl->next = chc->head;
704 6 : tl->type = TALER_EXCHANGEDB_TT_RESERVE_OPEN;
705 6 : tl->details.reserve_open = role;
706 6 : tl->serial_id = serial_id;
707 6 : tl->coin_history_id = chc->chid;
708 6 : chc->head = tl;
709 : }
710 : }
711 :
712 :
713 : /**
714 : * Work we need to do.
715 : */
716 : struct Work
717 : {
718 : /**
719 : * Name of the table.
720 : */
721 : const char *table;
722 :
723 : /**
724 : * SQL prepared statement name.
725 : */
726 : const char *statement;
727 :
728 : /**
729 : * Function to call to handle the result(s).
730 : */
731 : GNUNET_PQ_PostgresResultHandler cb;
732 : };
733 :
734 :
735 : /**
736 : * We found a coin history entry. Lookup details
737 : * from the respective table and store in @a cls.
738 : *
739 : * @param[in,out] cls a `struct CoinHistoryContext`
740 : * @param result a coin history entry result set
741 : * @param num_results total number of results in @a results
742 : */
743 : static void
744 82 : handle_history_entry (void *cls,
745 : PGresult *result,
746 : unsigned int num_results)
747 : {
748 82 : struct CoinHistoryContext *chc = cls;
749 82 : struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
750 : static const struct Work work[] = {
751 : [TALER_EXCHANGEDB_TT_DEPOSIT] =
752 : { "coin_deposits",
753 : "get_coin_transactions_deposit_with_coin_pub",
754 : &add_coin_deposit },
755 : [TALER_EXCHANGEDB_TT_MELT] =
756 : { "refresh",
757 : "get_coin_transactions_refresh_by_coin",
758 : &add_coin_melt },
759 : [TALER_EXCHANGEDB_TT_PURSE_DEPOSIT] =
760 : { "purse_deposits",
761 : "get_coin_transactions_purse_deposit_by_coin_pub",
762 : &add_coin_purse_deposit },
763 : [TALER_EXCHANGEDB_TT_PURSE_REFUND] =
764 : { "purse_decision",
765 : "get_coin_transactions_purse_decision_by_coin_pub",
766 : &add_coin_purse_decision },
767 : [TALER_EXCHANGEDB_TT_REFUND] =
768 : { "refunds",
769 : "get_coin_transactions_refunds_by_coin",
770 : &add_coin_refund },
771 : [TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW] =
772 : { "recoup",
773 : "get_coin_transactions_recoup_by_coin",
774 : &add_coin_recoup },
775 : [TALER_EXCHANGEDB_TT_RECOUP_REFRESH] =
776 : { "recoup_refresh::NEW",
777 : "get_coin_transactions_recoup_by_refreshed_coin",
778 : &add_coin_recoup_refresh },
779 : [TALER_EXCHANGEDB_TT_RECOUP_REFRESH_RECEIVER] =
780 : { "recoup_refresh::OLD",
781 : "get_coin_transactions_recoup_by_old_coin",
782 : &add_old_coin_recoup },
783 : [TALER_EXCHANGEDB_TT_RESERVE_OPEN] =
784 : { "reserves_open_deposits",
785 : "get_coin_transactions_reserve_open_by_coin",
786 : &add_coin_reserve_open },
787 : { NULL, NULL, NULL }
788 : };
789 : char *table_name;
790 : uint64_t serial_id;
791 82 : struct GNUNET_PQ_ResultSpec rs[] = {
792 82 : GNUNET_PQ_result_spec_string ("table_name",
793 : &table_name),
794 82 : GNUNET_PQ_result_spec_uint64 ("serial_id",
795 : &serial_id),
796 82 : GNUNET_PQ_result_spec_uint64 ("coin_history_serial_id",
797 : &chc->chid),
798 : GNUNET_PQ_result_spec_end
799 : };
800 82 : struct GNUNET_PQ_QueryParam params[] = {
801 82 : GNUNET_PQ_query_param_auto_from_type (chc->coin_pub),
802 82 : GNUNET_PQ_query_param_uint64 (&serial_id),
803 : GNUNET_PQ_query_param_end
804 : };
805 :
806 208 : for (unsigned int i = 0; i<num_results; i++)
807 : {
808 : enum GNUNET_DB_QueryStatus qs;
809 126 : bool found = false;
810 :
811 126 : if (GNUNET_OK !=
812 126 : GNUNET_PQ_extract_result (result,
813 : rs,
814 : i))
815 : {
816 0 : GNUNET_break (0);
817 0 : chc->failed = true;
818 0 : return;
819 : }
820 :
821 126 : for (unsigned int s = 0;
822 316 : NULL != work[s].statement;
823 190 : s++)
824 : {
825 506 : if (0 != strcmp (table_name,
826 316 : work[s].table))
827 190 : continue;
828 126 : found = true;
829 126 : qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
830 126 : work[s].statement,
831 : params,
832 126 : work[s].cb,
833 : chc);
834 126 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
835 : "Coin %s had %d transactions at %llu in table %s\n",
836 : TALER_B2S (chc->coin_pub),
837 : (int) qs,
838 : (unsigned long long) serial_id,
839 : table_name);
840 126 : if (0 > qs)
841 0 : chc->failed = true;
842 126 : break;
843 : }
844 126 : if (! found)
845 : {
846 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
847 : "Coin history includes unsupported table `%s`\n",
848 : table_name);
849 0 : chc->failed = true;
850 : }
851 126 : GNUNET_PQ_cleanup_result (rs);
852 126 : if (chc->failed)
853 0 : break;
854 : }
855 : }
856 :
857 :
858 : enum GNUNET_DB_QueryStatus
859 87 : TALER_EXCHANGEDB_get_coin_transactions (
860 : struct TALER_EXCHANGEDB_PostgresContext *pg,
861 : bool begin_transaction,
862 : const struct TALER_CoinSpendPublicKeyP *coin_pub,
863 : uint64_t start_off,
864 : uint64_t etag_in,
865 : uint64_t *etag_out,
866 : struct TALER_Amount *balance,
867 : struct TALER_DenominationHashP *h_denom_pub,
868 : struct TALER_EXCHANGEDB_TransactionList **tlp)
869 : {
870 87 : struct GNUNET_PQ_QueryParam params[] = {
871 87 : GNUNET_PQ_query_param_auto_from_type (coin_pub),
872 : GNUNET_PQ_query_param_end
873 : };
874 87 : struct GNUNET_PQ_QueryParam lparams[] = {
875 87 : GNUNET_PQ_query_param_auto_from_type (coin_pub),
876 87 : GNUNET_PQ_query_param_uint64 (&start_off),
877 : GNUNET_PQ_query_param_end
878 : };
879 87 : struct CoinHistoryContext chc = {
880 : .head = NULL,
881 : .coin_pub = coin_pub,
882 : .pg = pg
883 : };
884 :
885 87 : *tlp = NULL;
886 87 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
887 : "Getting transactions for coin %s\n",
888 : TALER_B2S (coin_pub));
889 87 : PREPARE (pg,
890 : "get_coin_transactions_history_etag_balance",
891 : "SELECT"
892 : " ch.coin_history_serial_id"
893 : ",kc.remaining"
894 : ",denom.denom_pub_hash"
895 : " FROM coin_history ch"
896 : " JOIN known_coins kc"
897 : " USING (coin_pub)"
898 : " JOIN denominations denom"
899 : " USING (denominations_serial)"
900 : " WHERE coin_pub=$1"
901 : " ORDER BY coin_history_serial_id DESC"
902 : " LIMIT 1;");
903 87 : PREPARE (pg,
904 : "get_coin_transactions_history",
905 : "SELECT"
906 : " table_name"
907 : ",serial_id"
908 : ",coin_history_serial_id"
909 : " FROM coin_history"
910 : " WHERE coin_pub=$1"
911 : " AND coin_history_serial_id > $2"
912 : " ORDER BY coin_history_serial_id DESC;");
913 87 : PREPARE (pg,
914 : "get_coin_transactions_deposit_with_coin_pub",
915 : "SELECT"
916 : " cdep.amount_with_fee"
917 : ",denoms.fee_deposit"
918 : ",denoms.denom_pub_hash"
919 : ",kc.age_commitment_hash"
920 : ",bdep.wallet_timestamp"
921 : ",bdep.refund_deadline"
922 : ",bdep.wire_deadline"
923 : ",bdep.merchant_pub"
924 : ",bdep.h_contract_terms"
925 : ",bdep.wallet_data_hash"
926 : ",bdep.wire_salt"
927 : ",wt.payto_uri"
928 : ",cdep.coin_sig"
929 : ",cdep.coin_deposit_serial_id"
930 : ",bdep.done"
931 : " FROM coin_deposits cdep"
932 : " JOIN batch_deposits bdep"
933 : " USING (batch_deposit_serial_id)"
934 : " JOIN wire_targets wt"
935 : " USING (wire_target_h_payto)"
936 : " JOIN known_coins kc"
937 : " ON (kc.coin_pub = cdep.coin_pub)"
938 : " JOIN denominations denoms"
939 : " USING (denominations_serial)"
940 : " WHERE cdep.coin_pub=$1"
941 : " AND cdep.coin_deposit_serial_id=$2;");
942 87 : PREPARE (pg,
943 : "get_coin_transactions_refresh_by_coin",
944 : "SELECT"
945 : " rc"
946 : ",refresh_seed"
947 : ",blinding_seed"
948 : ",old_coin_sig"
949 : ",amount_with_fee"
950 : ",denoms.denom_pub_hash"
951 : ",denoms.fee_refresh"
952 : ",kc.age_commitment_hash"
953 : ",refresh_id"
954 : ",ARRAY("
955 : " SELECT fresh_denoms.denom_pub_hash"
956 : " FROM unnest(refresh.denom_serials) WITH ORDINALITY"
957 : " AS fresh(denom_serial, coin_index)"
958 : " LEFT JOIN denominations fresh_denoms"
959 : " ON fresh_denoms.denominations_serial=fresh.denom_serial"
960 : " ORDER BY fresh.coin_index"
961 : ") AS denom_pub_hashes"
962 : " FROM refresh"
963 : " JOIN known_coins kc"
964 : " ON (refresh.old_coin_pub = kc.coin_pub)"
965 : " JOIN denominations denoms"
966 : " USING (denominations_serial)"
967 : " WHERE old_coin_pub=$1"
968 : " AND refresh_id=$2;");
969 87 : PREPARE (pg,
970 : "get_coin_transactions_purse_deposit_by_coin_pub",
971 : "SELECT"
972 : " partner_base_url"
973 : ",pd.amount_with_fee"
974 : ",denoms.fee_deposit"
975 : ",denoms.denom_pub_hash"
976 : ",pd.purse_pub"
977 : ",kc.age_commitment_hash"
978 : ",pd.coin_sig"
979 : ",pd.purse_deposit_serial_id"
980 : ",pdes.refunded"
981 : " FROM purse_deposits pd"
982 : " LEFT JOIN partners"
983 : " USING (partner_serial_id)"
984 : " JOIN purse_requests pr"
985 : " USING (purse_pub)"
986 : " LEFT JOIN purse_decision pdes"
987 : " USING (purse_pub)"
988 : " JOIN known_coins kc"
989 : " ON (pd.coin_pub = kc.coin_pub)"
990 : " JOIN denominations denoms"
991 : " USING (denominations_serial)"
992 : " WHERE pd.purse_deposit_serial_id=$2"
993 : " AND pd.coin_pub=$1;");
994 87 : PREPARE (pg,
995 : "get_coin_transactions_purse_decision_by_coin_pub",
996 : "SELECT"
997 : " pdes.purse_pub"
998 : ",pd.amount_with_fee"
999 : ",denom.fee_refund"
1000 : ",pdes.purse_decision_serial_id"
1001 : " FROM purse_decision pdes"
1002 : " JOIN purse_deposits pd"
1003 : " USING (purse_pub)"
1004 : " JOIN known_coins kc"
1005 : " ON (pd.coin_pub = kc.coin_pub)"
1006 : " JOIN denominations denom"
1007 : " USING (denominations_serial)"
1008 : " WHERE pd.coin_pub=$1"
1009 : " AND pdes.purse_decision_serial_id=$2"
1010 : " AND pdes.refunded;");
1011 87 : PREPARE (pg,
1012 : "get_coin_transactions_refunds_by_coin",
1013 : "SELECT"
1014 : " bdep.merchant_pub"
1015 : ",ref.merchant_sig"
1016 : ",bdep.h_contract_terms"
1017 : ",ref.rtransaction_id"
1018 : ",ref.amount_with_fee"
1019 : ",denom.fee_refund"
1020 : ",ref.refund_serial_id"
1021 : " FROM refunds ref"
1022 : " JOIN coin_deposits cdep"
1023 : " ON (ref.coin_pub = cdep.coin_pub AND ref.batch_deposit_serial_id = cdep.batch_deposit_serial_id)"
1024 : " JOIN batch_deposits bdep"
1025 : " ON (ref.batch_deposit_serial_id = bdep.batch_deposit_serial_id)"
1026 : " JOIN known_coins kc"
1027 : " ON (ref.coin_pub = kc.coin_pub)"
1028 : " JOIN denominations denom"
1029 : " USING (denominations_serial)"
1030 : " WHERE ref.coin_pub=$1"
1031 : " AND ref.refund_serial_id=$2;");
1032 87 : PREPARE (pg,
1033 : "get_coin_transactions_recoup_by_old_coin",
1034 : "SELECT"
1035 : " coins.coin_pub"
1036 : ",rr.coin_sig"
1037 : ",rr.coin_blind"
1038 : ",rr.amount"
1039 : ",rr.recoup_timestamp"
1040 : ",rr.coin_index"
1041 : ",rfc.rc"
1042 : ",denoms.denom_pub_hash"
1043 : ",coins.denom_sig"
1044 : ",rr.recoup_refresh_uuid"
1045 : " FROM recoup_refresh rr"
1046 : " JOIN refresh rfc"
1047 : " ON (rfc.refresh_id = rr.refresh_id)"
1048 : " JOIN known_coins coins"
1049 : " ON (coins.coin_pub = rr.coin_pub)"
1050 : " JOIN denominations denoms"
1051 : " ON (denoms.denominations_serial = coins.denominations_serial)"
1052 : " WHERE rr.recoup_refresh_uuid=$2"
1053 : " AND rfc.old_coin_pub=$1;");
1054 87 : PREPARE (pg,
1055 : "get_coin_transactions_recoup_by_coin",
1056 : "SELECT"
1057 : " res.reserve_pub"
1058 : ",denoms.denom_pub_hash"
1059 : ",rcp.coin_sig"
1060 : ",rcp.coin_blind"
1061 : ",rcp.amount"
1062 : ",rcp.recoup_timestamp"
1063 : ",rcp.coin_index"
1064 : ",ro.planchets_h"
1065 : ",rcp.recoup_uuid"
1066 : " FROM recoup rcp"
1067 : " JOIN withdraw ro"
1068 : " USING (withdraw_id)"
1069 : " JOIN reserves res"
1070 : " USING (reserve_pub)"
1071 : " JOIN known_coins coins"
1072 : " USING (coin_pub)"
1073 : " JOIN denominations denoms"
1074 : " ON (denoms.denominations_serial = coins.denominations_serial)"
1075 : " WHERE rcp.recoup_uuid=$2"
1076 : " AND coins.coin_pub=$1;");
1077 : /* Used to obtain recoup transactions
1078 : for a refreshed coin */
1079 87 : PREPARE (pg,
1080 : "get_coin_transactions_recoup_by_refreshed_coin",
1081 : "SELECT"
1082 : " old_coins.coin_pub AS old_coin_pub"
1083 : ",rr.coin_sig"
1084 : ",rr.coin_blind"
1085 : ",rr.amount"
1086 : ",rr.recoup_timestamp"
1087 : ",rr.coin_index"
1088 : ",rfc.rc"
1089 : ",denoms.denom_pub_hash"
1090 : ",coins.denom_sig"
1091 : ",recoup_refresh_uuid"
1092 : " FROM recoup_refresh rr"
1093 : " JOIN refresh rfc"
1094 : " ON (rr.refresh_id = rfc.refresh_id)"
1095 : " JOIN known_coins old_coins"
1096 : " ON (rfc.old_coin_pub = old_coins.coin_pub)"
1097 : " JOIN known_coins coins"
1098 : " ON (rr.coin_pub = coins.coin_pub)"
1099 : " JOIN denominations denoms"
1100 : " ON (denoms.denominations_serial = coins.denominations_serial)"
1101 : " WHERE rr.recoup_refresh_uuid=$2"
1102 : " AND coins.coin_pub=$1;");
1103 87 : PREPARE (pg,
1104 : "get_coin_transactions_reserve_open_by_coin",
1105 : "SELECT"
1106 : " rod.reserve_open_deposit_uuid"
1107 : ",rod.coin_sig"
1108 : ",rod.reserve_sig"
1109 : ",rod.contribution"
1110 : ",kc.age_commitment_hash"
1111 : " FROM reserves_open_deposits rod"
1112 : " JOIN known_coins kc"
1113 : " ON (rod.coin_pub = kc.coin_pub)"
1114 : " WHERE rod.coin_pub=$1"
1115 : " AND rod.reserve_open_deposit_uuid=$2;");
1116 87 : for (unsigned int i = 0; i<RETRIES; i++)
1117 : {
1118 : enum GNUNET_DB_QueryStatus qs;
1119 : uint64_t end;
1120 87 : struct GNUNET_PQ_ResultSpec rs[] = {
1121 87 : GNUNET_PQ_result_spec_uint64 ("coin_history_serial_id",
1122 : &end),
1123 87 : GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
1124 : h_denom_pub),
1125 87 : TALER_PQ_RESULT_SPEC_AMOUNT ("remaining",
1126 : balance),
1127 : GNUNET_PQ_result_spec_end
1128 : };
1129 :
1130 87 : if (begin_transaction)
1131 : {
1132 24 : if (GNUNET_OK !=
1133 24 : TALER_EXCHANGEDB_start_read_committed (pg,
1134 : "get-coin-transactions"))
1135 : {
1136 0 : GNUNET_break (0);
1137 87 : return GNUNET_DB_STATUS_HARD_ERROR;
1138 : }
1139 : }
1140 : /* First only check the last item, to see if
1141 : we even need to iterate */
1142 87 : qs = GNUNET_PQ_eval_prepared_singleton_select (
1143 : pg->conn,
1144 : "get_coin_transactions_history_etag_balance",
1145 : params,
1146 : rs);
1147 87 : switch (qs)
1148 : {
1149 0 : case GNUNET_DB_STATUS_HARD_ERROR:
1150 0 : if (begin_transaction)
1151 0 : TALER_EXCHANGEDB_rollback (pg);
1152 0 : return qs;
1153 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
1154 0 : if (begin_transaction)
1155 0 : TALER_EXCHANGEDB_rollback (pg);
1156 0 : continue;
1157 4 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
1158 4 : if (begin_transaction)
1159 4 : TALER_EXCHANGEDB_rollback (pg);
1160 4 : return qs;
1161 83 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
1162 83 : *etag_out = end;
1163 83 : if (end == etag_in)
1164 : {
1165 : /* client is up-to-date, nothing more to do */
1166 1 : if (begin_transaction)
1167 1 : TALER_EXCHANGEDB_rollback (pg);
1168 1 : return qs;
1169 : }
1170 : }
1171 : /* We indeed need to iterate over the history */
1172 82 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1173 : "Current ETag for coin %s is %llu\n",
1174 : TALER_B2S (coin_pub),
1175 : (unsigned long long) end);
1176 :
1177 82 : qs = GNUNET_PQ_eval_prepared_multi_select (
1178 : pg->conn,
1179 : "get_coin_transactions_history",
1180 : lparams,
1181 : &handle_history_entry,
1182 : &chc);
1183 82 : switch (qs)
1184 : {
1185 0 : case GNUNET_DB_STATUS_HARD_ERROR:
1186 0 : if (begin_transaction)
1187 0 : TALER_EXCHANGEDB_rollback (pg);
1188 0 : return qs;
1189 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
1190 0 : if (begin_transaction)
1191 0 : TALER_EXCHANGEDB_rollback (pg);
1192 0 : TALER_EXCHANGEDB_free_coin_transaction_list (chc.head);
1193 0 : chc.head = NULL;
1194 0 : continue;
1195 82 : default:
1196 82 : break;
1197 : }
1198 82 : if (chc.failed)
1199 : {
1200 0 : if (begin_transaction)
1201 0 : TALER_EXCHANGEDB_rollback (pg);
1202 0 : TALER_EXCHANGEDB_free_coin_transaction_list (chc.head);
1203 0 : return GNUNET_DB_STATUS_SOFT_ERROR;
1204 : }
1205 82 : if (! begin_transaction)
1206 : {
1207 63 : *tlp = chc.head;
1208 63 : return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
1209 : }
1210 19 : qs = TALER_EXCHANGEDB_commit (pg);
1211 19 : switch (qs)
1212 : {
1213 0 : case GNUNET_DB_STATUS_HARD_ERROR:
1214 0 : TALER_EXCHANGEDB_free_coin_transaction_list (chc.head);
1215 0 : chc.head = NULL;
1216 0 : return qs;
1217 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
1218 0 : TALER_EXCHANGEDB_free_coin_transaction_list (chc.head);
1219 0 : chc.head = NULL;
1220 0 : continue;
1221 19 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
1222 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
1223 19 : *tlp = chc.head;
1224 19 : return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
1225 : }
1226 : }
1227 0 : return GNUNET_DB_STATUS_SOFT_ERROR;
1228 : }
|