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