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_reserve_history.c
18 : * @brief Obtain (parts of) the history of a reserve.
19 : * @author Christian Grothoff
20 : */
21 : #include "taler/taler_error_codes.h"
22 : #include "taler/taler_pq_lib.h"
23 : #include "exchange-database/get_reserve_history.h"
24 : #include "exchange-database/start_read_committed.h"
25 : #include "exchange-database/commit.h"
26 : #include "exchange-database/rollback.h"
27 : #include "helper.h"
28 :
29 : /**
30 : * How often do we re-try when encountering DB serialization issues?
31 : * (We are read-only, so can only happen due to concurrent insert,
32 : * which should be very rare.)
33 : */
34 : #define RETRIES 3
35 :
36 :
37 : /**
38 : * Closure for callbacks invoked via #TALER_EXCHANGEDB_get_reserve_history().
39 : */
40 : struct ReserveHistoryContext
41 : {
42 :
43 : /**
44 : * Which reserve are we building the history for?
45 : */
46 : const struct TALER_ReservePublicKeyP *reserve_pub;
47 :
48 : /**
49 : * Where we build the history.
50 : */
51 : struct TALER_EXCHANGEDB_ReserveHistory *rh;
52 :
53 : /**
54 : * Tail of @e rh list.
55 : */
56 : struct TALER_EXCHANGEDB_ReserveHistory *rh_tail;
57 :
58 : /**
59 : * Plugin context.
60 : */
61 : struct TALER_EXCHANGEDB_PostgresContext *pg;
62 :
63 : /**
64 : * Current reserve_history_serial_id being processed,
65 : * set before each sub-table callback.
66 : */
67 : uint64_t current_history_offset;
68 :
69 : /**
70 : * Set to true on serious internal errors during
71 : * the callbacks.
72 : */
73 : bool failed;
74 : };
75 :
76 :
77 : /**
78 : * Append and return a fresh element to the reserve
79 : * history kept in @a rhc.
80 : *
81 : * @param rhc where the history is kept
82 : * @return the fresh element that was added
83 : */
84 : static struct TALER_EXCHANGEDB_ReserveHistory *
85 43 : append_rh (struct ReserveHistoryContext *rhc)
86 : {
87 : struct TALER_EXCHANGEDB_ReserveHistory *tail;
88 :
89 43 : tail = GNUNET_new (struct TALER_EXCHANGEDB_ReserveHistory);
90 43 : tail->history_offset = rhc->current_history_offset;
91 43 : if (NULL != rhc->rh_tail)
92 : {
93 26 : rhc->rh_tail->next = tail;
94 26 : rhc->rh_tail = tail;
95 : }
96 : else
97 : {
98 17 : rhc->rh_tail = tail;
99 17 : rhc->rh = tail;
100 : }
101 43 : return tail;
102 : }
103 :
104 :
105 : /**
106 : * Add bank transfers to result set for #TALER_EXCHANGEDB_get_reserve_history.
107 : *
108 : * @param cls a `struct ReserveHistoryContext *`
109 : * @param result SQL result
110 : * @param num_results number of rows in @a result
111 : */
112 : static void
113 16 : add_bank_to_exchange (void *cls,
114 : PGresult *result,
115 : unsigned int num_results)
116 : {
117 16 : struct ReserveHistoryContext *rhc = cls;
118 16 : struct TALER_EXCHANGEDB_PostgresContext *pg = rhc->pg;
119 :
120 32 : while (0 < num_results)
121 : {
122 : struct TALER_EXCHANGEDB_BankTransfer *bt;
123 : struct TALER_EXCHANGEDB_ReserveHistory *tail;
124 :
125 16 : bt = GNUNET_new (struct TALER_EXCHANGEDB_BankTransfer);
126 : {
127 16 : struct GNUNET_PQ_ResultSpec rs[] = {
128 16 : GNUNET_PQ_result_spec_uint64 ("wire_reference",
129 : &bt->wire_reference),
130 16 : TALER_PQ_RESULT_SPEC_AMOUNT ("credit",
131 : &bt->amount),
132 16 : GNUNET_PQ_result_spec_timestamp ("execution_date",
133 : &bt->execution_date),
134 16 : GNUNET_PQ_result_spec_string ("sender_account_details",
135 : &bt->sender_account_details.full_payto),
136 : GNUNET_PQ_result_spec_end
137 : };
138 :
139 16 : if (GNUNET_OK !=
140 16 : GNUNET_PQ_extract_result (result,
141 : rs,
142 : --num_results))
143 : {
144 0 : GNUNET_break (0);
145 0 : GNUNET_free (bt);
146 0 : rhc->failed = true;
147 0 : return;
148 : }
149 : }
150 16 : bt->reserve_pub = *rhc->reserve_pub;
151 16 : tail = append_rh (rhc);
152 16 : tail->type = TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE;
153 16 : tail->details.bank = bt;
154 : } /* end of 'while (0 < rows)' */
155 : }
156 :
157 :
158 : /**
159 : * Add coin withdrawals to result set for #TALER_EXCHANGEDB_get_reserve_history.
160 : *
161 : * @param cls a `struct ReserveHistoryContext *`
162 : * @param result SQL result
163 : * @param num_results number of rows in @a result
164 : */
165 : static void
166 11 : add_withdraw (void *cls,
167 : PGresult *result,
168 : unsigned int num_results)
169 : {
170 11 : struct ReserveHistoryContext *rhc = cls;
171 11 : struct TALER_EXCHANGEDB_PostgresContext *pg = rhc->pg;
172 :
173 22 : while (0 < num_results)
174 : {
175 : struct TALER_EXCHANGEDB_Withdraw *wd;
176 : struct TALER_EXCHANGEDB_ReserveHistory *tail;
177 :
178 11 : wd = GNUNET_new (struct TALER_EXCHANGEDB_Withdraw);
179 : {
180 : bool no_noreveal_index;
181 : bool no_max_age;
182 : bool no_selected_h;
183 : size_t num_denom_hs;
184 : size_t num_denom_serials;
185 11 : uint64_t *my_denom_serials = NULL;
186 11 : struct TALER_DenominationHashP *my_denom_pub_hashes = NULL;
187 11 : struct GNUNET_PQ_ResultSpec rs[] = {
188 11 : GNUNET_PQ_result_spec_auto_from_type ("planchets_h",
189 : &wd->planchets_h),
190 11 : GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
191 : &wd->reserve_sig),
192 11 : TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
193 : &wd->amount_with_fee),
194 11 : GNUNET_PQ_result_spec_allow_null (
195 : GNUNET_PQ_result_spec_uint16 ("max_age",
196 : &wd->max_age),
197 : &no_max_age),
198 11 : GNUNET_PQ_result_spec_allow_null (
199 : GNUNET_PQ_result_spec_uint16 ("noreveal_index",
200 : &wd->noreveal_index),
201 : &no_noreveal_index),
202 11 : GNUNET_PQ_result_spec_allow_null (
203 11 : GNUNET_PQ_result_spec_auto_from_type ("blinding_seed",
204 : &wd->blinding_seed),
205 : &wd->no_blinding_seed),
206 11 : GNUNET_PQ_result_spec_allow_null (
207 11 : GNUNET_PQ_result_spec_auto_from_type ("selected_h",
208 : &wd->selected_h),
209 : &no_selected_h),
210 11 : TALER_PQ_result_spec_array_denom_hash (pg->conn,
211 : "denom_pub_hashes",
212 : &num_denom_hs,
213 : &my_denom_pub_hashes),
214 11 : GNUNET_PQ_result_spec_array_uint64 (pg->conn,
215 : "denom_serials",
216 : &num_denom_serials,
217 : &my_denom_serials),
218 : GNUNET_PQ_result_spec_end
219 : };
220 :
221 11 : if (GNUNET_OK !=
222 11 : GNUNET_PQ_extract_result (result,
223 : rs,
224 : --num_results))
225 : {
226 0 : GNUNET_break (0);
227 0 : GNUNET_free (wd);
228 0 : rhc->failed = true;
229 0 : GNUNET_PQ_cleanup_result (rs);
230 0 : return;
231 : }
232 :
233 11 : if (num_denom_hs != num_denom_serials)
234 : {
235 0 : GNUNET_break (0);
236 0 : GNUNET_free (wd);
237 0 : rhc->failed = true;
238 0 : GNUNET_PQ_cleanup_result (rs);
239 0 : return;
240 : }
241 :
242 11 : if ((no_noreveal_index != no_max_age) ||
243 11 : (no_noreveal_index != no_selected_h))
244 : {
245 0 : GNUNET_break (0);
246 0 : GNUNET_free (wd);
247 0 : rhc->failed = true;
248 0 : GNUNET_PQ_cleanup_result (rs);
249 0 : return;
250 : }
251 11 : wd->age_proof_required = ! no_max_age;
252 11 : wd->num_coins = num_denom_serials;
253 11 : wd->reserve_pub = *rhc->reserve_pub;
254 11 : wd->denom_serials = my_denom_serials;
255 11 : wd->denom_pub_hashes = my_denom_pub_hashes;
256 : /* prevent cleanup from destroying our actual result */
257 11 : my_denom_serials = NULL;
258 11 : my_denom_pub_hashes = NULL;
259 11 : GNUNET_PQ_cleanup_result (rs);
260 : }
261 :
262 11 : tail = append_rh (rhc);
263 11 : tail->type = TALER_EXCHANGEDB_RO_WITHDRAW_COINS;
264 11 : tail->details.withdraw = wd;
265 : }
266 : }
267 :
268 :
269 : /**
270 : * Add recoups to result set for #TALER_EXCHANGEDB_get_reserve_history.
271 : *
272 : * @param cls a `struct ReserveHistoryContext *`
273 : * @param result SQL result
274 : * @param num_results number of rows in @a result
275 : */
276 : static void
277 5 : add_recoup (void *cls,
278 : PGresult *result,
279 : unsigned int num_results)
280 : {
281 5 : struct ReserveHistoryContext *rhc = cls;
282 5 : struct TALER_EXCHANGEDB_PostgresContext *pg = rhc->pg;
283 :
284 10 : while (0 < num_results)
285 : {
286 : struct TALER_EXCHANGEDB_Recoup *recoup;
287 : struct TALER_EXCHANGEDB_ReserveHistory *tail;
288 :
289 5 : recoup = GNUNET_new (struct TALER_EXCHANGEDB_Recoup);
290 : {
291 5 : struct GNUNET_PQ_ResultSpec rs[] = {
292 5 : TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
293 : &recoup->value),
294 5 : GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
295 : &recoup->coin.coin_pub),
296 5 : GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
297 : &recoup->coin_blind),
298 5 : GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
299 : &recoup->coin_sig),
300 5 : GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
301 : &recoup->timestamp),
302 5 : GNUNET_PQ_result_spec_auto_from_type ("planchets_h",
303 : &recoup->planchets_h),
304 5 : GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
305 : &recoup->coin.denom_pub_hash),
306 5 : TALER_PQ_result_spec_denom_sig (
307 : "denom_sig",
308 : &recoup->coin.denom_sig),
309 : GNUNET_PQ_result_spec_end
310 : };
311 :
312 5 : if (GNUNET_OK !=
313 5 : GNUNET_PQ_extract_result (result,
314 : rs,
315 : --num_results))
316 : {
317 0 : GNUNET_break (0);
318 0 : GNUNET_free (recoup);
319 0 : rhc->failed = true;
320 0 : return;
321 : }
322 : }
323 5 : recoup->reserve_pub = *rhc->reserve_pub;
324 5 : tail = append_rh (rhc);
325 5 : tail->type = TALER_EXCHANGEDB_RO_RECOUP_COIN;
326 5 : tail->details.recoup = recoup;
327 : } /* end of 'while (0 < rows)' */
328 : }
329 :
330 :
331 : /**
332 : * Add exchange-to-bank transfers to result set for
333 : * #TALER_EXCHANGEDB_get_reserve_history.
334 : *
335 : * @param cls a `struct ReserveHistoryContext *`
336 : * @param result SQL result
337 : * @param num_results number of rows in @a result
338 : */
339 : static void
340 0 : add_exchange_to_bank (void *cls,
341 : PGresult *result,
342 : unsigned int num_results)
343 : {
344 0 : struct ReserveHistoryContext *rhc = cls;
345 0 : struct TALER_EXCHANGEDB_PostgresContext *pg = rhc->pg;
346 :
347 0 : while (0 < num_results)
348 : {
349 : struct TALER_EXCHANGEDB_ClosingTransfer *closing;
350 : struct TALER_EXCHANGEDB_ReserveHistory *tail;
351 :
352 0 : closing = GNUNET_new (struct TALER_EXCHANGEDB_ClosingTransfer);
353 : {
354 0 : struct GNUNET_PQ_ResultSpec rs[] = {
355 0 : TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
356 : &closing->amount),
357 0 : TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee",
358 : &closing->closing_fee),
359 0 : GNUNET_PQ_result_spec_timestamp ("execution_date",
360 : &closing->execution_date),
361 0 : GNUNET_PQ_result_spec_string ("receiver_account",
362 : &closing->receiver_account_details.
363 : full_payto),
364 0 : GNUNET_PQ_result_spec_auto_from_type ("wtid",
365 : &closing->wtid),
366 : GNUNET_PQ_result_spec_end
367 : };
368 :
369 0 : if (GNUNET_OK !=
370 0 : GNUNET_PQ_extract_result (result,
371 : rs,
372 : --num_results))
373 : {
374 0 : GNUNET_break (0);
375 0 : GNUNET_free (closing);
376 0 : rhc->failed = true;
377 0 : return;
378 : }
379 : }
380 0 : closing->reserve_pub = *rhc->reserve_pub;
381 0 : tail = append_rh (rhc);
382 0 : tail->type = TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK;
383 0 : tail->details.closing = closing;
384 : } /* end of 'while (0 < rows)' */
385 : }
386 :
387 :
388 : /**
389 : * Add purse merge transfers to result set for
390 : * #TALER_EXCHANGEDB_get_reserve_history.
391 : *
392 : * @param cls a `struct ReserveHistoryContext *`
393 : * @param result SQL result
394 : * @param num_results number of rows in @a result
395 : */
396 : static void
397 8 : add_p2p_merge (void *cls,
398 : PGresult *result,
399 : unsigned int num_results)
400 : {
401 8 : struct ReserveHistoryContext *rhc = cls;
402 8 : struct TALER_EXCHANGEDB_PostgresContext *pg = rhc->pg;
403 :
404 16 : while (0 < num_results)
405 : {
406 : struct TALER_EXCHANGEDB_PurseMerge *merge;
407 : struct TALER_EXCHANGEDB_ReserveHistory *tail;
408 :
409 8 : merge = GNUNET_new (struct TALER_EXCHANGEDB_PurseMerge);
410 : {
411 : uint32_t flags32;
412 : struct TALER_Amount balance;
413 8 : struct GNUNET_PQ_ResultSpec rs[] = {
414 8 : TALER_PQ_RESULT_SPEC_AMOUNT ("purse_fee",
415 : &merge->purse_fee),
416 8 : TALER_PQ_RESULT_SPEC_AMOUNT ("balance",
417 : &balance),
418 8 : TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
419 : &merge->amount_with_fee),
420 8 : GNUNET_PQ_result_spec_timestamp ("merge_timestamp",
421 : &merge->merge_timestamp),
422 8 : GNUNET_PQ_result_spec_timestamp ("purse_expiration",
423 : &merge->purse_expiration),
424 8 : GNUNET_PQ_result_spec_uint32 ("age_limit",
425 : &merge->min_age),
426 8 : GNUNET_PQ_result_spec_uint32 ("flags",
427 : &flags32),
428 8 : GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
429 : &merge->h_contract_terms),
430 8 : GNUNET_PQ_result_spec_auto_from_type ("merge_pub",
431 : &merge->merge_pub),
432 8 : GNUNET_PQ_result_spec_auto_from_type ("purse_pub",
433 : &merge->purse_pub),
434 8 : GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
435 : &merge->reserve_sig),
436 : GNUNET_PQ_result_spec_end
437 : };
438 :
439 8 : if (GNUNET_OK !=
440 8 : GNUNET_PQ_extract_result (result,
441 : rs,
442 : --num_results))
443 : {
444 0 : GNUNET_break (0);
445 0 : GNUNET_free (merge);
446 0 : rhc->failed = true;
447 0 : return;
448 : }
449 8 : merge->flags = (enum TALER_WalletAccountMergeFlags) flags32;
450 8 : if ( (! GNUNET_TIME_absolute_is_future (
451 8 : merge->merge_timestamp.abs_time)) &&
452 8 : (-1 != TALER_amount_cmp (&balance,
453 8 : &merge->amount_with_fee)) )
454 8 : merge->merged = true;
455 : }
456 8 : merge->reserve_pub = *rhc->reserve_pub;
457 8 : tail = append_rh (rhc);
458 8 : tail->type = TALER_EXCHANGEDB_RO_PURSE_MERGE;
459 8 : tail->details.merge = merge;
460 : }
461 : }
462 :
463 :
464 : /**
465 : * Add paid for history requests to result set for
466 : * #TALER_EXCHANGEDB_get_reserve_history.
467 : *
468 : * @param cls a `struct ReserveHistoryContext *`
469 : * @param result SQL result
470 : * @param num_results number of rows in @a result
471 : */
472 : static void
473 0 : add_open_requests (void *cls,
474 : PGresult *result,
475 : unsigned int num_results)
476 : {
477 0 : struct ReserveHistoryContext *rhc = cls;
478 0 : struct TALER_EXCHANGEDB_PostgresContext *pg = rhc->pg;
479 :
480 0 : while (0 < num_results)
481 : {
482 : struct TALER_EXCHANGEDB_OpenRequest *orq;
483 : struct TALER_EXCHANGEDB_ReserveHistory *tail;
484 :
485 0 : orq = GNUNET_new (struct TALER_EXCHANGEDB_OpenRequest);
486 : {
487 0 : struct GNUNET_PQ_ResultSpec rs[] = {
488 0 : TALER_PQ_RESULT_SPEC_AMOUNT ("open_fee",
489 : &orq->open_fee),
490 0 : GNUNET_PQ_result_spec_timestamp ("request_timestamp",
491 : &orq->request_timestamp),
492 0 : GNUNET_PQ_result_spec_timestamp ("expiration_date",
493 : &orq->reserve_expiration),
494 0 : GNUNET_PQ_result_spec_uint32 ("requested_purse_limit",
495 : &orq->purse_limit),
496 0 : GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
497 : &orq->reserve_sig),
498 : GNUNET_PQ_result_spec_end
499 : };
500 :
501 0 : if (GNUNET_OK !=
502 0 : GNUNET_PQ_extract_result (result,
503 : rs,
504 : --num_results))
505 : {
506 0 : GNUNET_break (0);
507 0 : GNUNET_free (orq);
508 0 : rhc->failed = true;
509 0 : return;
510 : }
511 : }
512 0 : orq->reserve_pub = *rhc->reserve_pub;
513 0 : tail = append_rh (rhc);
514 0 : tail->type = TALER_EXCHANGEDB_RO_OPEN_REQUEST;
515 0 : tail->details.open_request = orq;
516 : }
517 : }
518 :
519 :
520 : /**
521 : * Add paid for history requests to result set for
522 : * #TALER_EXCHANGEDB_get_reserve_history.
523 : *
524 : * @param cls a `struct ReserveHistoryContext *`
525 : * @param result SQL result
526 : * @param num_results number of rows in @a result
527 : */
528 : static void
529 3 : add_close_requests (void *cls,
530 : PGresult *result,
531 : unsigned int num_results)
532 : {
533 3 : struct ReserveHistoryContext *rhc = cls;
534 :
535 6 : while (0 < num_results)
536 : {
537 : struct TALER_EXCHANGEDB_CloseRequest *crq;
538 : struct TALER_EXCHANGEDB_ReserveHistory *tail;
539 :
540 3 : crq = GNUNET_new (struct TALER_EXCHANGEDB_CloseRequest);
541 : {
542 : struct TALER_FullPayto payto_uri;
543 3 : struct GNUNET_PQ_ResultSpec rs[] = {
544 3 : GNUNET_PQ_result_spec_timestamp ("close_timestamp",
545 : &crq->request_timestamp),
546 3 : GNUNET_PQ_result_spec_string ("payto_uri",
547 : &payto_uri.full_payto),
548 3 : GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
549 : &crq->reserve_sig),
550 : GNUNET_PQ_result_spec_end
551 : };
552 :
553 3 : if (GNUNET_OK !=
554 3 : GNUNET_PQ_extract_result (result,
555 : rs,
556 : --num_results))
557 : {
558 0 : GNUNET_break (0);
559 0 : GNUNET_free (crq);
560 0 : rhc->failed = true;
561 0 : return;
562 : }
563 3 : TALER_full_payto_hash (payto_uri,
564 : &crq->target_account_h_payto);
565 3 : GNUNET_free (payto_uri.full_payto);
566 : }
567 3 : crq->reserve_pub = *rhc->reserve_pub;
568 3 : tail = append_rh (rhc);
569 3 : tail->type = TALER_EXCHANGEDB_RO_CLOSE_REQUEST;
570 3 : tail->details.close_request = crq;
571 : }
572 : }
573 :
574 :
575 : /**
576 : * Add reserve history entries found.
577 : *
578 : * @param cls a `struct ReserveHistoryContext *`
579 : * @param result SQL result
580 : * @param num_results number of rows in @a result
581 : */
582 : static void
583 18 : handle_history_entry (void *cls,
584 : PGresult *result,
585 : unsigned int num_results)
586 : {
587 : static const struct
588 : {
589 : /**
590 : * Table with reserve history entry we are responsible for.
591 : */
592 : const char *table;
593 : /**
594 : * Name of the prepared statement to run.
595 : */
596 : const char *statement;
597 : /**
598 : * Function to use to process the results.
599 : */
600 : GNUNET_PQ_PostgresResultHandler cb;
601 : } work[] = {
602 : /** #TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE */
603 : { "reserves_in",
604 : "get_reserve_history_reserves_in_get_transactions",
605 : add_bank_to_exchange },
606 : /** #TALER_EXCHANGEDB_RO_WITHDRAW_COINS */
607 : { "withdraw",
608 : "get_reserve_history_withdraw_details",
609 : &add_withdraw },
610 : /** #TALER_EXCHANGEDB_RO_RECOUP_COIN */
611 : { "recoup",
612 : "get_reserve_history_recoup_by_reserve",
613 : &add_recoup },
614 : /** #TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK */
615 : { "reserves_close",
616 : "get_reserve_history_close_by_reserve",
617 : &add_exchange_to_bank },
618 : /** #TALER_EXCHANGEDB_RO_PURSE_MERGE */
619 : { "purse_decision",
620 : "get_reserve_history_merge_by_reserve",
621 : &add_p2p_merge },
622 : /** #TALER_EXCHANGEDB_RO_OPEN_REQUEST */
623 : { "reserves_open_requests",
624 : "get_reserve_history_open_request_by_reserve",
625 : &add_open_requests },
626 : /** #TALER_EXCHANGEDB_RO_CLOSE_REQUEST */
627 : { "close_requests",
628 : "get_reserve_history_close_request_by_reserve",
629 : &add_close_requests },
630 : /* List terminator */
631 : { NULL, NULL, NULL }
632 : };
633 18 : struct ReserveHistoryContext *rhc = cls;
634 : char *table_name;
635 : uint64_t serial_id;
636 18 : struct GNUNET_PQ_ResultSpec rs[] = {
637 18 : GNUNET_PQ_result_spec_string ("table_name",
638 : &table_name),
639 18 : GNUNET_PQ_result_spec_uint64 ("serial_id",
640 : &serial_id),
641 18 : GNUNET_PQ_result_spec_uint64 ("reserve_history_serial_id",
642 : &rhc->current_history_offset),
643 : GNUNET_PQ_result_spec_end
644 : };
645 18 : struct GNUNET_PQ_QueryParam params[] = {
646 18 : GNUNET_PQ_query_param_auto_from_type (rhc->reserve_pub),
647 18 : GNUNET_PQ_query_param_uint64 (&serial_id),
648 : GNUNET_PQ_query_param_end
649 : };
650 :
651 61 : while (0 < num_results--)
652 : {
653 : enum GNUNET_DB_QueryStatus qs;
654 43 : bool found = false;
655 :
656 43 : if (GNUNET_OK !=
657 43 : GNUNET_PQ_extract_result (result,
658 : rs,
659 : num_results))
660 : {
661 0 : GNUNET_break (0);
662 0 : rhc->failed = true;
663 0 : return;
664 : }
665 :
666 43 : for (unsigned int i = 0;
667 114 : NULL != work[i].cb;
668 71 : i++)
669 : {
670 185 : if (0 != strcmp (table_name,
671 114 : work[i].table))
672 71 : continue;
673 43 : found = true;
674 43 : qs = GNUNET_PQ_eval_prepared_multi_select (rhc->pg->conn,
675 43 : work[i].statement,
676 : params,
677 43 : work[i].cb,
678 : rhc);
679 43 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
680 : "Reserve %s had %d transactions at %llu in table %s\n",
681 : TALER_B2S (rhc->reserve_pub),
682 : (int) qs,
683 : (unsigned long long) serial_id,
684 : table_name);
685 43 : if (0 >= qs)
686 0 : rhc->failed = true;
687 43 : break;
688 : }
689 43 : if (! found)
690 : {
691 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
692 : "Reserve history includes unsupported table `%s`\n",
693 : table_name);
694 0 : rhc->failed = true;
695 : }
696 43 : GNUNET_PQ_cleanup_result (rs);
697 43 : if (rhc->failed)
698 0 : break;
699 : }
700 : }
701 :
702 :
703 : enum GNUNET_DB_QueryStatus
704 21 : TALER_EXCHANGEDB_get_reserve_history (
705 : struct TALER_EXCHANGEDB_PostgresContext *pg,
706 : const struct TALER_ReservePublicKeyP *reserve_pub,
707 : uint64_t start_off,
708 : uint64_t etag_in,
709 : uint64_t *etag_out,
710 : struct TALER_Amount *balance,
711 : struct TALER_EXCHANGEDB_ReserveHistory **rhp)
712 : {
713 21 : struct ReserveHistoryContext rhc = {
714 : .pg = pg,
715 : .reserve_pub = reserve_pub
716 : };
717 21 : struct GNUNET_PQ_QueryParam params[] = {
718 21 : GNUNET_PQ_query_param_auto_from_type (reserve_pub),
719 : GNUNET_PQ_query_param_end
720 : };
721 21 : struct GNUNET_PQ_QueryParam lparams[] = {
722 21 : GNUNET_PQ_query_param_auto_from_type (reserve_pub),
723 21 : GNUNET_PQ_query_param_uint64 (&start_off),
724 : GNUNET_PQ_query_param_end
725 : };
726 :
727 21 : *rhp = NULL;
728 21 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
729 : "Getting transactions for reserve %s\n",
730 : TALER_B2S (reserve_pub));
731 21 : PREPARE (pg,
732 : "get_reserve_history_etag",
733 : "SELECT"
734 : " his.reserve_history_serial_id"
735 : ",r.current_balance"
736 : " FROM reserve_history his"
737 : " JOIN reserves r USING (reserve_pub)"
738 : " WHERE his.reserve_pub=$1"
739 : " ORDER BY reserve_history_serial_id DESC"
740 : " LIMIT 1;");
741 21 : PREPARE (pg,
742 : "get_reserve_history",
743 : "SELECT"
744 : " table_name"
745 : ",serial_id"
746 : ",reserve_history_serial_id"
747 : " FROM reserve_history"
748 : " WHERE reserve_pub=$1"
749 : " AND reserve_history_serial_id > $2"
750 : " ORDER BY reserve_history_serial_id DESC;");
751 21 : PREPARE (pg,
752 : "get_reserve_history_reserves_in_get_transactions",
753 : "SELECT"
754 : " ri.wire_reference"
755 : ",ri.credit"
756 : ",ri.execution_date"
757 : ",wt.payto_uri AS sender_account_details"
758 : " FROM reserves_in ri"
759 : " JOIN wire_targets wt"
760 : " ON (wire_source_h_payto = wire_target_h_payto)"
761 : " WHERE ri.reserve_pub=$1"
762 : " AND ri.reserve_in_serial_id=$2;");
763 21 : PREPARE (pg,
764 : "get_reserve_history_withdraw_details",
765 : "SELECT"
766 : " planchets_h"
767 : ",amount_with_fee"
768 : ",reserve_sig"
769 : ",max_age"
770 : ",noreveal_index"
771 : ",selected_h"
772 : ",blinding_seed"
773 : ",denom_serials"
774 : ",ARRAY("
775 : " SELECT denominations.denom_pub_hash FROM ("
776 : " SELECT UNNEST(denom_serials) AS id,"
777 : " generate_subscripts(denom_serials, 1) AS nr" /* for order */
778 : " ) AS denoms"
779 : " LEFT JOIN denominations ON denominations.denominations_serial=denoms.id"
780 : ") AS denom_pub_hashes"
781 : " FROM withdraw "
782 : " WHERE withdraw_id=$2"
783 : " AND reserve_pub=$1;");
784 21 : PREPARE (pg,
785 : "get_reserve_history_recoup_by_reserve",
786 : "SELECT"
787 : " rec.coin_pub"
788 : ",rec.coin_sig"
789 : ",rec.coin_blind"
790 : ",rec.amount"
791 : ",rec.recoup_timestamp"
792 : ",ro.planchets_h"
793 : ",denom.denom_pub_hash"
794 : ",kc.denom_sig"
795 : " FROM recoup rec"
796 : " JOIN withdraw ro"
797 : " USING (withdraw_id)"
798 : " JOIN reserves res"
799 : " USING (reserve_pub)"
800 : " JOIN known_coins kc"
801 : " USING (coin_pub)"
802 : " JOIN denominations denom"
803 : " ON (denom.denominations_serial = kc.denominations_serial)"
804 : " WHERE rec.recoup_uuid=$2"
805 : " AND res.reserve_pub=$1;");
806 21 : PREPARE (pg,
807 : "get_reserve_history_close_by_reserve",
808 : "SELECT"
809 : " rc.amount"
810 : ",rc.closing_fee"
811 : ",rc.execution_date"
812 : ",wt.payto_uri AS receiver_account"
813 : ",rc.wtid"
814 : " FROM reserves_close rc"
815 : " JOIN wire_targets wt"
816 : " USING (wire_target_h_payto)"
817 : " WHERE reserve_pub=$1"
818 : " AND close_uuid=$2;");
819 21 : PREPARE (pg,
820 : "get_reserve_history_merge_by_reserve",
821 : "SELECT"
822 : " pr.amount_with_fee"
823 : ",pr.balance"
824 : ",pr.purse_fee"
825 : ",pr.h_contract_terms"
826 : ",pr.merge_pub"
827 : ",am.reserve_sig"
828 : ",pm.purse_pub"
829 : ",pm.merge_timestamp"
830 : ",pr.purse_expiration"
831 : ",pr.age_limit"
832 : ",pr.flags"
833 : " FROM purse_decision pdes"
834 : " JOIN purse_requests pr"
835 : " ON (pr.purse_pub = pdes.purse_pub)"
836 : " JOIN purse_merges pm"
837 : " ON (pm.purse_pub = pdes.purse_pub)"
838 : " JOIN account_merges am"
839 : " ON (am.purse_pub = pm.purse_pub AND"
840 : " am.reserve_pub = pm.reserve_pub)"
841 : " WHERE pdes.purse_decision_serial_id=$2"
842 : " AND pm.reserve_pub=$1"
843 : " AND COALESCE(pm.partner_serial_id,0)=0" /* must be local! */
844 : " AND NOT pdes.refunded;");
845 21 : PREPARE (pg,
846 : "get_reserve_history_open_request_by_reserve",
847 : "SELECT"
848 : " reserve_payment AS open_fee"
849 : ",request_timestamp"
850 : ",expiration_date"
851 : ",requested_purse_limit"
852 : ",reserve_sig"
853 : " FROM reserves_open_requests"
854 : " WHERE reserve_pub=$1"
855 : " AND open_request_uuid=$2;");
856 21 : PREPARE (pg,
857 : "get_reserve_history_close_request_by_reserve",
858 : "SELECT"
859 : " close_timestamp"
860 : ",payto_uri"
861 : ",reserve_sig"
862 : " FROM close_requests"
863 : " WHERE reserve_pub=$1"
864 : " AND close_request_serial_id=$2;");
865 :
866 21 : for (unsigned int i = 0; i<RETRIES; i++)
867 : {
868 : enum GNUNET_DB_QueryStatus qs;
869 : uint64_t end;
870 21 : struct GNUNET_PQ_ResultSpec rs[] = {
871 21 : GNUNET_PQ_result_spec_uint64 ("reserve_history_serial_id",
872 : &end),
873 21 : TALER_PQ_RESULT_SPEC_AMOUNT ("current_balance",
874 : balance),
875 : GNUNET_PQ_result_spec_end
876 : };
877 :
878 21 : if (GNUNET_OK !=
879 21 : TALER_EXCHANGEDB_start_read_committed (pg,
880 : "get-reserve-transactions")
881 : )
882 : {
883 0 : GNUNET_break (0);
884 21 : return GNUNET_DB_STATUS_HARD_ERROR;
885 : }
886 : /* First only check the last item, to see if
887 : we even need to iterate */
888 21 : qs = GNUNET_PQ_eval_prepared_singleton_select (
889 : pg->conn,
890 : "get_reserve_history_etag",
891 : params,
892 : rs);
893 21 : switch (qs)
894 : {
895 0 : case GNUNET_DB_STATUS_HARD_ERROR:
896 0 : TALER_EXCHANGEDB_rollback (pg);
897 0 : return qs;
898 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
899 0 : TALER_EXCHANGEDB_rollback (pg);
900 0 : continue;
901 2 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
902 2 : TALER_EXCHANGEDB_rollback (pg);
903 2 : return qs;
904 19 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
905 19 : *etag_out = end;
906 19 : if (end == etag_in)
907 : {
908 : /* Nothing changed since the client last asked, so we are done --
909 : but the read-committed transaction opened above still has to be
910 : closed. Leaving it open parks the connection in "idle in
911 : transaction" until the next TALER_EXCHANGEDB_start(), whose
912 : preflight then rolls it back and logs it as a bug. */
913 1 : TALER_EXCHANGEDB_rollback (pg);
914 1 : return qs;
915 : }
916 : }
917 : /* We indeed need to iterate over the history */
918 18 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
919 : "Current ETag for reserve %s is %llu\n",
920 : TALER_B2S (reserve_pub),
921 : (unsigned long long) end);
922 :
923 18 : qs = GNUNET_PQ_eval_prepared_multi_select (
924 : pg->conn,
925 : "get_reserve_history",
926 : lparams,
927 : &handle_history_entry,
928 : &rhc);
929 18 : switch (qs)
930 : {
931 0 : case GNUNET_DB_STATUS_HARD_ERROR:
932 0 : TALER_EXCHANGEDB_rollback (pg);
933 0 : return qs;
934 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
935 0 : TALER_EXCHANGEDB_rollback (pg);
936 0 : TALER_EXCHANGEDB_free_reserve_history (rhc.rh);
937 0 : rhc.rh = NULL;
938 0 : rhc.rh_tail = NULL;
939 0 : continue;
940 18 : default:
941 18 : break;
942 : }
943 18 : if (rhc.failed)
944 : {
945 0 : TALER_EXCHANGEDB_rollback (pg);
946 0 : TALER_EXCHANGEDB_free_reserve_history (rhc.rh);
947 0 : return GNUNET_DB_STATUS_SOFT_ERROR;
948 : }
949 18 : qs = TALER_EXCHANGEDB_commit (pg);
950 18 : switch (qs)
951 : {
952 0 : case GNUNET_DB_STATUS_HARD_ERROR:
953 0 : TALER_EXCHANGEDB_free_reserve_history (rhc.rh);
954 0 : return qs;
955 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
956 0 : TALER_EXCHANGEDB_free_reserve_history (rhc.rh);
957 0 : rhc.rh = NULL;
958 0 : rhc.rh_tail = NULL;
959 0 : continue;
960 18 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
961 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
962 18 : *rhp = rhc.rh;
963 18 : return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
964 : }
965 : }
966 0 : return GNUNET_DB_STATUS_SOFT_ERROR;
967 : }
|