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