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