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