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