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