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 (39751B)


      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 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     TALER_JSON_spec_amount_any ("deposit_fee",
    164                                 &rh->details.deposit.deposit_fee),
    165     GNUNET_JSON_spec_fixed_auto ("merchant_pub",
    166                                  &rh->details.deposit.merchant_pub),
    167     GNUNET_JSON_spec_timestamp ("timestamp",
    168                                 &rh->details.deposit.wallet_timestamp),
    169     GNUNET_JSON_spec_mark_optional (
    170       GNUNET_JSON_spec_timestamp ("refund_deadline",
    171                                   &rh->details.deposit.refund_deadline),
    172       NULL),
    173     GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
    174                                  &rh->details.deposit.h_contract_terms),
    175     GNUNET_JSON_spec_fixed_auto ("h_wire",
    176                                  &rh->details.deposit.h_wire),
    177     GNUNET_JSON_spec_mark_optional (
    178       GNUNET_JSON_spec_fixed_auto ("h_policy",
    179                                    &rh->details.deposit.h_policy),
    180       &rh->details.deposit.no_h_policy),
    181     GNUNET_JSON_spec_mark_optional (
    182       GNUNET_JSON_spec_fixed_auto ("wallet_data_hash",
    183                                    &rh->details.deposit.wallet_data_hash),
    184       &rh->details.deposit.no_wallet_data_hash),
    185     GNUNET_JSON_spec_mark_optional (
    186       GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
    187                                    &rh->details.deposit.hac),
    188       &rh->details.deposit.no_hac),
    189     GNUNET_JSON_spec_fixed_auto ("coin_sig",
    190                                  &rh->details.deposit.sig),
    191     GNUNET_JSON_spec_end ()
    192   };
    193 
    194   rh->details.deposit.refund_deadline = GNUNET_TIME_UNIT_ZERO_TS;
    195   if (GNUNET_OK !=
    196       GNUNET_JSON_parse (transaction,
    197                          spec,
    198                          NULL, NULL))
    199   {
    200     GNUNET_break_op (0);
    201     return GNUNET_SYSERR;
    202   }
    203   if (GNUNET_OK !=
    204       TALER_wallet_deposit_verify (
    205         amount,
    206         &rh->details.deposit.deposit_fee,
    207         &rh->details.deposit.h_wire,
    208         &rh->details.deposit.h_contract_terms,
    209         rh->details.deposit.no_wallet_data_hash
    210         ? NULL
    211         : &rh->details.deposit.wallet_data_hash,
    212         rh->details.deposit.no_hac
    213         ? NULL
    214         : &rh->details.deposit.hac,
    215         rh->details.deposit.no_h_policy
    216         ? NULL
    217         : &rh->details.deposit.h_policy,
    218         &pc->dk->h_key,
    219         rh->details.deposit.wallet_timestamp,
    220         &rh->details.deposit.merchant_pub,
    221         rh->details.deposit.refund_deadline,
    222         pc->coin_pub,
    223         &rh->details.deposit.sig))
    224   {
    225     GNUNET_break_op (0);
    226     return GNUNET_SYSERR;
    227   }
    228   /* check that deposit fee matches our expectations from /keys! */
    229   if ( (GNUNET_YES !=
    230         TALER_amount_cmp_currency (&rh->details.deposit.deposit_fee,
    231                                    &pc->dk->fees.deposit)) ||
    232        (0 !=
    233         TALER_amount_cmp (&rh->details.deposit.deposit_fee,
    234                           &pc->dk->fees.deposit)) )
    235   {
    236     GNUNET_break_op (0);
    237     return GNUNET_SYSERR;
    238   }
    239   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 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     TALER_JSON_spec_amount_any ("melt_fee",
    261                                 &rh->details.melt.melt_fee),
    262     GNUNET_JSON_spec_fixed_auto ("rc",
    263                                  &rh->details.melt.rc),
    264     GNUNET_JSON_spec_mark_optional (
    265       GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
    266                                    &rh->details.melt.h_age_commitment),
    267       &rh->details.melt.no_hac),
    268     GNUNET_JSON_spec_fixed_auto ("coin_sig",
    269                                  &rh->details.melt.sig),
    270     GNUNET_JSON_spec_end ()
    271   };
    272 
    273   if (GNUNET_OK !=
    274       GNUNET_JSON_parse (transaction,
    275                          spec,
    276                          NULL, NULL))
    277   {
    278     GNUNET_break_op (0);
    279     return GNUNET_SYSERR;
    280   }
    281 
    282   /* check that melt fee matches our expectations from /keys! */
    283   if ( (GNUNET_YES !=
    284         TALER_amount_cmp_currency (&rh->details.melt.melt_fee,
    285                                    &pc->dk->fees.refresh)) ||
    286        (0 !=
    287         TALER_amount_cmp (&rh->details.melt.melt_fee,
    288                           &pc->dk->fees.refresh)) )
    289   {
    290     GNUNET_break_op (0);
    291     return GNUNET_SYSERR;
    292   }
    293   if (GNUNET_OK !=
    294       TALER_wallet_melt_verify (
    295         amount,
    296         &rh->details.melt.melt_fee,
    297         &rh->details.melt.rc,
    298         &pc->dk->h_key,
    299         rh->details.melt.no_hac
    300         ? NULL
    301         : &rh->details.melt.h_age_commitment,
    302         pc->coin_pub,
    303         &rh->details.melt.sig))
    304   {
    305     GNUNET_break_op (0);
    306     return GNUNET_SYSERR;
    307   }
    308   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 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     TALER_JSON_spec_amount_any ("refund_fee",
    330                                 &rh->details.refund.refund_fee),
    331     GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
    332                                  &rh->details.refund.h_contract_terms),
    333     GNUNET_JSON_spec_fixed_auto ("merchant_pub",
    334                                  &rh->details.refund.merchant_pub),
    335     GNUNET_JSON_spec_uint64 ("rtransaction_id",
    336                              &rh->details.refund.rtransaction_id),
    337     GNUNET_JSON_spec_fixed_auto ("merchant_sig",
    338                                  &rh->details.refund.sig),
    339     GNUNET_JSON_spec_end ()
    340   };
    341 
    342   if (GNUNET_OK !=
    343       GNUNET_JSON_parse (transaction,
    344                          spec,
    345                          NULL, NULL))
    346   {
    347     GNUNET_break_op (0);
    348     return GNUNET_SYSERR;
    349   }
    350   if (0 >
    351       TALER_amount_add (&rh->details.refund.sig_amount,
    352                         &rh->details.refund.refund_fee,
    353                         amount))
    354   {
    355     GNUNET_break_op (0);
    356     return GNUNET_SYSERR;
    357   }
    358   if (GNUNET_OK !=
    359       TALER_merchant_refund_verify (pc->coin_pub,
    360                                     &rh->details.refund.h_contract_terms,
    361                                     rh->details.refund.rtransaction_id,
    362                                     &rh->details.refund.sig_amount,
    363                                     &rh->details.refund.merchant_pub,
    364                                     &rh->details.refund.sig))
    365   {
    366     GNUNET_break_op (0);
    367     return GNUNET_SYSERR;
    368   }
    369   /* check that refund fee matches our expectations from /keys! */
    370   if ( (GNUNET_YES !=
    371         TALER_amount_cmp_currency (&rh->details.refund.refund_fee,
    372                                    &pc->dk->fees.refund)) ||
    373        (0 !=
    374         TALER_amount_cmp (&rh->details.refund.refund_fee,
    375                           &pc->dk->fees.refund)) )
    376   {
    377     GNUNET_break_op (0);
    378     return GNUNET_SYSERR;
    379   }
    380   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 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     GNUNET_JSON_spec_fixed_auto ("exchange_sig",
    402                                  &rh->details.recoup.exchange_sig),
    403     GNUNET_JSON_spec_fixed_auto ("exchange_pub",
    404                                  &rh->details.recoup.exchange_pub),
    405     GNUNET_JSON_spec_fixed_auto ("reserve_pub",
    406                                  &rh->details.recoup.reserve_pub),
    407     GNUNET_JSON_spec_fixed_auto ("coin_sig",
    408                                  &rh->details.recoup.coin_sig),
    409     GNUNET_JSON_spec_fixed_auto ("coin_blind",
    410                                  &rh->details.recoup.coin_bks),
    411     GNUNET_JSON_spec_timestamp ("timestamp",
    412                                 &rh->details.recoup.timestamp),
    413     GNUNET_JSON_spec_end ()
    414   };
    415 
    416   if (GNUNET_OK !=
    417       GNUNET_JSON_parse (transaction,
    418                          spec,
    419                          NULL, NULL))
    420   {
    421     GNUNET_break_op (0);
    422     return GNUNET_SYSERR;
    423   }
    424   if (GNUNET_OK !=
    425       TALER_EXCHANGE_test_signing_key (pc->keys,
    426                                        &rh->details.recoup.exchange_pub))
    427   {
    428     GNUNET_break_op (0);
    429     return GNUNET_SYSERR;
    430   }
    431   if (GNUNET_OK !=
    432       TALER_exchange_online_confirm_recoup_verify (
    433         rh->details.recoup.timestamp,
    434         amount,
    435         pc->coin_pub,
    436         &rh->details.recoup.reserve_pub,
    437         &rh->details.recoup.exchange_pub,
    438         &rh->details.recoup.exchange_sig))
    439   {
    440     GNUNET_break_op (0);
    441     return GNUNET_SYSERR;
    442   }
    443   if (GNUNET_OK !=
    444       TALER_wallet_recoup_verify (&pc->dk->h_key,
    445                                   &rh->details.recoup.coin_bks,
    446                                   pc->coin_pub,
    447                                   &rh->details.recoup.coin_sig))
    448   {
    449     GNUNET_break_op (0);
    450     return GNUNET_SYSERR;
    451   }
    452   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 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     GNUNET_JSON_spec_fixed_auto ("exchange_sig",
    476                                  &rh->details.recoup_refresh.exchange_sig),
    477     GNUNET_JSON_spec_fixed_auto ("exchange_pub",
    478                                  &rh->details.recoup_refresh.exchange_pub),
    479     GNUNET_JSON_spec_fixed_auto ("coin_sig",
    480                                  &rh->details.recoup_refresh.coin_sig),
    481     GNUNET_JSON_spec_fixed_auto ("old_coin_pub",
    482                                  &rh->details.recoup_refresh.old_coin_pub),
    483     GNUNET_JSON_spec_fixed_auto ("coin_blind",
    484                                  &rh->details.recoup_refresh.coin_bks),
    485     GNUNET_JSON_spec_timestamp ("timestamp",
    486                                 &rh->details.recoup_refresh.timestamp),
    487     GNUNET_JSON_spec_end ()
    488   };
    489 
    490   if (GNUNET_OK !=
    491       GNUNET_JSON_parse (transaction,
    492                          spec,
    493                          NULL, NULL))
    494   {
    495     GNUNET_break_op (0);
    496     return GNUNET_SYSERR;
    497   }
    498   if (GNUNET_OK !=
    499       TALER_EXCHANGE_test_signing_key (
    500         pc->keys,
    501         &rh->details.recoup_refresh.exchange_pub))
    502   {
    503     GNUNET_break_op (0);
    504     return GNUNET_SYSERR;
    505   }
    506   if (GNUNET_OK !=
    507       TALER_exchange_online_confirm_recoup_refresh_verify (
    508         rh->details.recoup_refresh.timestamp,
    509         amount,
    510         pc->coin_pub,
    511         &rh->details.recoup_refresh.old_coin_pub,
    512         &rh->details.recoup_refresh.exchange_pub,
    513         &rh->details.recoup_refresh.exchange_sig))
    514   {
    515     GNUNET_break_op (0);
    516     return GNUNET_SYSERR;
    517   }
    518   if (GNUNET_OK !=
    519       TALER_wallet_recoup_verify (&pc->dk->h_key,
    520                                   &rh->details.recoup_refresh.coin_bks,
    521                                   pc->coin_pub,
    522                                   &rh->details.recoup_refresh.coin_sig))
    523   {
    524     GNUNET_break_op (0);
    525     return GNUNET_SYSERR;
    526   }
    527   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 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     GNUNET_JSON_spec_fixed_auto ("exchange_sig",
    551                                  &rh->details.old_coin_recoup.exchange_sig),
    552     GNUNET_JSON_spec_fixed_auto ("exchange_pub",
    553                                  &rh->details.old_coin_recoup.exchange_pub),
    554     GNUNET_JSON_spec_fixed_auto ("coin_pub",
    555                                  &rh->details.old_coin_recoup.new_coin_pub),
    556     GNUNET_JSON_spec_timestamp ("timestamp",
    557                                 &rh->details.old_coin_recoup.timestamp),
    558     GNUNET_JSON_spec_end ()
    559   };
    560 
    561   if (GNUNET_OK !=
    562       GNUNET_JSON_parse (transaction,
    563                          spec,
    564                          NULL, NULL))
    565   {
    566     GNUNET_break_op (0);
    567     return GNUNET_SYSERR;
    568   }
    569   if (GNUNET_OK !=
    570       TALER_EXCHANGE_test_signing_key (
    571         pc->keys,
    572         &rh->details.old_coin_recoup.exchange_pub))
    573   {
    574     GNUNET_break_op (0);
    575     return GNUNET_SYSERR;
    576   }
    577   if (GNUNET_OK !=
    578       TALER_exchange_online_confirm_recoup_refresh_verify (
    579         rh->details.old_coin_recoup.timestamp,
    580         amount,
    581         &rh->details.old_coin_recoup.new_coin_pub,
    582         pc->coin_pub,
    583         &rh->details.old_coin_recoup.exchange_pub,
    584         &rh->details.old_coin_recoup.exchange_sig))
    585   {
    586     GNUNET_break_op (0);
    587     return GNUNET_SYSERR;
    588   }
    589   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 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     TALER_JSON_spec_web_url ("exchange_base_url",
    611                              &rh->details.purse_deposit.exchange_base_url),
    612     GNUNET_JSON_spec_mark_optional (
    613       GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
    614                                    &rh->details.purse_deposit.phac),
    615       NULL),
    616     GNUNET_JSON_spec_fixed_auto ("purse_pub",
    617                                  &rh->details.purse_deposit.purse_pub),
    618     GNUNET_JSON_spec_bool ("refunded",
    619                            &rh->details.purse_deposit.refunded),
    620     GNUNET_JSON_spec_fixed_auto ("coin_sig",
    621                                  &rh->details.purse_deposit.coin_sig),
    622     GNUNET_JSON_spec_end ()
    623   };
    624 
    625   if (GNUNET_OK !=
    626       GNUNET_JSON_parse (transaction,
    627                          spec,
    628                          NULL, NULL))
    629   {
    630     GNUNET_break_op (0);
    631     return GNUNET_SYSERR;
    632   }
    633   if (GNUNET_OK !=
    634       TALER_wallet_purse_deposit_verify (
    635         rh->details.purse_deposit.exchange_base_url,
    636         &rh->details.purse_deposit.purse_pub,
    637         amount,
    638         &pc->dk->h_key,
    639         &rh->details.purse_deposit.phac,
    640         pc->coin_pub,
    641         &rh->details.purse_deposit.coin_sig))
    642   {
    643     GNUNET_break_op (0);
    644     return GNUNET_SYSERR;
    645   }
    646   if (rh->details.purse_deposit.refunded)
    647   {
    648     /* We wave the deposit fee. */
    649     if (0 >
    650         TALER_amount_add (pc->total_in,
    651                           pc->total_in,
    652                           &pc->dk->fees.deposit))
    653     {
    654       /* overflow in refund history? inconceivable! Bad exchange! */
    655       GNUNET_break_op (0);
    656       return GNUNET_SYSERR;
    657     }
    658   }
    659   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 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     TALER_JSON_spec_amount_any ("refund_fee",
    681                                 &rh->details.purse_refund.refund_fee),
    682     GNUNET_JSON_spec_fixed_auto ("exchange_sig",
    683                                  &rh->details.purse_refund.exchange_sig),
    684     GNUNET_JSON_spec_fixed_auto ("exchange_pub",
    685                                  &rh->details.purse_refund.exchange_pub),
    686     GNUNET_JSON_spec_fixed_auto ("purse_pub",
    687                                  &rh->details.purse_refund.purse_pub),
    688     GNUNET_JSON_spec_end ()
    689   };
    690 
    691   if (GNUNET_OK !=
    692       GNUNET_JSON_parse (transaction,
    693                          spec,
    694                          NULL, NULL))
    695   {
    696     GNUNET_break_op (0);
    697     return GNUNET_SYSERR;
    698   }
    699   if (GNUNET_OK !=
    700       TALER_EXCHANGE_test_signing_key (
    701         pc->keys,
    702         &rh->details.purse_refund.exchange_pub))
    703   {
    704     GNUNET_break_op (0);
    705     return GNUNET_SYSERR;
    706   }
    707   if (GNUNET_OK !=
    708       TALER_exchange_online_purse_refund_verify (
    709         amount,
    710         &rh->details.purse_refund.refund_fee,
    711         pc->coin_pub,
    712         &rh->details.purse_refund.purse_pub,
    713         &rh->details.purse_refund.exchange_pub,
    714         &rh->details.purse_refund.exchange_sig))
    715   {
    716     GNUNET_break_op (0);
    717     return GNUNET_SYSERR;
    718   }
    719   if ( (GNUNET_YES !=
    720         TALER_amount_cmp_currency (&rh->details.purse_refund.refund_fee,
    721                                    &pc->dk->fees.refund)) ||
    722        (0 !=
    723         TALER_amount_cmp (&rh->details.purse_refund.refund_fee,
    724                           &pc->dk->fees.refund)) )
    725   {
    726     GNUNET_break_op (0);
    727     return GNUNET_SYSERR;
    728   }
    729   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 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     GNUNET_JSON_spec_fixed_auto (
    751       "reserve_sig",
    752       &rh->details.reserve_open_deposit.reserve_sig),
    753     GNUNET_JSON_spec_fixed_auto (
    754       "coin_sig",
    755       &rh->details.reserve_open_deposit.coin_sig),
    756     TALER_JSON_spec_amount_any (
    757       "coin_contribution",
    758       &rh->details.reserve_open_deposit.coin_contribution),
    759     GNUNET_JSON_spec_mark_optional (
    760       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     GNUNET_JSON_spec_end ()
    765   };
    766 
    767   if (GNUNET_OK !=
    768       GNUNET_JSON_parse (transaction,
    769                          spec,
    770                          NULL, NULL))
    771   {
    772     GNUNET_break_op (0);
    773     return GNUNET_SYSERR;
    774   }
    775   if (GNUNET_OK !=
    776       TALER_wallet_reserve_open_deposit_verify (
    777         amount,
    778         &pc->dk->h_key,
    779         rh->details.reserve_open_deposit.no_hac
    780         ? NULL
    781         : &rh->details.reserve_open_deposit.h_age_commitment,
    782         &rh->details.reserve_open_deposit.reserve_sig,
    783         pc->coin_pub,
    784         &rh->details.reserve_open_deposit.coin_sig))
    785   {
    786     GNUNET_break_op (0);
    787     return GNUNET_SYSERR;
    788   }
    789   return GNUNET_YES;
    790 }
    791 
    792 
    793 enum GNUNET_GenericReturnValue
    794 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 {
    804   const struct
    805   {
    806     const char *type;
    807     CoinCheckHelper helper;
    808     enum TALER_EXCHANGE_CoinTransactionType ctt;
    809   } 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   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   if (NULL == history)
    849   {
    850     GNUNET_break_op (0);
    851     return GNUNET_SYSERR;
    852   }
    853   len = json_array_size (history);
    854   if (0 == len)
    855   {
    856     GNUNET_break_op (0);
    857     return GNUNET_SYSERR;
    858   }
    859   *total_in = dk->value;
    860   GNUNET_assert (GNUNET_OK ==
    861                  TALER_amount_set_zero (total_in->currency,
    862                                         total_out));
    863   for (size_t off = 0; off < len; off++)
    864   {
    865     struct TALER_EXCHANGE_CoinHistoryEntry *rh = &rhistory[off];
    866     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       TALER_JSON_spec_amount_any ("amount",
    872                                   &rh->amount),
    873       GNUNET_JSON_spec_string ("type",
    874                                &type),
    875       GNUNET_JSON_spec_uint64 ("history_offset",
    876                                &rh->history_offset),
    877       GNUNET_JSON_spec_end ()
    878     };
    879 
    880     if (GNUNET_OK !=
    881         GNUNET_JSON_parse (transaction,
    882                            spec_glob,
    883                            NULL, NULL))
    884     {
    885       GNUNET_break_op (0);
    886       return GNUNET_SYSERR;
    887     }
    888     if (GNUNET_YES !=
    889         TALER_amount_cmp_currency (&rh->amount,
    890                                    total_in))
    891     {
    892       GNUNET_break_op (0);
    893       return GNUNET_SYSERR;
    894     }
    895     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    896                 "Operation of type %s with amount %s\n",
    897                 type,
    898                 TALER_amount2s (&rh->amount));
    899     add = GNUNET_SYSERR;
    900     for (unsigned int i = 0; NULL != map[i].type; i++)
    901     {
    902       if (0 == strcasecmp (type,
    903                            map[i].type))
    904       {
    905         rh->type = map[i].ctt;
    906         add = map[i].helper (&pc,
    907                              rh,
    908                              &rh->amount,
    909                              transaction);
    910         break;
    911       }
    912     }
    913     switch (add)
    914     {
    915     case GNUNET_SYSERR:
    916       /* entry type not supported, new version on server? */
    917       rh->type = TALER_EXCHANGE_CTT_NONE;
    918       GNUNET_break_op (0);
    919       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    920                   "Unexpected type `%s' in response\n",
    921                   type);
    922       return GNUNET_SYSERR;
    923     case GNUNET_YES:
    924       /* This amount should be debited from the coin */
    925       if (0 >
    926           TALER_amount_add (total_out,
    927                             total_out,
    928                             &rh->amount))
    929       {
    930         /* overflow in history already!? inconceivable! Bad exchange! */
    931         GNUNET_break_op (0);
    932         return GNUNET_SYSERR;
    933       }
    934       break;
    935     case GNUNET_NO:
    936       /* This amount should be credited to the coin. */
    937       if (0 >
    938           TALER_amount_add (total_in,
    939                             total_in,
    940                             &rh->amount))
    941       {
    942         /* overflow in refund history? inconceivable! Bad exchange! */
    943         GNUNET_break_op (0);
    944         return GNUNET_SYSERR;
    945       }
    946       break;
    947     } /* end of switch(add) */
    948   }
    949   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 handle_coins_history_ok (struct TALER_EXCHANGE_GetCoinsHistoryHandle *gcsh,
    962                          const json_t *j)
    963 {
    964   struct TALER_EXCHANGE_GetCoinsHistoryResponse rs = {
    965     .hr.reply = j,
    966     .hr.http_status = MHD_HTTP_OK
    967   };
    968   struct GNUNET_JSON_Specification spec[] = {
    969     TALER_JSON_spec_amount_any ("balance",
    970                                 &rs.details.ok.balance),
    971     GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
    972                                  &rs.details.ok.h_denom_pub),
    973     GNUNET_JSON_spec_array_const ("history",
    974                                   &rs.details.ok.history),
    975     GNUNET_JSON_spec_end ()
    976   };
    977 
    978   if (GNUNET_OK !=
    979       GNUNET_JSON_parse (j,
    980                          spec,
    981                          NULL,
    982                          NULL))
    983   {
    984     GNUNET_break_op (0);
    985     return GNUNET_SYSERR;
    986   }
    987   if (NULL != gcsh->cb)
    988   {
    989     gcsh->cb (gcsh->cb_cls,
    990               &rs);
    991     gcsh->cb = NULL;
    992   }
    993   GNUNET_JSON_parse_free (spec);
    994   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 handle_coins_history_finished (void *cls,
   1008                                long response_code,
   1009                                const void *response)
   1010 {
   1011   struct TALER_EXCHANGE_GetCoinsHistoryHandle *gcsh = cls;
   1012   const json_t *j = response;
   1013   struct TALER_EXCHANGE_GetCoinsHistoryResponse rs = {
   1014     .hr.reply = j,
   1015     .hr.http_status = (unsigned int) response_code
   1016   };
   1017 
   1018   gcsh->job = NULL;
   1019   switch (response_code)
   1020   {
   1021   case 0:
   1022     rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
   1023     break;
   1024   case MHD_HTTP_OK:
   1025     if (GNUNET_OK !=
   1026         handle_coins_history_ok (gcsh,
   1027                                  j))
   1028     {
   1029       rs.hr.http_status = 0;
   1030       rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
   1031     }
   1032     break;
   1033   case MHD_HTTP_NO_CONTENT:
   1034     break;
   1035   case MHD_HTTP_NOT_MODIFIED:
   1036     break;
   1037   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     GNUNET_break (0);
   1041     rs.hr.ec = TALER_JSON_get_error_code (j);
   1042     rs.hr.hint = TALER_JSON_get_error_hint (j);
   1043     break;
   1044   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     GNUNET_break (0);
   1048     rs.hr.ec = TALER_JSON_get_error_code (j);
   1049     rs.hr.hint = TALER_JSON_get_error_hint (j);
   1050     break;
   1051   case MHD_HTTP_NOT_FOUND:
   1052     rs.hr.ec = TALER_JSON_get_error_code (j);
   1053     rs.hr.hint = TALER_JSON_get_error_hint (j);
   1054     break;
   1055   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     rs.hr.ec = TALER_JSON_get_error_code (j);
   1059     rs.hr.hint = TALER_JSON_get_error_hint (j);
   1060     break;
   1061   default:
   1062     /* unexpected response code */
   1063     GNUNET_break_op (0);
   1064     rs.hr.ec = TALER_JSON_get_error_code (j);
   1065     rs.hr.hint = TALER_JSON_get_error_hint (j);
   1066     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     break;
   1071   }
   1072   if (NULL != gcsh->cb)
   1073   {
   1074     gcsh->cb (gcsh->cb_cls,
   1075               &rs);
   1076     gcsh->cb = NULL;
   1077   }
   1078   TALER_EXCHANGE_get_coins_history_cancel (gcsh);
   1079 }
   1080 
   1081 
   1082 struct TALER_EXCHANGE_GetCoinsHistoryHandle *
   1083 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   gcsh = GNUNET_new (struct TALER_EXCHANGE_GetCoinsHistoryHandle);
   1091   gcsh->ctx = ctx;
   1092   gcsh->base_url = GNUNET_strdup (url);
   1093   gcsh->coin_priv = *coin_priv;
   1094   GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
   1095                                       &gcsh->coin_pub.eddsa_pub);
   1096   gcsh->options.start_off = 0;
   1097   return gcsh;
   1098 }
   1099 
   1100 
   1101 enum GNUNET_GenericReturnValue
   1102 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   for (unsigned int i = 0; i < num_options; i++)
   1108   {
   1109     const struct TALER_EXCHANGE_GetCoinsHistoryOptionValue *opt = &options[i];
   1110 
   1111     switch (opt->option)
   1112     {
   1113     case TALER_EXCHANGE_GET_COINS_HISTORY_OPTION_END:
   1114       return GNUNET_OK;
   1115     case TALER_EXCHANGE_GET_COINS_HISTORY_OPTION_START_OFF:
   1116       gcsh->options.start_off = opt->details.start_off;
   1117       break;
   1118     }
   1119   }
   1120   return GNUNET_OK;
   1121 }
   1122 
   1123 
   1124 enum TALER_ErrorCode
   1125 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   if (NULL != gcsh->job)
   1135   {
   1136     GNUNET_break (0);
   1137     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
   1138   }
   1139   gcsh->cb = cb;
   1140   gcsh->cb_cls = cb_cls;
   1141   {
   1142     char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
   1143     char *end;
   1144 
   1145     end = GNUNET_STRINGS_data_to_string (
   1146       &gcsh->coin_pub,
   1147       sizeof (gcsh->coin_pub),
   1148       pub_str,
   1149       sizeof (pub_str));
   1150     *end = '\0';
   1151     if (0 != gcsh->options.start_off)
   1152       GNUNET_snprintf (arg_str,
   1153                        sizeof (arg_str),
   1154                        "coins/%s/history?start=%llu",
   1155                        pub_str,
   1156                        (unsigned long long) gcsh->options.start_off);
   1157     else
   1158       GNUNET_snprintf (arg_str,
   1159                        sizeof (arg_str),
   1160                        "coins/%s/history",
   1161                        pub_str);
   1162   }
   1163   gcsh->url = TALER_url_join (gcsh->base_url,
   1164                               arg_str,
   1165                               NULL);
   1166   if (NULL == gcsh->url)
   1167     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
   1168   eh = TALER_EXCHANGE_curl_easy_get_ (gcsh->url);
   1169   if (NULL == eh)
   1170     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
   1171   {
   1172     struct TALER_CoinSpendSignatureP coin_sig;
   1173     char *sig_hdr;
   1174     char *hdr;
   1175 
   1176     TALER_wallet_coin_history_sign (gcsh->options.start_off,
   1177                                     &gcsh->coin_priv,
   1178                                     &coin_sig);
   1179     sig_hdr = GNUNET_STRINGS_data_to_string_alloc (
   1180       &coin_sig,
   1181       sizeof (coin_sig));
   1182     GNUNET_asprintf (&hdr,
   1183                      "%s: %s",
   1184                      TALER_COIN_HISTORY_SIGNATURE_HEADER,
   1185                      sig_hdr);
   1186     GNUNET_free (sig_hdr);
   1187     job_headers = curl_slist_append (NULL,
   1188                                      hdr);
   1189     GNUNET_free (hdr);
   1190     if (NULL == job_headers)
   1191     {
   1192       GNUNET_break (0);
   1193       curl_easy_cleanup (eh);
   1194       GNUNET_free (gcsh->url);
   1195       return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
   1196     }
   1197   }
   1198   gcsh->job = GNUNET_CURL_job_add2 (gcsh->ctx,
   1199                                     eh,
   1200                                     job_headers,
   1201                                     &handle_coins_history_finished,
   1202                                     gcsh);
   1203   curl_slist_free_all (job_headers);
   1204   if (NULL == gcsh->job)
   1205     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
   1206   return TALER_EC_NONE;
   1207 }
   1208 
   1209 
   1210 void
   1211 TALER_EXCHANGE_get_coins_history_cancel (
   1212   struct TALER_EXCHANGE_GetCoinsHistoryHandle *gcsh)
   1213 {
   1214   if (NULL != gcsh->job)
   1215   {
   1216     GNUNET_CURL_job_cancel (gcsh->job);
   1217     gcsh->job = NULL;
   1218   }
   1219   GNUNET_free (gcsh->url);
   1220   GNUNET_free (gcsh->base_url);
   1221   GNUNET_free (gcsh);
   1222 }
   1223 
   1224 
   1225 enum GNUNET_GenericReturnValue
   1226 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   json_array_foreach (history, off, entry)
   1234   {
   1235     struct TALER_CoinSpendSignatureP cs;
   1236     struct GNUNET_JSON_Specification spec[] = {
   1237       GNUNET_JSON_spec_fixed_auto ("coin_sig",
   1238                                    &cs),
   1239       GNUNET_JSON_spec_end ()
   1240     };
   1241 
   1242     if (GNUNET_OK !=
   1243         GNUNET_JSON_parse (entry,
   1244                            spec,
   1245                            NULL, NULL))
   1246       continue; /* entry without coin signature */
   1247     if (0 ==
   1248         GNUNET_memcmp (&cs,
   1249                        coin_sig))
   1250     {
   1251       GNUNET_break_op (0);
   1252       return GNUNET_SYSERR;
   1253     }
   1254   }
   1255   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 */