Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2014-2026 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
15 : <http://www.gnu.org/licenses/>
16 : */
17 : /**
18 : * @file lib/exchange_api_get-coins-COIN_PUB-history.c
19 : * @brief Implementation of the GET /coins/$COIN_PUB/history request
20 : * @author Christian Grothoff
21 : */
22 : #include <jansson.h>
23 : #include <microhttpd.h> /* just for HTTP status codes */
24 : #include <gnunet/gnunet_util_lib.h>
25 : #include <gnunet/gnunet_json_lib.h>
26 : #include <gnunet/gnunet_curl_lib.h>
27 : #include "taler/taler_json_lib.h"
28 : #include "exchange_api_handle.h"
29 : #include "taler/taler_signatures.h"
30 : #include "exchange_api_curl_defaults.h"
31 :
32 :
33 : /**
34 : * @brief A GET /coins/$COIN_PUB/history Handle
35 : */
36 : struct TALER_EXCHANGE_GetCoinsHistoryHandle
37 : {
38 :
39 : /**
40 : * Base URL of the exchange.
41 : */
42 : char *base_url;
43 :
44 : /**
45 : * The url for this request.
46 : */
47 : char *url;
48 :
49 : /**
50 : * Handle for the request.
51 : */
52 : struct GNUNET_CURL_Job *job;
53 :
54 : /**
55 : * Function to call with the result.
56 : */
57 : TALER_EXCHANGE_GetCoinsHistoryCallback cb;
58 :
59 : /**
60 : * Closure for @e cb.
61 : */
62 : TALER_EXCHANGE_GET_COINS_HISTORY_RESULT_CLOSURE *cb_cls;
63 :
64 : /**
65 : * CURL context to use.
66 : */
67 : struct GNUNET_CURL_Context *ctx;
68 :
69 : /**
70 : * Private key of the coin (for signing in _start).
71 : */
72 : struct TALER_CoinSpendPrivateKeyP coin_priv;
73 :
74 : /**
75 : * Public key of the coin we are querying.
76 : */
77 : struct TALER_CoinSpendPublicKeyP coin_pub;
78 :
79 : /**
80 : * Options set for this request.
81 : */
82 : struct
83 : {
84 : /**
85 : * Only return entries with history_offset > this value.
86 : * Default: 0 (return all entries).
87 : */
88 : uint64_t start_off;
89 : } options;
90 :
91 : };
92 :
93 :
94 : /**
95 : * Context for coin helpers.
96 : */
97 : struct CoinHistoryParseContext
98 : {
99 :
100 : /**
101 : * Keys of the exchange.
102 : */
103 : const struct TALER_EXCHANGE_Keys *keys;
104 :
105 : /**
106 : * Denomination of the coin.
107 : */
108 : const struct TALER_EXCHANGE_DenomPublicKey *dk;
109 :
110 : /**
111 : * Our coin public key.
112 : */
113 : const struct TALER_CoinSpendPublicKeyP *coin_pub;
114 :
115 : /**
116 : * Where to sum up total refunds.
117 : */
118 : struct TALER_Amount *total_in;
119 :
120 : /**
121 : * Total amount encountered.
122 : */
123 : struct TALER_Amount *total_out;
124 :
125 : };
126 :
127 :
128 : /**
129 : * Signature of functions that operate on one of
130 : * the coin's history entries.
131 : *
132 : * @param[in,out] pc overall context
133 : * @param[out] rh where to write the history entry
134 : * @param amount main amount of this operation
135 : * @param transaction JSON details for the operation
136 : * @return #GNUNET_SYSERR on error,
137 : * #GNUNET_OK to add, #GNUNET_NO to subtract
138 : */
139 : typedef enum GNUNET_GenericReturnValue
140 : (*CoinCheckHelper)(struct CoinHistoryParseContext *pc,
141 : struct TALER_EXCHANGE_CoinHistoryEntry *rh,
142 : const struct TALER_Amount *amount,
143 : json_t *transaction);
144 :
145 :
146 : /**
147 : * Handle deposit entry in the coin's history.
148 : *
149 : * @param[in,out] pc overall context
150 : * @param[out] rh history entry to initialize
151 : * @param amount main amount of this operation
152 : * @param transaction JSON details for the operation
153 : * @return #GNUNET_SYSERR on error,
154 : * #GNUNET_OK to add, #GNUNET_NO to subtract
155 : */
156 : static enum GNUNET_GenericReturnValue
157 2 : help_deposit (struct CoinHistoryParseContext *pc,
158 : struct TALER_EXCHANGE_CoinHistoryEntry *rh,
159 : const struct TALER_Amount *amount,
160 : json_t *transaction)
161 : {
162 : struct GNUNET_JSON_Specification spec[] = {
163 2 : TALER_JSON_spec_amount_any ("deposit_fee",
164 : &rh->details.deposit.deposit_fee),
165 2 : GNUNET_JSON_spec_fixed_auto ("merchant_pub",
166 : &rh->details.deposit.merchant_pub),
167 2 : GNUNET_JSON_spec_timestamp ("timestamp",
168 : &rh->details.deposit.wallet_timestamp),
169 2 : GNUNET_JSON_spec_mark_optional (
170 : GNUNET_JSON_spec_timestamp ("refund_deadline",
171 : &rh->details.deposit.refund_deadline),
172 : NULL),
173 2 : GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
174 : &rh->details.deposit.h_contract_terms),
175 2 : GNUNET_JSON_spec_fixed_auto ("h_wire",
176 : &rh->details.deposit.h_wire),
177 2 : GNUNET_JSON_spec_mark_optional (
178 2 : GNUNET_JSON_spec_fixed_auto ("h_policy",
179 : &rh->details.deposit.h_policy),
180 : &rh->details.deposit.no_h_policy),
181 2 : GNUNET_JSON_spec_mark_optional (
182 2 : GNUNET_JSON_spec_fixed_auto ("wallet_data_hash",
183 : &rh->details.deposit.wallet_data_hash),
184 : &rh->details.deposit.no_wallet_data_hash),
185 2 : GNUNET_JSON_spec_mark_optional (
186 2 : GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
187 : &rh->details.deposit.hac),
188 : &rh->details.deposit.no_hac),
189 2 : GNUNET_JSON_spec_fixed_auto ("coin_sig",
190 : &rh->details.deposit.sig),
191 2 : GNUNET_JSON_spec_end ()
192 : };
193 :
194 2 : rh->details.deposit.refund_deadline = GNUNET_TIME_UNIT_ZERO_TS;
195 2 : if (GNUNET_OK !=
196 2 : GNUNET_JSON_parse (transaction,
197 : spec,
198 : NULL, NULL))
199 : {
200 0 : GNUNET_break_op (0);
201 0 : return GNUNET_SYSERR;
202 : }
203 2 : if (GNUNET_OK !=
204 6 : TALER_wallet_deposit_verify (
205 : amount,
206 2 : &rh->details.deposit.deposit_fee,
207 2 : &rh->details.deposit.h_wire,
208 2 : &rh->details.deposit.h_contract_terms,
209 2 : rh->details.deposit.no_wallet_data_hash
210 : ? NULL
211 : : &rh->details.deposit.wallet_data_hash,
212 2 : rh->details.deposit.no_hac
213 : ? NULL
214 : : &rh->details.deposit.hac,
215 2 : rh->details.deposit.no_h_policy
216 : ? NULL
217 : : &rh->details.deposit.h_policy,
218 2 : &pc->dk->h_key,
219 : rh->details.deposit.wallet_timestamp,
220 2 : &rh->details.deposit.merchant_pub,
221 : rh->details.deposit.refund_deadline,
222 : pc->coin_pub,
223 2 : &rh->details.deposit.sig))
224 : {
225 0 : GNUNET_break_op (0);
226 0 : return GNUNET_SYSERR;
227 : }
228 : /* check that deposit fee matches our expectations from /keys! */
229 2 : if ( (GNUNET_YES !=
230 2 : TALER_amount_cmp_currency (&rh->details.deposit.deposit_fee,
231 4 : &pc->dk->fees.deposit)) ||
232 : (0 !=
233 2 : TALER_amount_cmp (&rh->details.deposit.deposit_fee,
234 2 : &pc->dk->fees.deposit)) )
235 : {
236 0 : GNUNET_break_op (0);
237 0 : return GNUNET_SYSERR;
238 : }
239 2 : return GNUNET_YES;
240 : }
241 :
242 :
243 : /**
244 : * Handle melt entry in the coin's history.
245 : *
246 : * @param[in,out] pc overall context
247 : * @param[out] rh history entry to initialize
248 : * @param amount main amount of this operation
249 : * @param transaction JSON details for the operation
250 : * @return #GNUNET_SYSERR on error,
251 : * #GNUNET_OK to add, #GNUNET_NO to subtract
252 : */
253 : static enum GNUNET_GenericReturnValue
254 0 : help_melt (struct CoinHistoryParseContext *pc,
255 : struct TALER_EXCHANGE_CoinHistoryEntry *rh,
256 : const struct TALER_Amount *amount,
257 : json_t *transaction)
258 : {
259 : struct GNUNET_JSON_Specification spec[] = {
260 0 : TALER_JSON_spec_amount_any ("melt_fee",
261 : &rh->details.melt.melt_fee),
262 0 : GNUNET_JSON_spec_fixed_auto ("rc",
263 : &rh->details.melt.rc),
264 0 : GNUNET_JSON_spec_mark_optional (
265 0 : GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
266 : &rh->details.melt.h_age_commitment),
267 : &rh->details.melt.no_hac),
268 0 : GNUNET_JSON_spec_fixed_auto ("coin_sig",
269 : &rh->details.melt.sig),
270 0 : GNUNET_JSON_spec_end ()
271 : };
272 :
273 0 : if (GNUNET_OK !=
274 0 : GNUNET_JSON_parse (transaction,
275 : spec,
276 : NULL, NULL))
277 : {
278 0 : GNUNET_break_op (0);
279 0 : return GNUNET_SYSERR;
280 : }
281 :
282 : /* check that melt fee matches our expectations from /keys! */
283 0 : if ( (GNUNET_YES !=
284 0 : TALER_amount_cmp_currency (&rh->details.melt.melt_fee,
285 0 : &pc->dk->fees.refresh)) ||
286 : (0 !=
287 0 : TALER_amount_cmp (&rh->details.melt.melt_fee,
288 0 : &pc->dk->fees.refresh)) )
289 : {
290 0 : GNUNET_break_op (0);
291 0 : return GNUNET_SYSERR;
292 : }
293 0 : if (GNUNET_OK !=
294 0 : TALER_wallet_melt_verify (
295 : amount,
296 0 : &rh->details.melt.melt_fee,
297 0 : &rh->details.melt.rc,
298 0 : &pc->dk->h_key,
299 0 : rh->details.melt.no_hac
300 : ? NULL
301 : : &rh->details.melt.h_age_commitment,
302 : pc->coin_pub,
303 0 : &rh->details.melt.sig))
304 : {
305 0 : GNUNET_break_op (0);
306 0 : return GNUNET_SYSERR;
307 : }
308 0 : return GNUNET_YES;
309 : }
310 :
311 :
312 : /**
313 : * Handle refund entry in the coin's history.
314 : *
315 : * @param[in,out] pc overall context
316 : * @param[out] rh history entry to initialize
317 : * @param amount main amount of this operation
318 : * @param transaction JSON details for the operation
319 : * @return #GNUNET_SYSERR on error,
320 : * #GNUNET_OK to add, #GNUNET_NO to subtract
321 : */
322 : static enum GNUNET_GenericReturnValue
323 0 : help_refund (struct CoinHistoryParseContext *pc,
324 : struct TALER_EXCHANGE_CoinHistoryEntry *rh,
325 : const struct TALER_Amount *amount,
326 : json_t *transaction)
327 : {
328 : struct GNUNET_JSON_Specification spec[] = {
329 0 : TALER_JSON_spec_amount_any ("refund_fee",
330 : &rh->details.refund.refund_fee),
331 0 : GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
332 : &rh->details.refund.h_contract_terms),
333 0 : GNUNET_JSON_spec_fixed_auto ("merchant_pub",
334 : &rh->details.refund.merchant_pub),
335 0 : GNUNET_JSON_spec_uint64 ("rtransaction_id",
336 : &rh->details.refund.rtransaction_id),
337 0 : GNUNET_JSON_spec_fixed_auto ("merchant_sig",
338 : &rh->details.refund.sig),
339 0 : GNUNET_JSON_spec_end ()
340 : };
341 :
342 0 : if (GNUNET_OK !=
343 0 : GNUNET_JSON_parse (transaction,
344 : spec,
345 : NULL, NULL))
346 : {
347 0 : GNUNET_break_op (0);
348 0 : return GNUNET_SYSERR;
349 : }
350 0 : if (0 >
351 0 : TALER_amount_add (&rh->details.refund.sig_amount,
352 0 : &rh->details.refund.refund_fee,
353 : amount))
354 : {
355 0 : GNUNET_break_op (0);
356 0 : return GNUNET_SYSERR;
357 : }
358 0 : if (GNUNET_OK !=
359 0 : TALER_merchant_refund_verify (pc->coin_pub,
360 0 : &rh->details.refund.h_contract_terms,
361 : rh->details.refund.rtransaction_id,
362 0 : &rh->details.refund.sig_amount,
363 0 : &rh->details.refund.merchant_pub,
364 0 : &rh->details.refund.sig))
365 : {
366 0 : GNUNET_break_op (0);
367 0 : return GNUNET_SYSERR;
368 : }
369 : /* check that refund fee matches our expectations from /keys! */
370 0 : if ( (GNUNET_YES !=
371 0 : TALER_amount_cmp_currency (&rh->details.refund.refund_fee,
372 0 : &pc->dk->fees.refund)) ||
373 : (0 !=
374 0 : TALER_amount_cmp (&rh->details.refund.refund_fee,
375 0 : &pc->dk->fees.refund)) )
376 : {
377 0 : GNUNET_break_op (0);
378 0 : return GNUNET_SYSERR;
379 : }
380 0 : return GNUNET_NO;
381 : }
382 :
383 :
384 : /**
385 : * Handle recoup entry in the coin's history.
386 : *
387 : * @param[in,out] pc overall context
388 : * @param[out] rh history entry to initialize
389 : * @param amount main amount of this operation
390 : * @param transaction JSON details for the operation
391 : * @return #GNUNET_SYSERR on error,
392 : * #GNUNET_OK to add, #GNUNET_NO to subtract
393 : */
394 : static enum GNUNET_GenericReturnValue
395 0 : help_recoup (struct CoinHistoryParseContext *pc,
396 : struct TALER_EXCHANGE_CoinHistoryEntry *rh,
397 : const struct TALER_Amount *amount,
398 : json_t *transaction)
399 : {
400 : struct GNUNET_JSON_Specification spec[] = {
401 0 : GNUNET_JSON_spec_fixed_auto ("exchange_sig",
402 : &rh->details.recoup.exchange_sig),
403 0 : GNUNET_JSON_spec_fixed_auto ("exchange_pub",
404 : &rh->details.recoup.exchange_pub),
405 0 : GNUNET_JSON_spec_fixed_auto ("reserve_pub",
406 : &rh->details.recoup.reserve_pub),
407 0 : GNUNET_JSON_spec_fixed_auto ("coin_sig",
408 : &rh->details.recoup.coin_sig),
409 0 : GNUNET_JSON_spec_fixed_auto ("coin_blind",
410 : &rh->details.recoup.coin_bks),
411 0 : GNUNET_JSON_spec_timestamp ("timestamp",
412 : &rh->details.recoup.timestamp),
413 0 : GNUNET_JSON_spec_end ()
414 : };
415 :
416 0 : if (GNUNET_OK !=
417 0 : GNUNET_JSON_parse (transaction,
418 : spec,
419 : NULL, NULL))
420 : {
421 0 : GNUNET_break_op (0);
422 0 : return GNUNET_SYSERR;
423 : }
424 0 : if (GNUNET_OK !=
425 0 : TALER_EXCHANGE_test_signing_key (pc->keys,
426 0 : &rh->details.recoup.exchange_pub))
427 : {
428 0 : GNUNET_break_op (0);
429 0 : return GNUNET_SYSERR;
430 : }
431 0 : if (GNUNET_OK !=
432 0 : TALER_exchange_online_confirm_recoup_verify (
433 : rh->details.recoup.timestamp,
434 : amount,
435 : pc->coin_pub,
436 0 : &rh->details.recoup.reserve_pub,
437 0 : &rh->details.recoup.exchange_pub,
438 0 : &rh->details.recoup.exchange_sig))
439 : {
440 0 : GNUNET_break_op (0);
441 0 : return GNUNET_SYSERR;
442 : }
443 0 : if (GNUNET_OK !=
444 0 : TALER_wallet_recoup_verify (&pc->dk->h_key,
445 0 : &rh->details.recoup.coin_bks,
446 : pc->coin_pub,
447 0 : &rh->details.recoup.coin_sig))
448 : {
449 0 : GNUNET_break_op (0);
450 0 : return GNUNET_SYSERR;
451 : }
452 0 : return GNUNET_YES;
453 : }
454 :
455 :
456 : /**
457 : * Handle recoup-refresh entry in the coin's history.
458 : * This is the coin that was subjected to a recoup,
459 : * the value being credited to the old coin.
460 : *
461 : * @param[in,out] pc overall context
462 : * @param[out] rh history entry to initialize
463 : * @param amount main amount of this operation
464 : * @param transaction JSON details for the operation
465 : * @return #GNUNET_SYSERR on error,
466 : * #GNUNET_OK to add, #GNUNET_NO to subtract
467 : */
468 : static enum GNUNET_GenericReturnValue
469 0 : help_recoup_refresh (struct CoinHistoryParseContext *pc,
470 : struct TALER_EXCHANGE_CoinHistoryEntry *rh,
471 : const struct TALER_Amount *amount,
472 : json_t *transaction)
473 : {
474 : struct GNUNET_JSON_Specification spec[] = {
475 0 : GNUNET_JSON_spec_fixed_auto ("exchange_sig",
476 : &rh->details.recoup_refresh.exchange_sig),
477 0 : GNUNET_JSON_spec_fixed_auto ("exchange_pub",
478 : &rh->details.recoup_refresh.exchange_pub),
479 0 : GNUNET_JSON_spec_fixed_auto ("coin_sig",
480 : &rh->details.recoup_refresh.coin_sig),
481 0 : GNUNET_JSON_spec_fixed_auto ("old_coin_pub",
482 : &rh->details.recoup_refresh.old_coin_pub),
483 0 : GNUNET_JSON_spec_fixed_auto ("coin_blind",
484 : &rh->details.recoup_refresh.coin_bks),
485 0 : GNUNET_JSON_spec_timestamp ("timestamp",
486 : &rh->details.recoup_refresh.timestamp),
487 0 : GNUNET_JSON_spec_end ()
488 : };
489 :
490 0 : if (GNUNET_OK !=
491 0 : GNUNET_JSON_parse (transaction,
492 : spec,
493 : NULL, NULL))
494 : {
495 0 : GNUNET_break_op (0);
496 0 : return GNUNET_SYSERR;
497 : }
498 0 : if (GNUNET_OK !=
499 0 : TALER_EXCHANGE_test_signing_key (
500 : pc->keys,
501 0 : &rh->details.recoup_refresh.exchange_pub))
502 : {
503 0 : GNUNET_break_op (0);
504 0 : return GNUNET_SYSERR;
505 : }
506 0 : if (GNUNET_OK !=
507 0 : TALER_exchange_online_confirm_recoup_refresh_verify (
508 : rh->details.recoup_refresh.timestamp,
509 : amount,
510 : pc->coin_pub,
511 0 : &rh->details.recoup_refresh.old_coin_pub,
512 0 : &rh->details.recoup_refresh.exchange_pub,
513 0 : &rh->details.recoup_refresh.exchange_sig))
514 : {
515 0 : GNUNET_break_op (0);
516 0 : return GNUNET_SYSERR;
517 : }
518 0 : if (GNUNET_OK !=
519 0 : TALER_wallet_recoup_verify (&pc->dk->h_key,
520 0 : &rh->details.recoup_refresh.coin_bks,
521 : pc->coin_pub,
522 0 : &rh->details.recoup_refresh.coin_sig))
523 : {
524 0 : GNUNET_break_op (0);
525 0 : return GNUNET_SYSERR;
526 : }
527 0 : return GNUNET_YES;
528 : }
529 :
530 :
531 : /**
532 : * Handle old coin recoup entry in the coin's history.
533 : * This is the coin that was credited in a recoup,
534 : * the value being credited to this coin.
535 : *
536 : * @param[in,out] pc overall context
537 : * @param[out] rh history entry to initialize
538 : * @param amount main amount of this operation
539 : * @param transaction JSON details for the operation
540 : * @return #GNUNET_SYSERR on error,
541 : * #GNUNET_OK to add, #GNUNET_NO to subtract
542 : */
543 : static enum GNUNET_GenericReturnValue
544 0 : help_old_coin_recoup (struct CoinHistoryParseContext *pc,
545 : struct TALER_EXCHANGE_CoinHistoryEntry *rh,
546 : const struct TALER_Amount *amount,
547 : json_t *transaction)
548 : {
549 : struct GNUNET_JSON_Specification spec[] = {
550 0 : GNUNET_JSON_spec_fixed_auto ("exchange_sig",
551 : &rh->details.old_coin_recoup.exchange_sig),
552 0 : GNUNET_JSON_spec_fixed_auto ("exchange_pub",
553 : &rh->details.old_coin_recoup.exchange_pub),
554 0 : GNUNET_JSON_spec_fixed_auto ("coin_pub",
555 : &rh->details.old_coin_recoup.new_coin_pub),
556 0 : GNUNET_JSON_spec_timestamp ("timestamp",
557 : &rh->details.old_coin_recoup.timestamp),
558 0 : GNUNET_JSON_spec_end ()
559 : };
560 :
561 0 : if (GNUNET_OK !=
562 0 : GNUNET_JSON_parse (transaction,
563 : spec,
564 : NULL, NULL))
565 : {
566 0 : GNUNET_break_op (0);
567 0 : return GNUNET_SYSERR;
568 : }
569 0 : if (GNUNET_OK !=
570 0 : TALER_EXCHANGE_test_signing_key (
571 : pc->keys,
572 0 : &rh->details.old_coin_recoup.exchange_pub))
573 : {
574 0 : GNUNET_break_op (0);
575 0 : return GNUNET_SYSERR;
576 : }
577 0 : if (GNUNET_OK !=
578 0 : TALER_exchange_online_confirm_recoup_refresh_verify (
579 : rh->details.old_coin_recoup.timestamp,
580 : amount,
581 0 : &rh->details.old_coin_recoup.new_coin_pub,
582 : pc->coin_pub,
583 0 : &rh->details.old_coin_recoup.exchange_pub,
584 0 : &rh->details.old_coin_recoup.exchange_sig))
585 : {
586 0 : GNUNET_break_op (0);
587 0 : return GNUNET_SYSERR;
588 : }
589 0 : return GNUNET_NO;
590 : }
591 :
592 :
593 : /**
594 : * Handle purse deposit entry in the coin's history.
595 : *
596 : * @param[in,out] pc overall context
597 : * @param[out] rh history entry to initialize
598 : * @param amount main amount of this operation
599 : * @param transaction JSON details for the operation
600 : * @return #GNUNET_SYSERR on error,
601 : * #GNUNET_OK to add, #GNUNET_NO to subtract
602 : */
603 : static enum GNUNET_GenericReturnValue
604 3 : help_purse_deposit (struct CoinHistoryParseContext *pc,
605 : struct TALER_EXCHANGE_CoinHistoryEntry *rh,
606 : const struct TALER_Amount *amount,
607 : json_t *transaction)
608 : {
609 : struct GNUNET_JSON_Specification spec[] = {
610 3 : TALER_JSON_spec_web_url ("exchange_base_url",
611 : &rh->details.purse_deposit.exchange_base_url),
612 3 : GNUNET_JSON_spec_mark_optional (
613 3 : GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
614 : &rh->details.purse_deposit.phac),
615 : NULL),
616 3 : GNUNET_JSON_spec_fixed_auto ("purse_pub",
617 : &rh->details.purse_deposit.purse_pub),
618 3 : GNUNET_JSON_spec_bool ("refunded",
619 : &rh->details.purse_deposit.refunded),
620 3 : GNUNET_JSON_spec_fixed_auto ("coin_sig",
621 : &rh->details.purse_deposit.coin_sig),
622 3 : GNUNET_JSON_spec_end ()
623 : };
624 :
625 3 : if (GNUNET_OK !=
626 3 : GNUNET_JSON_parse (transaction,
627 : spec,
628 : NULL, NULL))
629 : {
630 0 : GNUNET_break_op (0);
631 0 : return GNUNET_SYSERR;
632 : }
633 3 : if (GNUNET_OK !=
634 3 : TALER_wallet_purse_deposit_verify (
635 : rh->details.purse_deposit.exchange_base_url,
636 3 : &rh->details.purse_deposit.purse_pub,
637 : amount,
638 3 : &pc->dk->h_key,
639 3 : &rh->details.purse_deposit.phac,
640 : pc->coin_pub,
641 3 : &rh->details.purse_deposit.coin_sig))
642 : {
643 0 : GNUNET_break_op (0);
644 0 : return GNUNET_SYSERR;
645 : }
646 3 : if (rh->details.purse_deposit.refunded)
647 : {
648 : /* We wave the deposit fee. */
649 0 : if (0 >
650 0 : TALER_amount_add (pc->total_in,
651 0 : pc->total_in,
652 0 : &pc->dk->fees.deposit))
653 : {
654 : /* overflow in refund history? inconceivable! Bad exchange! */
655 0 : GNUNET_break_op (0);
656 0 : return GNUNET_SYSERR;
657 : }
658 : }
659 3 : return GNUNET_YES;
660 : }
661 :
662 :
663 : /**
664 : * Handle purse refund entry in the coin's history.
665 : *
666 : * @param[in,out] pc overall context
667 : * @param[out] rh history entry to initialize
668 : * @param amount main amount of this operation
669 : * @param transaction JSON details for the operation
670 : * @return #GNUNET_SYSERR on error,
671 : * #GNUNET_OK to add, #GNUNET_NO to subtract
672 : */
673 : static enum GNUNET_GenericReturnValue
674 0 : help_purse_refund (struct CoinHistoryParseContext *pc,
675 : struct TALER_EXCHANGE_CoinHistoryEntry *rh,
676 : const struct TALER_Amount *amount,
677 : json_t *transaction)
678 : {
679 : struct GNUNET_JSON_Specification spec[] = {
680 0 : TALER_JSON_spec_amount_any ("refund_fee",
681 : &rh->details.purse_refund.refund_fee),
682 0 : GNUNET_JSON_spec_fixed_auto ("exchange_sig",
683 : &rh->details.purse_refund.exchange_sig),
684 0 : GNUNET_JSON_spec_fixed_auto ("exchange_pub",
685 : &rh->details.purse_refund.exchange_pub),
686 0 : GNUNET_JSON_spec_fixed_auto ("purse_pub",
687 : &rh->details.purse_refund.purse_pub),
688 0 : GNUNET_JSON_spec_end ()
689 : };
690 :
691 0 : if (GNUNET_OK !=
692 0 : GNUNET_JSON_parse (transaction,
693 : spec,
694 : NULL, NULL))
695 : {
696 0 : GNUNET_break_op (0);
697 0 : return GNUNET_SYSERR;
698 : }
699 0 : if (GNUNET_OK !=
700 0 : TALER_EXCHANGE_test_signing_key (
701 : pc->keys,
702 0 : &rh->details.purse_refund.exchange_pub))
703 : {
704 0 : GNUNET_break_op (0);
705 0 : return GNUNET_SYSERR;
706 : }
707 0 : if (GNUNET_OK !=
708 0 : TALER_exchange_online_purse_refund_verify (
709 : amount,
710 0 : &rh->details.purse_refund.refund_fee,
711 : pc->coin_pub,
712 0 : &rh->details.purse_refund.purse_pub,
713 0 : &rh->details.purse_refund.exchange_pub,
714 0 : &rh->details.purse_refund.exchange_sig))
715 : {
716 0 : GNUNET_break_op (0);
717 0 : return GNUNET_SYSERR;
718 : }
719 0 : if ( (GNUNET_YES !=
720 0 : TALER_amount_cmp_currency (&rh->details.purse_refund.refund_fee,
721 0 : &pc->dk->fees.refund)) ||
722 : (0 !=
723 0 : TALER_amount_cmp (&rh->details.purse_refund.refund_fee,
724 0 : &pc->dk->fees.refund)) )
725 : {
726 0 : GNUNET_break_op (0);
727 0 : return GNUNET_SYSERR;
728 : }
729 0 : return GNUNET_NO;
730 : }
731 :
732 :
733 : /**
734 : * Handle reserve deposit entry in the coin's history.
735 : *
736 : * @param[in,out] pc overall context
737 : * @param[out] rh history entry to initialize
738 : * @param amount main amount of this operation
739 : * @param transaction JSON details for the operation
740 : * @return #GNUNET_SYSERR on error,
741 : * #GNUNET_OK to add, #GNUNET_NO to subtract
742 : */
743 : static enum GNUNET_GenericReturnValue
744 0 : help_reserve_open_deposit (struct CoinHistoryParseContext *pc,
745 : struct TALER_EXCHANGE_CoinHistoryEntry *rh,
746 : const struct TALER_Amount *amount,
747 : json_t *transaction)
748 : {
749 : struct GNUNET_JSON_Specification spec[] = {
750 0 : GNUNET_JSON_spec_fixed_auto (
751 : "reserve_sig",
752 : &rh->details.reserve_open_deposit.reserve_sig),
753 0 : GNUNET_JSON_spec_fixed_auto (
754 : "coin_sig",
755 : &rh->details.reserve_open_deposit.coin_sig),
756 0 : TALER_JSON_spec_amount_any (
757 : "coin_contribution",
758 : &rh->details.reserve_open_deposit.coin_contribution),
759 0 : GNUNET_JSON_spec_mark_optional (
760 0 : GNUNET_JSON_spec_fixed_auto (
761 : "h_age_commitment",
762 : &rh->details.reserve_open_deposit.h_age_commitment),
763 : &rh->details.reserve_open_deposit.no_hac),
764 0 : GNUNET_JSON_spec_end ()
765 : };
766 :
767 0 : if (GNUNET_OK !=
768 0 : GNUNET_JSON_parse (transaction,
769 : spec,
770 : NULL, NULL))
771 : {
772 0 : GNUNET_break_op (0);
773 0 : return GNUNET_SYSERR;
774 : }
775 0 : if (GNUNET_OK !=
776 0 : TALER_wallet_reserve_open_deposit_verify (
777 : amount,
778 0 : &pc->dk->h_key,
779 0 : rh->details.reserve_open_deposit.no_hac
780 : ? NULL
781 : : &rh->details.reserve_open_deposit.h_age_commitment,
782 0 : &rh->details.reserve_open_deposit.reserve_sig,
783 : pc->coin_pub,
784 0 : &rh->details.reserve_open_deposit.coin_sig))
785 : {
786 0 : GNUNET_break_op (0);
787 0 : return GNUNET_SYSERR;
788 : }
789 0 : return GNUNET_YES;
790 : }
791 :
792 :
793 : enum GNUNET_GenericReturnValue
794 4 : TALER_EXCHANGE_parse_coin_history (
795 : const struct TALER_EXCHANGE_Keys *keys,
796 : const struct TALER_EXCHANGE_DenomPublicKey *dk,
797 : const json_t *history,
798 : const struct TALER_CoinSpendPublicKeyP *coin_pub,
799 : struct TALER_Amount *total_in,
800 : struct TALER_Amount *total_out,
801 : unsigned int rlen,
802 : struct TALER_EXCHANGE_CoinHistoryEntry rhistory[static rlen])
803 4 : {
804 : const struct
805 : {
806 : const char *type;
807 : CoinCheckHelper helper;
808 : enum TALER_EXCHANGE_CoinTransactionType ctt;
809 4 : } map[] = {
810 : { "DEPOSIT",
811 : &help_deposit,
812 : TALER_EXCHANGE_CTT_DEPOSIT },
813 : { "MELT",
814 : &help_melt,
815 : TALER_EXCHANGE_CTT_MELT },
816 : { "REFUND",
817 : &help_refund,
818 : TALER_EXCHANGE_CTT_REFUND },
819 : { "RECOUP-WITHDRAW",
820 : &help_recoup,
821 : TALER_EXCHANGE_CTT_RECOUP },
822 : { "RECOUP-REFRESH",
823 : &help_recoup_refresh,
824 : TALER_EXCHANGE_CTT_RECOUP_REFRESH },
825 : { "RECOUP-REFRESH-RECEIVER",
826 : &help_old_coin_recoup,
827 : TALER_EXCHANGE_CTT_OLD_COIN_RECOUP },
828 : { "PURSE-DEPOSIT",
829 : &help_purse_deposit,
830 : TALER_EXCHANGE_CTT_PURSE_DEPOSIT },
831 : { "PURSE-REFUND",
832 : &help_purse_refund,
833 : TALER_EXCHANGE_CTT_PURSE_REFUND },
834 : { "RESERVE-OPEN-DEPOSIT",
835 : &help_reserve_open_deposit,
836 : TALER_EXCHANGE_CTT_RESERVE_OPEN_DEPOSIT },
837 : { NULL, NULL, TALER_EXCHANGE_CTT_NONE }
838 : };
839 4 : struct CoinHistoryParseContext pc = {
840 : .keys = keys,
841 : .dk = dk,
842 : .coin_pub = coin_pub,
843 : .total_out = total_out,
844 : .total_in = total_in
845 : };
846 : size_t len;
847 :
848 4 : if (NULL == history)
849 : {
850 0 : GNUNET_break_op (0);
851 0 : return GNUNET_SYSERR;
852 : }
853 4 : len = json_array_size (history);
854 4 : if (0 == len)
855 : {
856 0 : GNUNET_break_op (0);
857 0 : return GNUNET_SYSERR;
858 : }
859 4 : *total_in = dk->value;
860 4 : GNUNET_assert (GNUNET_OK ==
861 : TALER_amount_set_zero (total_in->currency,
862 : total_out));
863 9 : for (size_t off = 0; off < len; off++)
864 : {
865 5 : struct TALER_EXCHANGE_CoinHistoryEntry *rh = &rhistory[off];
866 5 : json_t *transaction = json_array_get (history,
867 : off);
868 : enum GNUNET_GenericReturnValue add;
869 : const char *type;
870 : struct GNUNET_JSON_Specification spec_glob[] = {
871 5 : TALER_JSON_spec_amount_any ("amount",
872 : &rh->amount),
873 5 : GNUNET_JSON_spec_string ("type",
874 : &type),
875 5 : GNUNET_JSON_spec_uint64 ("history_offset",
876 : &rh->history_offset),
877 5 : GNUNET_JSON_spec_end ()
878 : };
879 :
880 5 : if (GNUNET_OK !=
881 5 : GNUNET_JSON_parse (transaction,
882 : spec_glob,
883 : NULL, NULL))
884 : {
885 0 : GNUNET_break_op (0);
886 0 : return GNUNET_SYSERR;
887 : }
888 5 : if (GNUNET_YES !=
889 5 : TALER_amount_cmp_currency (&rh->amount,
890 : total_in))
891 : {
892 0 : GNUNET_break_op (0);
893 0 : return GNUNET_SYSERR;
894 : }
895 5 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
896 : "Operation of type %s with amount %s\n",
897 : type,
898 : TALER_amount2s (&rh->amount));
899 5 : add = GNUNET_SYSERR;
900 23 : for (unsigned int i = 0; NULL != map[i].type; i++)
901 : {
902 23 : if (0 == strcasecmp (type,
903 23 : map[i].type))
904 : {
905 5 : rh->type = map[i].ctt;
906 5 : add = map[i].helper (&pc,
907 : rh,
908 5 : &rh->amount,
909 : transaction);
910 5 : break;
911 : }
912 : }
913 5 : switch (add)
914 : {
915 0 : case GNUNET_SYSERR:
916 : /* entry type not supported, new version on server? */
917 0 : rh->type = TALER_EXCHANGE_CTT_NONE;
918 0 : GNUNET_break_op (0);
919 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
920 : "Unexpected type `%s' in response\n",
921 : type);
922 0 : return GNUNET_SYSERR;
923 5 : case GNUNET_YES:
924 : /* This amount should be debited from the coin */
925 5 : if (0 >
926 5 : TALER_amount_add (total_out,
927 : total_out,
928 5 : &rh->amount))
929 : {
930 : /* overflow in history already!? inconceivable! Bad exchange! */
931 0 : GNUNET_break_op (0);
932 0 : return GNUNET_SYSERR;
933 : }
934 5 : break;
935 0 : case GNUNET_NO:
936 : /* This amount should be credited to the coin. */
937 0 : if (0 >
938 0 : TALER_amount_add (total_in,
939 : total_in,
940 0 : &rh->amount))
941 : {
942 : /* overflow in refund history? inconceivable! Bad exchange! */
943 0 : GNUNET_break_op (0);
944 0 : return GNUNET_SYSERR;
945 : }
946 0 : break;
947 : } /* end of switch(add) */
948 : }
949 4 : return GNUNET_OK;
950 : }
951 :
952 :
953 : /**
954 : * We received an #MHD_HTTP_OK response. Handle the JSON response.
955 : *
956 : * @param gcsh handle of the request
957 : * @param j JSON response
958 : * @return #GNUNET_OK on success
959 : */
960 : static enum GNUNET_GenericReturnValue
961 4 : handle_coins_history_ok (struct TALER_EXCHANGE_GetCoinsHistoryHandle *gcsh,
962 : const json_t *j)
963 : {
964 4 : struct TALER_EXCHANGE_GetCoinsHistoryResponse rs = {
965 : .hr.reply = j,
966 : .hr.http_status = MHD_HTTP_OK
967 : };
968 : struct GNUNET_JSON_Specification spec[] = {
969 4 : TALER_JSON_spec_amount_any ("balance",
970 : &rs.details.ok.balance),
971 4 : GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
972 : &rs.details.ok.h_denom_pub),
973 4 : GNUNET_JSON_spec_array_const ("history",
974 : &rs.details.ok.history),
975 4 : GNUNET_JSON_spec_end ()
976 : };
977 :
978 4 : if (GNUNET_OK !=
979 4 : GNUNET_JSON_parse (j,
980 : spec,
981 : NULL,
982 : NULL))
983 : {
984 0 : GNUNET_break_op (0);
985 0 : return GNUNET_SYSERR;
986 : }
987 4 : if (NULL != gcsh->cb)
988 : {
989 4 : gcsh->cb (gcsh->cb_cls,
990 : &rs);
991 4 : gcsh->cb = NULL;
992 : }
993 4 : GNUNET_JSON_parse_free (spec);
994 4 : return GNUNET_OK;
995 : }
996 :
997 :
998 : /**
999 : * Function called when we're done processing the
1000 : * HTTP GET /coins/$COIN_PUB/history request.
1001 : *
1002 : * @param cls the `struct TALER_EXCHANGE_GetCoinsHistoryHandle`
1003 : * @param response_code HTTP response code, 0 on error
1004 : * @param response parsed JSON result, NULL on error
1005 : */
1006 : static void
1007 4 : handle_coins_history_finished (void *cls,
1008 : long response_code,
1009 : const void *response)
1010 : {
1011 4 : struct TALER_EXCHANGE_GetCoinsHistoryHandle *gcsh = cls;
1012 4 : const json_t *j = response;
1013 4 : struct TALER_EXCHANGE_GetCoinsHistoryResponse rs = {
1014 : .hr.reply = j,
1015 4 : .hr.http_status = (unsigned int) response_code
1016 : };
1017 :
1018 4 : gcsh->job = NULL;
1019 4 : switch (response_code)
1020 : {
1021 0 : case 0:
1022 0 : rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
1023 0 : break;
1024 4 : case MHD_HTTP_OK:
1025 4 : if (GNUNET_OK !=
1026 4 : handle_coins_history_ok (gcsh,
1027 : j))
1028 : {
1029 0 : rs.hr.http_status = 0;
1030 0 : rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
1031 : }
1032 4 : break;
1033 0 : case MHD_HTTP_NO_CONTENT:
1034 0 : break;
1035 0 : case MHD_HTTP_NOT_MODIFIED:
1036 0 : break;
1037 0 : case MHD_HTTP_BAD_REQUEST:
1038 : /* This should never happen, either us or the exchange is buggy
1039 : (or API version conflict); just pass JSON reply to the application */
1040 0 : GNUNET_break (0);
1041 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
1042 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
1043 0 : break;
1044 0 : case MHD_HTTP_FORBIDDEN:
1045 : /* This should never happen, either us or the exchange is buggy
1046 : (or API version conflict); just pass JSON reply to the application */
1047 0 : GNUNET_break (0);
1048 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
1049 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
1050 0 : break;
1051 0 : case MHD_HTTP_NOT_FOUND:
1052 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
1053 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
1054 0 : break;
1055 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
1056 : /* Server had an internal issue; we should retry, but this API
1057 : leaves this to the application */
1058 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
1059 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
1060 0 : break;
1061 0 : default:
1062 : /* unexpected response code */
1063 0 : GNUNET_break_op (0);
1064 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
1065 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
1066 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1067 : "Unexpected response code %u/%d for coins history\n",
1068 : (unsigned int) response_code,
1069 : (int) rs.hr.ec);
1070 0 : break;
1071 : }
1072 4 : if (NULL != gcsh->cb)
1073 : {
1074 0 : gcsh->cb (gcsh->cb_cls,
1075 : &rs);
1076 0 : gcsh->cb = NULL;
1077 : }
1078 4 : TALER_EXCHANGE_get_coins_history_cancel (gcsh);
1079 4 : }
1080 :
1081 :
1082 : struct TALER_EXCHANGE_GetCoinsHistoryHandle *
1083 4 : TALER_EXCHANGE_get_coins_history_create (
1084 : struct GNUNET_CURL_Context *ctx,
1085 : const char *url,
1086 : const struct TALER_CoinSpendPrivateKeyP *coin_priv)
1087 : {
1088 : struct TALER_EXCHANGE_GetCoinsHistoryHandle *gcsh;
1089 :
1090 4 : gcsh = GNUNET_new (struct TALER_EXCHANGE_GetCoinsHistoryHandle);
1091 4 : gcsh->ctx = ctx;
1092 4 : gcsh->base_url = GNUNET_strdup (url);
1093 4 : gcsh->coin_priv = *coin_priv;
1094 4 : GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
1095 : &gcsh->coin_pub.eddsa_pub);
1096 4 : gcsh->options.start_off = 0;
1097 4 : return gcsh;
1098 : }
1099 :
1100 :
1101 : enum GNUNET_GenericReturnValue
1102 0 : TALER_EXCHANGE_get_coins_history_set_options_ (
1103 : struct TALER_EXCHANGE_GetCoinsHistoryHandle *gcsh,
1104 : unsigned int num_options,
1105 : const struct TALER_EXCHANGE_GetCoinsHistoryOptionValue *options)
1106 : {
1107 0 : for (unsigned int i = 0; i < num_options; i++)
1108 : {
1109 0 : const struct TALER_EXCHANGE_GetCoinsHistoryOptionValue *opt = &options[i];
1110 :
1111 0 : switch (opt->option)
1112 : {
1113 0 : case TALER_EXCHANGE_GET_COINS_HISTORY_OPTION_END:
1114 0 : return GNUNET_OK;
1115 0 : case TALER_EXCHANGE_GET_COINS_HISTORY_OPTION_START_OFF:
1116 0 : gcsh->options.start_off = opt->details.start_off;
1117 0 : break;
1118 : }
1119 : }
1120 0 : return GNUNET_OK;
1121 : }
1122 :
1123 :
1124 : enum TALER_ErrorCode
1125 4 : TALER_EXCHANGE_get_coins_history_start (
1126 : struct TALER_EXCHANGE_GetCoinsHistoryHandle *gcsh,
1127 : TALER_EXCHANGE_GetCoinsHistoryCallback cb,
1128 : TALER_EXCHANGE_GET_COINS_HISTORY_RESULT_CLOSURE *cb_cls)
1129 : {
1130 : char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 64];
1131 : struct curl_slist *job_headers;
1132 : CURL *eh;
1133 :
1134 4 : if (NULL != gcsh->job)
1135 : {
1136 0 : GNUNET_break (0);
1137 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
1138 : }
1139 4 : gcsh->cb = cb;
1140 4 : gcsh->cb_cls = cb_cls;
1141 : {
1142 : char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
1143 : char *end;
1144 :
1145 4 : end = GNUNET_STRINGS_data_to_string (
1146 4 : &gcsh->coin_pub,
1147 : sizeof (gcsh->coin_pub),
1148 : pub_str,
1149 : sizeof (pub_str));
1150 4 : *end = '\0';
1151 4 : if (0 != gcsh->options.start_off)
1152 0 : GNUNET_snprintf (arg_str,
1153 : sizeof (arg_str),
1154 : "coins/%s/history?start=%llu",
1155 : pub_str,
1156 0 : (unsigned long long) gcsh->options.start_off);
1157 : else
1158 4 : GNUNET_snprintf (arg_str,
1159 : sizeof (arg_str),
1160 : "coins/%s/history",
1161 : pub_str);
1162 : }
1163 4 : gcsh->url = TALER_url_join (gcsh->base_url,
1164 : arg_str,
1165 : NULL);
1166 4 : if (NULL == gcsh->url)
1167 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
1168 4 : eh = TALER_EXCHANGE_curl_easy_get_ (gcsh->url);
1169 4 : if (NULL == eh)
1170 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
1171 : {
1172 : struct TALER_CoinSpendSignatureP coin_sig;
1173 : char *sig_hdr;
1174 : char *hdr;
1175 :
1176 4 : TALER_wallet_coin_history_sign (gcsh->options.start_off,
1177 4 : &gcsh->coin_priv,
1178 : &coin_sig);
1179 4 : sig_hdr = GNUNET_STRINGS_data_to_string_alloc (
1180 : &coin_sig,
1181 : sizeof (coin_sig));
1182 4 : GNUNET_asprintf (&hdr,
1183 : "%s: %s",
1184 : TALER_COIN_HISTORY_SIGNATURE_HEADER,
1185 : sig_hdr);
1186 4 : GNUNET_free (sig_hdr);
1187 4 : job_headers = curl_slist_append (NULL,
1188 : hdr);
1189 4 : GNUNET_free (hdr);
1190 4 : if (NULL == job_headers)
1191 : {
1192 0 : GNUNET_break (0);
1193 0 : curl_easy_cleanup (eh);
1194 0 : GNUNET_free (gcsh->url);
1195 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
1196 : }
1197 : }
1198 4 : gcsh->job = GNUNET_CURL_job_add2 (gcsh->ctx,
1199 : eh,
1200 : job_headers,
1201 : &handle_coins_history_finished,
1202 : gcsh);
1203 4 : curl_slist_free_all (job_headers);
1204 4 : if (NULL == gcsh->job)
1205 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
1206 4 : return TALER_EC_NONE;
1207 : }
1208 :
1209 :
1210 : void
1211 4 : TALER_EXCHANGE_get_coins_history_cancel (
1212 : struct TALER_EXCHANGE_GetCoinsHistoryHandle *gcsh)
1213 : {
1214 4 : if (NULL != gcsh->job)
1215 : {
1216 0 : GNUNET_CURL_job_cancel (gcsh->job);
1217 0 : gcsh->job = NULL;
1218 : }
1219 4 : GNUNET_free (gcsh->url);
1220 4 : GNUNET_free (gcsh->base_url);
1221 4 : GNUNET_free (gcsh);
1222 4 : }
1223 :
1224 :
1225 : enum GNUNET_GenericReturnValue
1226 0 : TALER_EXCHANGE_check_coin_signature_conflict (
1227 : const json_t *history,
1228 : const struct TALER_CoinSpendSignatureP *coin_sig)
1229 : {
1230 : size_t off;
1231 : json_t *entry;
1232 :
1233 0 : json_array_foreach (history, off, entry)
1234 : {
1235 : struct TALER_CoinSpendSignatureP cs;
1236 : struct GNUNET_JSON_Specification spec[] = {
1237 0 : GNUNET_JSON_spec_fixed_auto ("coin_sig",
1238 : &cs),
1239 0 : GNUNET_JSON_spec_end ()
1240 : };
1241 :
1242 0 : if (GNUNET_OK !=
1243 0 : GNUNET_JSON_parse (entry,
1244 : spec,
1245 : NULL, NULL))
1246 0 : continue; /* entry without coin signature */
1247 0 : if (0 ==
1248 0 : GNUNET_memcmp (&cs,
1249 : coin_sig))
1250 : {
1251 0 : GNUNET_break_op (0);
1252 0 : return GNUNET_SYSERR;
1253 : }
1254 : }
1255 0 : return GNUNET_OK;
1256 : }
1257 :
1258 :
1259 : #if FIXME_IMPLEMENT /* #9422 */
1260 : /**
1261 : * FIXME-Oec-#9422: we need some specific routines that show
1262 : * that certain coin operations are indeed in conflict,
1263 : * for example that the coin is of a different denomination
1264 : * or different age restrictions.
1265 : * This relates to unimplemented error handling for
1266 : * coins in the exchange!
1267 : *
1268 : * Check that the provided @a proof indeeds indicates
1269 : * a conflict for @a coin_pub.
1270 : *
1271 : * @param keys exchange keys
1272 : * @param proof provided conflict proof
1273 : * @param dk denomination of @a coin_pub that the client
1274 : * used
1275 : * @param coin_pub public key of the coin
1276 : * @param required balance required on the coin for the operation
1277 : * @return #GNUNET_OK if @a proof holds
1278 : */
1279 : // FIXME-#9422: should be properly defined and implemented!
1280 : enum GNUNET_GenericReturnValue
1281 : TALER_EXCHANGE_check_coin_conflict_ (
1282 : const struct TALER_EXCHANGE_Keys *keys,
1283 : const json_t *proof,
1284 : const struct TALER_EXCHANGE_DenomPublicKey *dk,
1285 : const struct TALER_CoinSpendPublicKeyP *coin_pub,
1286 : const struct TALER_Amount *required)
1287 : {
1288 : enum TALER_ErrorCode ec;
1289 :
1290 : ec = TALER_JSON_get_error_code (proof);
1291 : switch (ec)
1292 : {
1293 : case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS:
1294 : /* Nothing to check anymore here, proof needs to be
1295 : checked in the GET /coins/$COIN_PUB handler */
1296 : break;
1297 : case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY:
1298 : // FIXME-#9422: write check!
1299 : break;
1300 : default:
1301 : GNUNET_break_op (0);
1302 : return GNUNET_SYSERR;
1303 : }
1304 : return GNUNET_OK;
1305 : }
1306 :
1307 :
1308 : #endif
1309 :
1310 :
1311 : /* end of exchange_api_get-coins-COIN_PUB-history.c */
|