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


      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_blinding_secret",
    410                                  &rh->details.recoup.coin_bks),
    411     GNUNET_JSON_spec_fixed_auto ("planchets_h",
    412                                  &rh->details.recoup.planchets_h),
    413     GNUNET_JSON_spec_uint32 ("coin_index",
    414                              &rh->details.recoup.coin_index),
    415     GNUNET_JSON_spec_timestamp ("timestamp",
    416                                 &rh->details.recoup.timestamp),
    417     GNUNET_JSON_spec_end ()
    418   };
    419 
    420   if (GNUNET_OK !=
    421       GNUNET_JSON_parse (transaction,
    422                          spec,
    423                          NULL, NULL))
    424   {
    425     GNUNET_break_op (0);
    426     return GNUNET_SYSERR;
    427   }
    428   if (GNUNET_OK !=
    429       TALER_EXCHANGE_test_signing_key (pc->keys,
    430                                        &rh->details.recoup.exchange_pub))
    431   {
    432     GNUNET_break_op (0);
    433     return GNUNET_SYSERR;
    434   }
    435   if (GNUNET_OK !=
    436       TALER_exchange_online_confirm_recoup_verify (
    437         rh->details.recoup.timestamp,
    438         amount,
    439         pc->coin_pub,
    440         &rh->details.recoup.reserve_pub,
    441         &rh->details.recoup.exchange_pub,
    442         &rh->details.recoup.exchange_sig))
    443   {
    444     GNUNET_break_op (0);
    445     return GNUNET_SYSERR;
    446   }
    447   if (GNUNET_OK !=
    448       TALER_wallet_recoup_verify (&pc->dk->h_key,
    449                                   &rh->details.recoup.coin_bks,
    450                                   pc->coin_pub,
    451                                   &rh->details.recoup.coin_sig))
    452   {
    453     GNUNET_break_op (0);
    454     return GNUNET_SYSERR;
    455   }
    456   return GNUNET_YES;
    457 }
    458 
    459 
    460 /**
    461  * Handle recoup-refresh entry in the coin's history.
    462  * This is the coin that was subjected to a recoup,
    463  * the value being credited to the old coin.
    464  *
    465  * @param[in,out] pc overall context
    466  * @param[out] rh history entry to initialize
    467  * @param amount main amount of this operation
    468  * @param transaction JSON details for the operation
    469  * @return #GNUNET_SYSERR on error,
    470  *         #GNUNET_OK to add, #GNUNET_NO to subtract
    471  */
    472 static enum GNUNET_GenericReturnValue
    473 help_recoup_refresh (struct CoinHistoryParseContext *pc,
    474                      struct TALER_EXCHANGE_CoinHistoryEntry *rh,
    475                      const struct TALER_Amount *amount,
    476                      json_t *transaction)
    477 {
    478   struct GNUNET_JSON_Specification spec[] = {
    479     GNUNET_JSON_spec_fixed_auto ("exchange_sig",
    480                                  &rh->details.recoup_refresh.exchange_sig),
    481     GNUNET_JSON_spec_fixed_auto ("exchange_pub",
    482                                  &rh->details.recoup_refresh.exchange_pub),
    483     GNUNET_JSON_spec_fixed_auto ("coin_sig",
    484                                  &rh->details.recoup_refresh.coin_sig),
    485     GNUNET_JSON_spec_fixed_auto ("old_coin_pub",
    486                                  &rh->details.recoup_refresh.old_coin_pub),
    487     GNUNET_JSON_spec_fixed_auto ("coin_blinding_secret",
    488                                  &rh->details.recoup_refresh.coin_bks),
    489     GNUNET_JSON_spec_fixed_auto ("rc",
    490                                  &rh->details.recoup_refresh.rc),
    491     GNUNET_JSON_spec_uint32 ("coin_index",
    492                              &rh->details.recoup_refresh.coin_index),
    493     GNUNET_JSON_spec_timestamp ("timestamp",
    494                                 &rh->details.recoup_refresh.timestamp),
    495     GNUNET_JSON_spec_end ()
    496   };
    497 
    498   if (GNUNET_OK !=
    499       GNUNET_JSON_parse (transaction,
    500                          spec,
    501                          NULL, NULL))
    502   {
    503     GNUNET_break_op (0);
    504     return GNUNET_SYSERR;
    505   }
    506   if (GNUNET_OK !=
    507       TALER_EXCHANGE_test_signing_key (
    508         pc->keys,
    509         &rh->details.recoup_refresh.exchange_pub))
    510   {
    511     GNUNET_break_op (0);
    512     return GNUNET_SYSERR;
    513   }
    514   if (GNUNET_OK !=
    515       TALER_exchange_online_confirm_recoup_refresh_verify (
    516         rh->details.recoup_refresh.timestamp,
    517         amount,
    518         pc->coin_pub,
    519         &rh->details.recoup_refresh.old_coin_pub,
    520         &rh->details.recoup_refresh.exchange_pub,
    521         &rh->details.recoup_refresh.exchange_sig))
    522   {
    523     GNUNET_break_op (0);
    524     return GNUNET_SYSERR;
    525   }
    526   if (GNUNET_OK !=
    527       TALER_wallet_recoup_refresh_verify (&pc->dk->h_key,
    528                                           &rh->details.recoup_refresh.coin_bks,
    529                                           pc->coin_pub,
    530                                           &rh->details.recoup_refresh.coin_sig))
    531   {
    532     GNUNET_break_op (0);
    533     return GNUNET_SYSERR;
    534   }
    535   return GNUNET_YES;
    536 }
    537 
    538 
    539 /**
    540  * Handle old coin recoup entry in the coin's history.
    541  * This is the coin that was credited in a recoup,
    542  * the value being credited to this coin.
    543  *
    544  * @param[in,out] pc overall context
    545  * @param[out] rh history entry to initialize
    546  * @param amount main amount of this operation
    547  * @param transaction JSON details for the operation
    548  * @return #GNUNET_SYSERR on error,
    549  *         #GNUNET_OK to add, #GNUNET_NO to subtract
    550  */
    551 static enum GNUNET_GenericReturnValue
    552 help_old_coin_recoup (struct CoinHistoryParseContext *pc,
    553                       struct TALER_EXCHANGE_CoinHistoryEntry *rh,
    554                       const struct TALER_Amount *amount,
    555                       json_t *transaction)
    556 {
    557   struct GNUNET_JSON_Specification spec[] = {
    558     GNUNET_JSON_spec_fixed_auto ("exchange_sig",
    559                                  &rh->details.old_coin_recoup.exchange_sig),
    560     GNUNET_JSON_spec_fixed_auto ("exchange_pub",
    561                                  &rh->details.old_coin_recoup.exchange_pub),
    562     GNUNET_JSON_spec_fixed_auto ("coin_pub",
    563                                  &rh->details.old_coin_recoup.new_coin_pub),
    564     GNUNET_JSON_spec_fixed_auto ("rc",
    565                                  &rh->details.old_coin_recoup.rc),
    566     GNUNET_JSON_spec_timestamp ("timestamp",
    567                                 &rh->details.old_coin_recoup.timestamp),
    568     GNUNET_JSON_spec_end ()
    569   };
    570 
    571   if (GNUNET_OK !=
    572       GNUNET_JSON_parse (transaction,
    573                          spec,
    574                          NULL, NULL))
    575   {
    576     GNUNET_break_op (0);
    577     return GNUNET_SYSERR;
    578   }
    579   if (GNUNET_OK !=
    580       TALER_EXCHANGE_test_signing_key (
    581         pc->keys,
    582         &rh->details.old_coin_recoup.exchange_pub))
    583   {
    584     GNUNET_break_op (0);
    585     return GNUNET_SYSERR;
    586   }
    587   if (GNUNET_OK !=
    588       TALER_exchange_online_confirm_recoup_refresh_verify (
    589         rh->details.old_coin_recoup.timestamp,
    590         amount,
    591         &rh->details.old_coin_recoup.new_coin_pub,
    592         pc->coin_pub,
    593         &rh->details.old_coin_recoup.exchange_pub,
    594         &rh->details.old_coin_recoup.exchange_sig))
    595   {
    596     GNUNET_break_op (0);
    597     return GNUNET_SYSERR;
    598   }
    599   return GNUNET_NO;
    600 }
    601 
    602 
    603 /**
    604  * Handle purse deposit entry in the coin's history.
    605  *
    606  * @param[in,out] pc overall context
    607  * @param[out] rh history entry to initialize
    608  * @param amount main amount of this operation
    609  * @param transaction JSON details for the operation
    610  * @return #GNUNET_SYSERR on error,
    611  *         #GNUNET_OK to add, #GNUNET_NO to subtract
    612  */
    613 static enum GNUNET_GenericReturnValue
    614 help_purse_deposit (struct CoinHistoryParseContext *pc,
    615                     struct TALER_EXCHANGE_CoinHistoryEntry *rh,
    616                     const struct TALER_Amount *amount,
    617                     json_t *transaction)
    618 {
    619   struct GNUNET_JSON_Specification spec[] = {
    620     TALER_JSON_spec_web_url ("exchange_base_url",
    621                              &rh->details.purse_deposit.exchange_base_url),
    622     GNUNET_JSON_spec_mark_optional (
    623       GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
    624                                    &rh->details.purse_deposit.phac),
    625       NULL),
    626     GNUNET_JSON_spec_fixed_auto ("purse_pub",
    627                                  &rh->details.purse_deposit.purse_pub),
    628     GNUNET_JSON_spec_bool ("refunded",
    629                            &rh->details.purse_deposit.refunded),
    630     GNUNET_JSON_spec_fixed_auto ("coin_sig",
    631                                  &rh->details.purse_deposit.coin_sig),
    632     GNUNET_JSON_spec_end ()
    633   };
    634 
    635   if (GNUNET_OK !=
    636       GNUNET_JSON_parse (transaction,
    637                          spec,
    638                          NULL, NULL))
    639   {
    640     GNUNET_break_op (0);
    641     return GNUNET_SYSERR;
    642   }
    643   if (GNUNET_OK !=
    644       TALER_wallet_purse_deposit_verify (
    645         rh->details.purse_deposit.exchange_base_url,
    646         &rh->details.purse_deposit.purse_pub,
    647         amount,
    648         &pc->dk->h_key,
    649         &rh->details.purse_deposit.phac,
    650         pc->coin_pub,
    651         &rh->details.purse_deposit.coin_sig))
    652   {
    653     GNUNET_break_op (0);
    654     return GNUNET_SYSERR;
    655   }
    656   if (rh->details.purse_deposit.refunded)
    657   {
    658     /* We wave the deposit fee. */
    659     if (0 >
    660         TALER_amount_add (pc->total_in,
    661                           pc->total_in,
    662                           &pc->dk->fees.deposit))
    663     {
    664       /* overflow in refund history? inconceivable! Bad exchange! */
    665       GNUNET_break_op (0);
    666       return GNUNET_SYSERR;
    667     }
    668   }
    669   return GNUNET_YES;
    670 }
    671 
    672 
    673 /**
    674  * Handle purse refund entry in the coin's history.
    675  *
    676  * @param[in,out] pc overall context
    677  * @param[out] rh history entry to initialize
    678  * @param amount main amount of this operation
    679  * @param transaction JSON details for the operation
    680  * @return #GNUNET_SYSERR on error,
    681  *         #GNUNET_OK to add, #GNUNET_NO to subtract
    682  */
    683 static enum GNUNET_GenericReturnValue
    684 help_purse_refund (struct CoinHistoryParseContext *pc,
    685                    struct TALER_EXCHANGE_CoinHistoryEntry *rh,
    686                    const struct TALER_Amount *amount,
    687                    json_t *transaction)
    688 {
    689   struct GNUNET_JSON_Specification spec[] = {
    690     TALER_JSON_spec_amount_any ("refund_fee",
    691                                 &rh->details.purse_refund.refund_fee),
    692     GNUNET_JSON_spec_fixed_auto ("exchange_sig",
    693                                  &rh->details.purse_refund.exchange_sig),
    694     GNUNET_JSON_spec_fixed_auto ("exchange_pub",
    695                                  &rh->details.purse_refund.exchange_pub),
    696     GNUNET_JSON_spec_fixed_auto ("purse_pub",
    697                                  &rh->details.purse_refund.purse_pub),
    698     GNUNET_JSON_spec_end ()
    699   };
    700 
    701   if (GNUNET_OK !=
    702       GNUNET_JSON_parse (transaction,
    703                          spec,
    704                          NULL, NULL))
    705   {
    706     GNUNET_break_op (0);
    707     return GNUNET_SYSERR;
    708   }
    709   if (GNUNET_OK !=
    710       TALER_EXCHANGE_test_signing_key (
    711         pc->keys,
    712         &rh->details.purse_refund.exchange_pub))
    713   {
    714     GNUNET_break_op (0);
    715     return GNUNET_SYSERR;
    716   }
    717   if (GNUNET_OK !=
    718       TALER_exchange_online_purse_refund_verify (
    719         amount,
    720         &rh->details.purse_refund.refund_fee,
    721         pc->coin_pub,
    722         &rh->details.purse_refund.purse_pub,
    723         &rh->details.purse_refund.exchange_pub,
    724         &rh->details.purse_refund.exchange_sig))
    725   {
    726     GNUNET_break_op (0);
    727     return GNUNET_SYSERR;
    728   }
    729   if ( (GNUNET_YES !=
    730         TALER_amount_cmp_currency (&rh->details.purse_refund.refund_fee,
    731                                    &pc->dk->fees.refund)) ||
    732        (0 !=
    733         TALER_amount_cmp (&rh->details.purse_refund.refund_fee,
    734                           &pc->dk->fees.refund)) )
    735   {
    736     GNUNET_break_op (0);
    737     return GNUNET_SYSERR;
    738   }
    739   return GNUNET_NO;
    740 }
    741 
    742 
    743 /**
    744  * Handle reserve deposit entry in the coin's history.
    745  *
    746  * @param[in,out] pc overall context
    747  * @param[out] rh history entry to initialize
    748  * @param amount main amount of this operation
    749  * @param transaction JSON details for the operation
    750  * @return #GNUNET_SYSERR on error,
    751  *         #GNUNET_OK to add, #GNUNET_NO to subtract
    752  */
    753 static enum GNUNET_GenericReturnValue
    754 help_reserve_open_deposit (struct CoinHistoryParseContext *pc,
    755                            struct TALER_EXCHANGE_CoinHistoryEntry *rh,
    756                            const struct TALER_Amount *amount,
    757                            json_t *transaction)
    758 {
    759   struct GNUNET_JSON_Specification spec[] = {
    760     GNUNET_JSON_spec_fixed_auto (
    761       "reserve_sig",
    762       &rh->details.reserve_open_deposit.reserve_sig),
    763     GNUNET_JSON_spec_fixed_auto (
    764       "coin_sig",
    765       &rh->details.reserve_open_deposit.coin_sig),
    766     TALER_JSON_spec_amount_any (
    767       "coin_contribution",
    768       &rh->details.reserve_open_deposit.coin_contribution),
    769     GNUNET_JSON_spec_mark_optional (
    770       GNUNET_JSON_spec_fixed_auto (
    771         "h_age_commitment",
    772         &rh->details.reserve_open_deposit.h_age_commitment),
    773       &rh->details.reserve_open_deposit.no_hac),
    774     GNUNET_JSON_spec_end ()
    775   };
    776 
    777   if (GNUNET_OK !=
    778       GNUNET_JSON_parse (transaction,
    779                          spec,
    780                          NULL, NULL))
    781   {
    782     GNUNET_break_op (0);
    783     return GNUNET_SYSERR;
    784   }
    785   if (GNUNET_OK !=
    786       TALER_wallet_reserve_open_deposit_verify (
    787         amount,
    788         &pc->dk->h_key,
    789         rh->details.reserve_open_deposit.no_hac
    790         ? NULL
    791         : &rh->details.reserve_open_deposit.h_age_commitment,
    792         &rh->details.reserve_open_deposit.reserve_sig,
    793         pc->coin_pub,
    794         &rh->details.reserve_open_deposit.coin_sig))
    795   {
    796     GNUNET_break_op (0);
    797     return GNUNET_SYSERR;
    798   }
    799   return GNUNET_YES;
    800 }
    801 
    802 
    803 enum GNUNET_GenericReturnValue
    804 TALER_EXCHANGE_parse_coin_history (
    805   const struct TALER_EXCHANGE_Keys *keys,
    806   const struct TALER_EXCHANGE_DenomPublicKey *dk,
    807   const json_t *history,
    808   const struct TALER_CoinSpendPublicKeyP *coin_pub,
    809   struct TALER_Amount *total_in,
    810   struct TALER_Amount *total_out,
    811   unsigned int rlen,
    812   struct TALER_EXCHANGE_CoinHistoryEntry rhistory[static rlen])
    813 {
    814   const struct
    815   {
    816     const char *type;
    817     CoinCheckHelper helper;
    818     enum TALER_EXCHANGE_CoinTransactionType ctt;
    819   } map[] = {
    820     { "DEPOSIT",
    821       &help_deposit,
    822       TALER_EXCHANGE_CTT_DEPOSIT },
    823     { "MELT",
    824       &help_melt,
    825       TALER_EXCHANGE_CTT_MELT },
    826     { "REFUND",
    827       &help_refund,
    828       TALER_EXCHANGE_CTT_REFUND },
    829     { "RECOUP-WITHDRAW",
    830       &help_recoup,
    831       TALER_EXCHANGE_CTT_RECOUP },
    832     { "RECOUP-REFRESH",
    833       &help_recoup_refresh,
    834       TALER_EXCHANGE_CTT_RECOUP_REFRESH },
    835     { "RECOUP-REFRESH-RECEIVER",
    836       &help_old_coin_recoup,
    837       TALER_EXCHANGE_CTT_OLD_COIN_RECOUP },
    838     { "PURSE-DEPOSIT",
    839       &help_purse_deposit,
    840       TALER_EXCHANGE_CTT_PURSE_DEPOSIT },
    841     { "PURSE-REFUND",
    842       &help_purse_refund,
    843       TALER_EXCHANGE_CTT_PURSE_REFUND },
    844     { "RESERVE-OPEN-DEPOSIT",
    845       &help_reserve_open_deposit,
    846       TALER_EXCHANGE_CTT_RESERVE_OPEN_DEPOSIT },
    847     { NULL, NULL, TALER_EXCHANGE_CTT_NONE }
    848   };
    849   struct CoinHistoryParseContext pc = {
    850     .keys = keys,
    851     .dk = dk,
    852     .coin_pub = coin_pub,
    853     .total_out = total_out,
    854     .total_in = total_in
    855   };
    856   size_t len;
    857 
    858   if (NULL == history)
    859   {
    860     GNUNET_break_op (0);
    861     return GNUNET_SYSERR;
    862   }
    863   len = json_array_size (history);
    864   if (0 == len)
    865   {
    866     GNUNET_break_op (0);
    867     return GNUNET_SYSERR;
    868   }
    869   *total_in = dk->value;
    870   GNUNET_assert (GNUNET_OK ==
    871                  TALER_amount_set_zero (total_in->currency,
    872                                         total_out));
    873   for (size_t off = 0; off < len; off++)
    874   {
    875     struct TALER_EXCHANGE_CoinHistoryEntry *rh = &rhistory[off];
    876     json_t *transaction = json_array_get (history,
    877                                           off);
    878     enum GNUNET_GenericReturnValue add;
    879     const char *type;
    880     struct GNUNET_JSON_Specification spec_glob[] = {
    881       TALER_JSON_spec_amount_any ("amount",
    882                                   &rh->amount),
    883       GNUNET_JSON_spec_string ("type",
    884                                &type),
    885       GNUNET_JSON_spec_uint64 ("history_offset",
    886                                &rh->history_offset),
    887       GNUNET_JSON_spec_end ()
    888     };
    889 
    890     if (GNUNET_OK !=
    891         GNUNET_JSON_parse (transaction,
    892                            spec_glob,
    893                            NULL, NULL))
    894     {
    895       GNUNET_break_op (0);
    896       return GNUNET_SYSERR;
    897     }
    898     if (GNUNET_YES !=
    899         TALER_amount_cmp_currency (&rh->amount,
    900                                    total_in))
    901     {
    902       GNUNET_break_op (0);
    903       return GNUNET_SYSERR;
    904     }
    905     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    906                 "Operation of type %s with amount %s\n",
    907                 type,
    908                 TALER_amount2s (&rh->amount));
    909     add = GNUNET_SYSERR;
    910     for (unsigned int i = 0; NULL != map[i].type; i++)
    911     {
    912       if (0 == strcasecmp (type,
    913                            map[i].type))
    914       {
    915         rh->type = map[i].ctt;
    916         add = map[i].helper (&pc,
    917                              rh,
    918                              &rh->amount,
    919                              transaction);
    920         break;
    921       }
    922     }
    923     switch (add)
    924     {
    925     case GNUNET_SYSERR:
    926       /* entry type not supported, new version on server? */
    927       rh->type = TALER_EXCHANGE_CTT_NONE;
    928       GNUNET_break_op (0);
    929       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    930                   "Unexpected type `%s' in response\n",
    931                   type);
    932       return GNUNET_SYSERR;
    933     case GNUNET_YES:
    934       /* This amount should be debited from the coin */
    935       if (0 >
    936           TALER_amount_add (total_out,
    937                             total_out,
    938                             &rh->amount))
    939       {
    940         /* overflow in history already!? inconceivable! Bad exchange! */
    941         GNUNET_break_op (0);
    942         return GNUNET_SYSERR;
    943       }
    944       break;
    945     case GNUNET_NO:
    946       /* This amount should be credited to the coin. */
    947       if (0 >
    948           TALER_amount_add (total_in,
    949                             total_in,
    950                             &rh->amount))
    951       {
    952         /* overflow in refund history? inconceivable! Bad exchange! */
    953         GNUNET_break_op (0);
    954         return GNUNET_SYSERR;
    955       }
    956       break;
    957     } /* end of switch(add) */
    958   }
    959   return GNUNET_OK;
    960 }
    961 
    962 
    963 /**
    964  * We received an #MHD_HTTP_OK response. Handle the JSON response.
    965  *
    966  * @param gcsh handle of the request
    967  * @param j JSON response
    968  * @return #GNUNET_OK on success
    969  */
    970 static enum GNUNET_GenericReturnValue
    971 handle_coins_history_ok (struct TALER_EXCHANGE_GetCoinsHistoryHandle *gcsh,
    972                          const json_t *j)
    973 {
    974   struct TALER_EXCHANGE_GetCoinsHistoryResponse rs = {
    975     .hr.reply = j,
    976     .hr.http_status = MHD_HTTP_OK
    977   };
    978   struct GNUNET_JSON_Specification spec[] = {
    979     TALER_JSON_spec_amount_any ("balance",
    980                                 &rs.details.ok.balance),
    981     GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
    982                                  &rs.details.ok.h_denom_pub),
    983     GNUNET_JSON_spec_array_const ("history",
    984                                   &rs.details.ok.history),
    985     GNUNET_JSON_spec_end ()
    986   };
    987 
    988   if (GNUNET_OK !=
    989       GNUNET_JSON_parse (j,
    990                          spec,
    991                          NULL,
    992                          NULL))
    993   {
    994     GNUNET_break_op (0);
    995     return GNUNET_SYSERR;
    996   }
    997   if (NULL != gcsh->cb)
    998   {
    999     gcsh->cb (gcsh->cb_cls,
   1000               &rs);
   1001     gcsh->cb = NULL;
   1002   }
   1003   GNUNET_JSON_parse_free (spec);
   1004   return GNUNET_OK;
   1005 }
   1006 
   1007 
   1008 /**
   1009  * Function called when we're done processing the
   1010  * HTTP GET /coins/$COIN_PUB/history request.
   1011  *
   1012  * @param cls the `struct TALER_EXCHANGE_GetCoinsHistoryHandle`
   1013  * @param response_code HTTP response code, 0 on error
   1014  * @param response parsed JSON result, NULL on error
   1015  */
   1016 static void
   1017 handle_coins_history_finished (void *cls,
   1018                                long response_code,
   1019                                const void *response)
   1020 {
   1021   struct TALER_EXCHANGE_GetCoinsHistoryHandle *gcsh = cls;
   1022   const json_t *j = response;
   1023   struct TALER_EXCHANGE_GetCoinsHistoryResponse rs = {
   1024     .hr.reply = j,
   1025     .hr.http_status = (unsigned int) response_code
   1026   };
   1027 
   1028   gcsh->job = NULL;
   1029   switch (response_code)
   1030   {
   1031   case 0:
   1032     rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
   1033     break;
   1034   case MHD_HTTP_OK:
   1035     if (GNUNET_OK !=
   1036         handle_coins_history_ok (gcsh,
   1037                                  j))
   1038     {
   1039       rs.hr.http_status = 0;
   1040       rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
   1041     }
   1042     break;
   1043   case MHD_HTTP_NO_CONTENT:
   1044     break;
   1045   case MHD_HTTP_NOT_MODIFIED:
   1046     break;
   1047   case MHD_HTTP_BAD_REQUEST:
   1048     /* This should never happen, either us or the exchange is buggy
   1049        (or API version conflict); just pass JSON reply to the application */
   1050     GNUNET_break (0);
   1051     rs.hr.ec = TALER_JSON_get_error_code (j);
   1052     rs.hr.hint = TALER_JSON_get_error_hint (j);
   1053     break;
   1054   case MHD_HTTP_FORBIDDEN:
   1055     /* This should never happen, either us or the exchange is buggy
   1056        (or API version conflict); just pass JSON reply to the application */
   1057     GNUNET_break (0);
   1058     rs.hr.ec = TALER_JSON_get_error_code (j);
   1059     rs.hr.hint = TALER_JSON_get_error_hint (j);
   1060     break;
   1061   case MHD_HTTP_NOT_FOUND:
   1062     rs.hr.ec = TALER_JSON_get_error_code (j);
   1063     rs.hr.hint = TALER_JSON_get_error_hint (j);
   1064     break;
   1065   case MHD_HTTP_INTERNAL_SERVER_ERROR:
   1066     /* Server had an internal issue; we should retry, but this API
   1067        leaves this to the application */
   1068     rs.hr.ec = TALER_JSON_get_error_code (j);
   1069     rs.hr.hint = TALER_JSON_get_error_hint (j);
   1070     break;
   1071   default:
   1072     /* unexpected response code */
   1073     GNUNET_break_op (0);
   1074     rs.hr.ec = TALER_JSON_get_error_code (j);
   1075     rs.hr.hint = TALER_JSON_get_error_hint (j);
   1076     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1077                 "Unexpected response code %u/%d for coins history\n",
   1078                 (unsigned int) response_code,
   1079                 (int) rs.hr.ec);
   1080     break;
   1081   }
   1082   if (NULL != gcsh->cb)
   1083   {
   1084     gcsh->cb (gcsh->cb_cls,
   1085               &rs);
   1086     gcsh->cb = NULL;
   1087   }
   1088   TALER_EXCHANGE_get_coins_history_cancel (gcsh);
   1089 }
   1090 
   1091 
   1092 struct TALER_EXCHANGE_GetCoinsHistoryHandle *
   1093 TALER_EXCHANGE_get_coins_history_create (
   1094   struct GNUNET_CURL_Context *ctx,
   1095   const char *url,
   1096   const struct TALER_CoinSpendPrivateKeyP *coin_priv)
   1097 {
   1098   struct TALER_EXCHANGE_GetCoinsHistoryHandle *gcsh;
   1099 
   1100   gcsh = GNUNET_new (struct TALER_EXCHANGE_GetCoinsHistoryHandle);
   1101   gcsh->ctx = ctx;
   1102   gcsh->base_url = GNUNET_strdup (url);
   1103   gcsh->coin_priv = *coin_priv;
   1104   GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
   1105                                       &gcsh->coin_pub.eddsa_pub);
   1106   gcsh->options.start_off = 0;
   1107   return gcsh;
   1108 }
   1109 
   1110 
   1111 enum GNUNET_GenericReturnValue
   1112 TALER_EXCHANGE_get_coins_history_set_options_ (
   1113   struct TALER_EXCHANGE_GetCoinsHistoryHandle *gcsh,
   1114   unsigned int num_options,
   1115   const struct TALER_EXCHANGE_GetCoinsHistoryOptionValue *options)
   1116 {
   1117   for (unsigned int i = 0; i < num_options; i++)
   1118   {
   1119     const struct TALER_EXCHANGE_GetCoinsHistoryOptionValue *opt = &options[i];
   1120 
   1121     switch (opt->option)
   1122     {
   1123     case TALER_EXCHANGE_GET_COINS_HISTORY_OPTION_END:
   1124       return GNUNET_OK;
   1125     case TALER_EXCHANGE_GET_COINS_HISTORY_OPTION_START_OFF:
   1126       gcsh->options.start_off = opt->details.start_off;
   1127       break;
   1128     }
   1129   }
   1130   return GNUNET_OK;
   1131 }
   1132 
   1133 
   1134 enum TALER_ErrorCode
   1135 TALER_EXCHANGE_get_coins_history_start (
   1136   struct TALER_EXCHANGE_GetCoinsHistoryHandle *gcsh,
   1137   TALER_EXCHANGE_GetCoinsHistoryCallback cb,
   1138   TALER_EXCHANGE_GET_COINS_HISTORY_RESULT_CLOSURE *cb_cls)
   1139 {
   1140   char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 64];
   1141   struct curl_slist *job_headers;
   1142   CURL *eh;
   1143 
   1144   if (NULL != gcsh->job)
   1145   {
   1146     GNUNET_break (0);
   1147     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
   1148   }
   1149   gcsh->cb = cb;
   1150   gcsh->cb_cls = cb_cls;
   1151   {
   1152     char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
   1153     char *end;
   1154 
   1155     end = GNUNET_STRINGS_data_to_string (
   1156       &gcsh->coin_pub,
   1157       sizeof (gcsh->coin_pub),
   1158       pub_str,
   1159       sizeof (pub_str));
   1160     *end = '\0';
   1161     if (0 != gcsh->options.start_off)
   1162       GNUNET_snprintf (arg_str,
   1163                        sizeof (arg_str),
   1164                        "coins/%s/history?start=%llu",
   1165                        pub_str,
   1166                        (unsigned long long) gcsh->options.start_off);
   1167     else
   1168       GNUNET_snprintf (arg_str,
   1169                        sizeof (arg_str),
   1170                        "coins/%s/history",
   1171                        pub_str);
   1172   }
   1173   gcsh->url = TALER_url_join (gcsh->base_url,
   1174                               arg_str,
   1175                               NULL);
   1176   if (NULL == gcsh->url)
   1177     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
   1178   eh = TALER_EXCHANGE_curl_easy_get_ (gcsh->url);
   1179   if (NULL == eh)
   1180     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
   1181   {
   1182     struct TALER_CoinSpendSignatureP coin_sig;
   1183     char *sig_hdr;
   1184     char *hdr;
   1185 
   1186     TALER_wallet_coin_history_sign (gcsh->options.start_off,
   1187                                     &gcsh->coin_priv,
   1188                                     &coin_sig);
   1189     sig_hdr = GNUNET_STRINGS_data_to_string_alloc (
   1190       &coin_sig,
   1191       sizeof (coin_sig));
   1192     GNUNET_asprintf (&hdr,
   1193                      "%s: %s",
   1194                      TALER_COIN_HISTORY_SIGNATURE_HEADER,
   1195                      sig_hdr);
   1196     GNUNET_free (sig_hdr);
   1197     job_headers = curl_slist_append (NULL,
   1198                                      hdr);
   1199     GNUNET_free (hdr);
   1200     if (NULL == job_headers)
   1201     {
   1202       GNUNET_break (0);
   1203       curl_easy_cleanup (eh);
   1204       GNUNET_free (gcsh->url);
   1205       return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
   1206     }
   1207   }
   1208   gcsh->job = GNUNET_CURL_job_add2 (gcsh->ctx,
   1209                                     eh,
   1210                                     job_headers,
   1211                                     &handle_coins_history_finished,
   1212                                     gcsh);
   1213   curl_slist_free_all (job_headers);
   1214   if (NULL == gcsh->job)
   1215     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
   1216   return TALER_EC_NONE;
   1217 }
   1218 
   1219 
   1220 void
   1221 TALER_EXCHANGE_get_coins_history_cancel (
   1222   struct TALER_EXCHANGE_GetCoinsHistoryHandle *gcsh)
   1223 {
   1224   if (NULL != gcsh->job)
   1225   {
   1226     GNUNET_CURL_job_cancel (gcsh->job);
   1227     gcsh->job = NULL;
   1228   }
   1229   GNUNET_free (gcsh->url);
   1230   GNUNET_free (gcsh->base_url);
   1231   GNUNET_free (gcsh);
   1232 }
   1233 
   1234 
   1235 enum GNUNET_GenericReturnValue
   1236 TALER_EXCHANGE_check_coin_signature_conflict (
   1237   const json_t *history,
   1238   const struct TALER_CoinSpendSignatureP *coin_sig)
   1239 {
   1240   size_t off;
   1241   json_t *entry;
   1242 
   1243   json_array_foreach (history, off, entry)
   1244   {
   1245     struct TALER_CoinSpendSignatureP cs;
   1246     struct GNUNET_JSON_Specification spec[] = {
   1247       GNUNET_JSON_spec_fixed_auto ("coin_sig",
   1248                                    &cs),
   1249       GNUNET_JSON_spec_end ()
   1250     };
   1251 
   1252     if (GNUNET_OK !=
   1253         GNUNET_JSON_parse (entry,
   1254                            spec,
   1255                            NULL, NULL))
   1256       continue; /* entry without coin signature */
   1257     if (0 ==
   1258         GNUNET_memcmp (&cs,
   1259                        coin_sig))
   1260     {
   1261       GNUNET_break_op (0);
   1262       return GNUNET_SYSERR;
   1263     }
   1264   }
   1265   return GNUNET_OK;
   1266 }
   1267 
   1268 
   1269 /* end of exchange_api_get-coins-COIN_PUB-history.c */