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