exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

exchange_api_get-coins-COIN_PUB-history.c (38768B)


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