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-reserves-RESERVE_PUB-history.c (39983B)


      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-reserves-RESERVE_PUB-history.c
     19  * @brief Implementation of the GET /reserves/$RESERVE_PUB/history requests
     20  * @author Christian Grothoff
     21  */
     22 #include <jansson.h>
     23 #include <microhttpd.h> /* just for HTTP history 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 #define DEBUG 0
     33 
     34 /**
     35  * @brief A GET /reserves/$RESERVE_PUB/history Handle
     36  */
     37 struct TALER_EXCHANGE_GetReservesHistoryHandle
     38 {
     39 
     40   /**
     41    * Base URL of the exchange.
     42    */
     43   char *base_url;
     44 
     45   /**
     46    * The url for this request.
     47    */
     48   char *url;
     49 
     50   /**
     51    * The keys of the exchange this request handle will use.
     52    */
     53   struct TALER_EXCHANGE_Keys *keys;
     54 
     55   /**
     56    * Handle for the request.
     57    */
     58   struct GNUNET_CURL_Job *job;
     59 
     60   /**
     61    * Function to call with the result.
     62    */
     63   TALER_EXCHANGE_GetReservesHistoryCallback cb;
     64 
     65   /**
     66    * Closure for @e cb.
     67    */
     68   TALER_EXCHANGE_GET_RESERVES_HISTORY_RESULT_CLOSURE *cb_cls;
     69 
     70   /**
     71    * CURL context to use.
     72    */
     73   struct GNUNET_CURL_Context *ctx;
     74 
     75   /**
     76    * Private key of the reserve we are querying.
     77    */
     78   struct TALER_ReservePrivateKeyP reserve_priv;
     79 
     80   /**
     81    * Public key of the reserve we are querying.
     82    */
     83   struct TALER_ReservePublicKeyP reserve_pub;
     84 
     85   /**
     86    * Where to store the etag (if any).
     87    */
     88   uint64_t etag;
     89 
     90   /**
     91    * Options for the request.
     92    */
     93   struct
     94   {
     95     /**
     96      * Only return entries with offset strictly greater than this value.
     97      */
     98     uint64_t start_off;
     99   } options;
    100 
    101 };
    102 
    103 
    104 /**
    105  * Context for history entry helpers.
    106  */
    107 struct HistoryParseContext
    108 {
    109 
    110   /**
    111    * Keys of the exchange we use.
    112    */
    113   const struct TALER_EXCHANGE_Keys *keys;
    114 
    115   /**
    116    * Our reserve public key.
    117    */
    118   const struct TALER_ReservePublicKeyP *reserve_pub;
    119 
    120   /**
    121    * Array of UUIDs.
    122    */
    123   struct GNUNET_HashCode *uuids;
    124 
    125   /**
    126    * Where to sum up total inbound amounts.
    127    */
    128   struct TALER_Amount *total_in;
    129 
    130   /**
    131    * Where to sum up total outbound amounts.
    132    */
    133   struct TALER_Amount *total_out;
    134 
    135   /**
    136    * Number of entries already used in @e uuids.
    137    */
    138   unsigned int uuid_off;
    139 };
    140 
    141 
    142 /**
    143  * Type of a function called to parse a reserve history
    144  * entry @a rh.
    145  *
    146  * @param[in,out] rh where to write the result
    147  * @param[in,out] uc UUID context for duplicate detection
    148  * @param transaction the transaction to parse
    149  * @return #GNUNET_OK on success
    150  */
    151 typedef enum GNUNET_GenericReturnValue
    152 (*ParseHelper)(struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
    153                struct HistoryParseContext *uc,
    154                const json_t *transaction);
    155 
    156 
    157 /**
    158  * Parse "credit" reserve history entry.
    159  *
    160  * @param[in,out] rh entry to parse
    161  * @param uc our context
    162  * @param transaction the transaction to parse
    163  * @return #GNUNET_OK on success
    164  */
    165 static enum GNUNET_GenericReturnValue
    166 parse_credit (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
    167               struct HistoryParseContext *uc,
    168               const json_t *transaction)
    169 {
    170   struct TALER_FullPayto wire_uri;
    171   uint64_t wire_reference;
    172   struct GNUNET_TIME_Timestamp timestamp;
    173   struct GNUNET_JSON_Specification withdraw_spec[] = {
    174     GNUNET_JSON_spec_uint64 ("wire_reference",
    175                              &wire_reference),
    176     GNUNET_JSON_spec_timestamp ("timestamp",
    177                                 &timestamp),
    178     TALER_JSON_spec_full_payto_uri ("sender_account_url",
    179                                     &wire_uri),
    180     GNUNET_JSON_spec_end ()
    181   };
    182 
    183   rh->type = TALER_EXCHANGE_RTT_CREDIT;
    184   if (0 >
    185       TALER_amount_add (uc->total_in,
    186                         uc->total_in,
    187                         &rh->amount))
    188   {
    189     /* overflow in history already!? inconceivable! Bad exchange! */
    190     GNUNET_break_op (0);
    191     return GNUNET_SYSERR;
    192   }
    193   if (GNUNET_OK !=
    194       GNUNET_JSON_parse (transaction,
    195                          withdraw_spec,
    196                          NULL, NULL))
    197   {
    198     GNUNET_break_op (0);
    199     return GNUNET_SYSERR;
    200   }
    201   rh->details.in_details.sender_url.full_payto
    202     = GNUNET_strdup (wire_uri.full_payto);
    203   rh->details.in_details.wire_reference = wire_reference;
    204   rh->details.in_details.timestamp = timestamp;
    205   return GNUNET_OK;
    206 }
    207 
    208 
    209 /**
    210  * Parse "withdraw" reserve history entry.
    211  *
    212  * @param[in,out] rh entry to parse
    213  * @param uc our context
    214  * @param transaction the transaction to parse
    215  * @return #GNUNET_OK on success
    216  */
    217 static enum GNUNET_GenericReturnValue
    218 parse_withdraw (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
    219                 struct HistoryParseContext *uc,
    220                 const json_t *transaction)
    221 {
    222   uint16_t num_coins;
    223   struct TALER_Amount withdraw_fee;
    224   struct TALER_Amount withdraw_amount;
    225   struct TALER_Amount amount_without_fee;
    226   uint8_t max_age = 0;
    227   uint8_t noreveal_index = 0;
    228   struct TALER_HashBlindedPlanchetsP planchets_h;
    229   struct TALER_HashBlindedPlanchetsP selected_h;
    230   struct TALER_ReserveSignatureP reserve_sig;
    231   struct TALER_BlindingMasterSeedP blinding_seed;
    232   struct TALER_DenominationHashP *denom_pub_hashes;
    233   size_t num_denom_pub_hashes;
    234   bool no_max_age;
    235   bool no_noreveal_index;
    236   bool no_blinding_seed;
    237   bool no_selected_h;
    238   struct GNUNET_JSON_Specification withdraw_spec[] = {
    239     GNUNET_JSON_spec_fixed_auto ("reserve_sig",
    240                                  &reserve_sig),
    241     GNUNET_JSON_spec_uint16 ("num_coins",
    242                              &num_coins),
    243     GNUNET_JSON_spec_fixed_auto ("planchets_h",
    244                                  &planchets_h),
    245     TALER_JSON_spec_amount_any ("amount",
    246                                 &withdraw_amount),
    247     TALER_JSON_spec_amount_any ("withdraw_fee",
    248                                 &withdraw_fee),
    249     TALER_JSON_spec_array_of_denom_pub_h ("denom_pub_hashes",
    250                                           &num_denom_pub_hashes,
    251                                           &denom_pub_hashes),
    252     GNUNET_JSON_spec_mark_optional (
    253       GNUNET_JSON_spec_fixed_auto ("selected_h",
    254                                    &selected_h),
    255       &no_selected_h),
    256     GNUNET_JSON_spec_mark_optional (
    257       GNUNET_JSON_spec_uint8 ("max_age",
    258                               &max_age),
    259       &no_max_age),
    260     GNUNET_JSON_spec_mark_optional (
    261       GNUNET_JSON_spec_fixed_auto ("blinding_seed",
    262                                    &blinding_seed),
    263       &no_blinding_seed),
    264     GNUNET_JSON_spec_mark_optional (
    265       GNUNET_JSON_spec_uint8 ("noreveal_index",
    266                               &noreveal_index),
    267       &no_noreveal_index),
    268     GNUNET_JSON_spec_end ()
    269   };
    270 
    271   rh->type = TALER_EXCHANGE_RTT_WITHDRAWAL;
    272   if (GNUNET_OK !=
    273       GNUNET_JSON_parse (transaction,
    274                          withdraw_spec,
    275                          NULL, NULL))
    276   {
    277     GNUNET_break_op (0);
    278     return GNUNET_SYSERR;
    279   }
    280 
    281   if ((no_max_age != no_noreveal_index) ||
    282       (no_max_age != no_selected_h))
    283   {
    284     GNUNET_break_op (0);
    285     GNUNET_JSON_parse_free (withdraw_spec);
    286     return GNUNET_SYSERR;
    287   }
    288   rh->details.withdraw.age_restricted = ! no_max_age;
    289 
    290   if (num_coins != num_denom_pub_hashes)
    291   {
    292     GNUNET_break_op (0);
    293     GNUNET_JSON_parse_free (withdraw_spec);
    294     return GNUNET_SYSERR;
    295   }
    296 
    297   /* Check that the signature is a valid withdraw request */
    298   if (0>TALER_amount_subtract (
    299         &amount_without_fee,
    300         &withdraw_amount,
    301         &withdraw_fee))
    302   {
    303     GNUNET_break_op (0);
    304     GNUNET_JSON_parse_free (withdraw_spec);
    305     return GNUNET_SYSERR;
    306   }
    307 
    308   if (GNUNET_OK !=
    309       TALER_wallet_withdraw_verify (
    310         &amount_without_fee,
    311         &withdraw_fee,
    312         &planchets_h,
    313         no_blinding_seed ? NULL : &blinding_seed,
    314         no_max_age ? NULL : &uc->keys->age_mask,
    315         no_max_age ? 0 : max_age,
    316         uc->reserve_pub,
    317         &reserve_sig))
    318   {
    319     GNUNET_break_op (0);
    320     GNUNET_JSON_parse_free (withdraw_spec);
    321     return GNUNET_SYSERR;
    322   }
    323 
    324   rh->details.withdraw.num_coins = num_coins;
    325   rh->details.withdraw.age_restricted = ! no_max_age;
    326   rh->details.withdraw.max_age = max_age;
    327   rh->details.withdraw.planchets_h = planchets_h;
    328   rh->details.withdraw.selected_h = selected_h;
    329   rh->details.withdraw.noreveal_index = noreveal_index;
    330   rh->details.withdraw.no_blinding_seed = no_blinding_seed;
    331   rh->details.withdraw.reserve_sig = reserve_sig;
    332   if (! no_blinding_seed)
    333     rh->details.withdraw.blinding_seed = blinding_seed;
    334 
    335   /* check that withdraw fee matches expectations! */
    336   {
    337     const struct TALER_EXCHANGE_Keys *key_state;
    338     struct TALER_Amount fee_acc;
    339     struct TALER_Amount amount_acc;
    340 
    341     GNUNET_assert (GNUNET_OK ==
    342                    TALER_amount_set_zero (withdraw_amount.currency,
    343                                           &fee_acc));
    344     GNUNET_assert (GNUNET_OK ==
    345                    TALER_amount_set_zero (withdraw_amount.currency,
    346                                           &amount_acc));
    347 
    348     key_state = uc->keys;
    349 
    350     /* accumulate the withdraw fees */
    351     for (size_t i=0; i < num_coins; i++)
    352     {
    353       const struct TALER_EXCHANGE_DenomPublicKey *dki;
    354 
    355       dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state,
    356                                                          &denom_pub_hashes[i]);
    357       if (NULL == dki)
    358       {
    359         GNUNET_break_op (0);
    360         GNUNET_JSON_parse_free (withdraw_spec);
    361         return GNUNET_SYSERR;
    362       }
    363       if ( (0 >
    364             TALER_amount_add (&fee_acc,
    365                               &fee_acc,
    366                               &dki->fees.withdraw)) ||
    367            (0 >
    368             TALER_amount_add (&amount_acc,
    369                               &amount_acc,
    370                               &dki->value)) )
    371       {
    372         GNUNET_break_op (0);
    373         GNUNET_JSON_parse_free (withdraw_spec);
    374         return GNUNET_SYSERR;
    375       }
    376     }
    377 
    378     if ( (GNUNET_YES !=
    379           TALER_amount_cmp_currency (&fee_acc,
    380                                      &withdraw_fee)) ||
    381          (0 !=
    382           TALER_amount_cmp (&amount_acc,
    383                             &amount_without_fee)) )
    384     {
    385       GNUNET_break_op (0);
    386       GNUNET_JSON_parse_free (withdraw_spec);
    387       return GNUNET_SYSERR;
    388     }
    389     rh->details.withdraw.fee = withdraw_fee;
    390   }
    391 
    392   /* Check check that the same withdraw transaction
    393        isn't listed twice by the exchange. We use the
    394        "uuid" array to remember the hashes of all
    395        signatures, and compare the hashes to find
    396        duplicates. */
    397   GNUNET_CRYPTO_hash (&reserve_sig,
    398                       sizeof (reserve_sig),
    399                       &uc->uuids[uc->uuid_off]);
    400   for (unsigned int i = 0; i<uc->uuid_off; i++)
    401   {
    402     if (0 == GNUNET_memcmp (&uc->uuids[uc->uuid_off],
    403                             &uc->uuids[i]))
    404     {
    405       GNUNET_break_op (0);
    406       GNUNET_JSON_parse_free (withdraw_spec);
    407       return GNUNET_SYSERR;
    408     }
    409   }
    410   uc->uuid_off++;
    411 
    412   if (0 >
    413       TALER_amount_add (uc->total_out,
    414                         uc->total_out,
    415                         &rh->amount))
    416   {
    417     /* overflow in history already!? inconceivable! Bad exchange! */
    418     GNUNET_break_op (0);
    419     GNUNET_JSON_parse_free (withdraw_spec);
    420     return GNUNET_SYSERR;
    421   }
    422   GNUNET_JSON_parse_free (withdraw_spec);
    423   return GNUNET_OK;
    424 }
    425 
    426 
    427 /**
    428  * Parse "recoup" reserve history entry.
    429  *
    430  * @param[in,out] rh entry to parse
    431  * @param uc our context
    432  * @param transaction the transaction to parse
    433  * @return #GNUNET_OK on success
    434  */
    435 static enum GNUNET_GenericReturnValue
    436 parse_recoup (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
    437               struct HistoryParseContext *uc,
    438               const json_t *transaction)
    439 {
    440   const struct TALER_EXCHANGE_Keys *key_state;
    441   struct GNUNET_JSON_Specification recoup_spec[] = {
    442     GNUNET_JSON_spec_fixed_auto ("coin_pub",
    443                                  &rh->details.recoup_details.coin_pub),
    444     GNUNET_JSON_spec_fixed_auto ("exchange_sig",
    445                                  &rh->details.recoup_details.exchange_sig),
    446     GNUNET_JSON_spec_fixed_auto ("exchange_pub",
    447                                  &rh->details.recoup_details.exchange_pub),
    448     GNUNET_JSON_spec_timestamp ("timestamp",
    449                                 &rh->details.recoup_details.timestamp),
    450     GNUNET_JSON_spec_fixed_auto ("planchets_h",
    451                                  &rh->details.recoup_details.planchets_h),
    452     GNUNET_JSON_spec_end ()
    453   };
    454 
    455   rh->type = TALER_EXCHANGE_RTT_RECOUP;
    456   if (GNUNET_OK !=
    457       GNUNET_JSON_parse (transaction,
    458                          recoup_spec,
    459                          NULL, NULL))
    460   {
    461     GNUNET_break_op (0);
    462     return GNUNET_SYSERR;
    463   }
    464   key_state = uc->keys;
    465   if (GNUNET_OK !=
    466       TALER_EXCHANGE_test_signing_key (key_state,
    467                                        &rh->details.
    468                                        recoup_details.exchange_pub))
    469   {
    470     GNUNET_break_op (0);
    471     return GNUNET_SYSERR;
    472   }
    473   if (GNUNET_OK !=
    474       TALER_exchange_online_confirm_recoup_verify (
    475         rh->details.recoup_details.timestamp,
    476         &rh->amount,
    477         &rh->details.recoup_details.coin_pub,
    478         uc->reserve_pub,
    479         &rh->details.recoup_details.exchange_pub,
    480         &rh->details.recoup_details.exchange_sig))
    481   {
    482     GNUNET_break_op (0);
    483     return GNUNET_SYSERR;
    484   }
    485   if (0 >
    486       TALER_amount_add (uc->total_in,
    487                         uc->total_in,
    488                         &rh->amount))
    489   {
    490     /* overflow in history already!? inconceivable! Bad exchange! */
    491     GNUNET_break_op (0);
    492     return GNUNET_SYSERR;
    493   }
    494   return GNUNET_OK;
    495 }
    496 
    497 
    498 /**
    499  * Parse "closing" reserve history entry.
    500  *
    501  * @param[in,out] rh entry to parse
    502  * @param uc our context
    503  * @param transaction the transaction to parse
    504  * @return #GNUNET_OK on success
    505  */
    506 static enum GNUNET_GenericReturnValue
    507 parse_closing (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
    508                struct HistoryParseContext *uc,
    509                const json_t *transaction)
    510 {
    511   const struct TALER_EXCHANGE_Keys *key_state;
    512   struct TALER_FullPayto receiver_uri;
    513   struct GNUNET_JSON_Specification closing_spec[] = {
    514     TALER_JSON_spec_full_payto_uri (
    515       "receiver_account_details",
    516       &receiver_uri),
    517     GNUNET_JSON_spec_fixed_auto ("wtid",
    518                                  &rh->details.close_details.wtid),
    519     GNUNET_JSON_spec_fixed_auto ("exchange_sig",
    520                                  &rh->details.close_details.exchange_sig),
    521     GNUNET_JSON_spec_fixed_auto ("exchange_pub",
    522                                  &rh->details.close_details.exchange_pub),
    523     TALER_JSON_spec_amount_any ("closing_fee",
    524                                 &rh->details.close_details.fee),
    525     GNUNET_JSON_spec_timestamp ("timestamp",
    526                                 &rh->details.close_details.timestamp),
    527     GNUNET_JSON_spec_end ()
    528   };
    529 
    530   rh->type = TALER_EXCHANGE_RTT_CLOSING;
    531   if (GNUNET_OK !=
    532       GNUNET_JSON_parse (transaction,
    533                          closing_spec,
    534                          NULL, NULL))
    535   {
    536     GNUNET_break_op (0);
    537     return GNUNET_SYSERR;
    538   }
    539   key_state = uc->keys;
    540   if (GNUNET_OK !=
    541       TALER_EXCHANGE_test_signing_key (
    542         key_state,
    543         &rh->details.close_details.exchange_pub))
    544   {
    545     GNUNET_break_op (0);
    546     return GNUNET_SYSERR;
    547   }
    548   if (GNUNET_OK !=
    549       TALER_exchange_online_reserve_closed_verify (
    550         rh->details.close_details.timestamp,
    551         &rh->amount,
    552         &rh->details.close_details.fee,
    553         receiver_uri,
    554         &rh->details.close_details.wtid,
    555         uc->reserve_pub,
    556         &rh->details.close_details.exchange_pub,
    557         &rh->details.close_details.exchange_sig))
    558   {
    559     GNUNET_break_op (0);
    560     return GNUNET_SYSERR;
    561   }
    562   if (0 >
    563       TALER_amount_add (uc->total_out,
    564                         uc->total_out,
    565                         &rh->amount))
    566   {
    567     /* overflow in history already!? inconceivable! Bad exchange! */
    568     GNUNET_break_op (0);
    569     return GNUNET_SYSERR;
    570   }
    571   rh->details.close_details.receiver_account_details.full_payto
    572     = GNUNET_strdup (receiver_uri.full_payto);
    573   return GNUNET_OK;
    574 }
    575 
    576 
    577 /**
    578  * Parse "merge" reserve history entry.
    579  *
    580  * @param[in,out] rh entry to parse
    581  * @param uc our context
    582  * @param transaction the transaction to parse
    583  * @return #GNUNET_OK on success
    584  */
    585 static enum GNUNET_GenericReturnValue
    586 parse_merge (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
    587              struct HistoryParseContext *uc,
    588              const json_t *transaction)
    589 {
    590   uint32_t flags32;
    591   struct GNUNET_JSON_Specification merge_spec[] = {
    592     GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
    593                                  &rh->details.merge_details.h_contract_terms),
    594     GNUNET_JSON_spec_fixed_auto ("merge_pub",
    595                                  &rh->details.merge_details.merge_pub),
    596     GNUNET_JSON_spec_fixed_auto ("purse_pub",
    597                                  &rh->details.merge_details.purse_pub),
    598     GNUNET_JSON_spec_uint32 ("min_age",
    599                              &rh->details.merge_details.min_age),
    600     GNUNET_JSON_spec_uint32 ("flags",
    601                              &flags32),
    602     GNUNET_JSON_spec_fixed_auto ("reserve_sig",
    603                                  &rh->details.merge_details.reserve_sig),
    604     TALER_JSON_spec_amount_any ("purse_fee",
    605                                 &rh->details.merge_details.purse_fee),
    606     GNUNET_JSON_spec_timestamp ("merge_timestamp",
    607                                 &rh->details.merge_details.merge_timestamp),
    608     GNUNET_JSON_spec_timestamp ("purse_expiration",
    609                                 &rh->details.merge_details.purse_expiration),
    610     GNUNET_JSON_spec_bool ("merged",
    611                            &rh->details.merge_details.merged),
    612     GNUNET_JSON_spec_end ()
    613   };
    614 
    615   rh->type = TALER_EXCHANGE_RTT_MERGE;
    616   if (GNUNET_OK !=
    617       GNUNET_JSON_parse (transaction,
    618                          merge_spec,
    619                          NULL, NULL))
    620   {
    621     GNUNET_break_op (0);
    622     return GNUNET_SYSERR;
    623   }
    624   rh->details.merge_details.flags =
    625     (enum TALER_WalletAccountMergeFlags) flags32;
    626   if (GNUNET_OK !=
    627       TALER_wallet_account_merge_verify (
    628         rh->details.merge_details.merge_timestamp,
    629         &rh->details.merge_details.purse_pub,
    630         rh->details.merge_details.purse_expiration,
    631         &rh->details.merge_details.h_contract_terms,
    632         &rh->amount,
    633         &rh->details.merge_details.purse_fee,
    634         rh->details.merge_details.min_age,
    635         rh->details.merge_details.flags,
    636         uc->reserve_pub,
    637         &rh->details.merge_details.reserve_sig))
    638   {
    639     GNUNET_break_op (0);
    640     return GNUNET_SYSERR;
    641   }
    642   if (rh->details.merge_details.merged)
    643   {
    644     if (0 >
    645         TALER_amount_add (uc->total_in,
    646                           uc->total_in,
    647                           &rh->amount))
    648     {
    649       /* overflow in history already!? inconceivable! Bad exchange! */
    650       GNUNET_break_op (0);
    651       return GNUNET_SYSERR;
    652     }
    653   }
    654   else
    655   {
    656     if (0 >
    657         TALER_amount_add (uc->total_out,
    658                           uc->total_out,
    659                           &rh->details.merge_details.purse_fee))
    660     {
    661       /* overflow in history already!? inconceivable! Bad exchange! */
    662       GNUNET_break_op (0);
    663       return GNUNET_SYSERR;
    664     }
    665   }
    666   return GNUNET_OK;
    667 }
    668 
    669 
    670 /**
    671  * Parse "open" reserve open entry.
    672  *
    673  * @param[in,out] rh entry to parse
    674  * @param uc our context
    675  * @param transaction the transaction to parse
    676  * @return #GNUNET_OK on success
    677  */
    678 static enum GNUNET_GenericReturnValue
    679 parse_open (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
    680             struct HistoryParseContext *uc,
    681             const json_t *transaction)
    682 {
    683   struct GNUNET_JSON_Specification open_spec[] = {
    684     GNUNET_JSON_spec_fixed_auto ("reserve_sig",
    685                                  &rh->details.open_request.reserve_sig),
    686     TALER_JSON_spec_amount_any ("open_fee",
    687                                 &rh->details.open_request.reserve_payment),
    688     GNUNET_JSON_spec_uint32 ("requested_min_purses",
    689                              &rh->details.open_request.purse_limit),
    690     GNUNET_JSON_spec_timestamp ("request_timestamp",
    691                                 &rh->details.open_request.request_timestamp),
    692     GNUNET_JSON_spec_timestamp ("requested_expiration",
    693                                 &rh->details.open_request.reserve_expiration),
    694     GNUNET_JSON_spec_end ()
    695   };
    696 
    697   rh->type = TALER_EXCHANGE_RTT_OPEN;
    698   if (GNUNET_OK !=
    699       GNUNET_JSON_parse (transaction,
    700                          open_spec,
    701                          NULL, NULL))
    702   {
    703     GNUNET_break_op (0);
    704     return GNUNET_SYSERR;
    705   }
    706   /* the entry has no "amount" of its own; the amount debited from
    707      the reserve is the reserve payment for the open request */
    708   rh->amount = rh->details.open_request.reserve_payment;
    709   if (GNUNET_OK !=
    710       TALER_wallet_reserve_open_verify (
    711         &rh->details.open_request.reserve_payment,
    712         rh->details.open_request.request_timestamp,
    713         rh->details.open_request.reserve_expiration,
    714         rh->details.open_request.purse_limit,
    715         uc->reserve_pub,
    716         &rh->details.open_request.reserve_sig))
    717   {
    718     GNUNET_break_op (0);
    719     return GNUNET_SYSERR;
    720   }
    721   if (0 >
    722       TALER_amount_add (uc->total_out,
    723                         uc->total_out,
    724                         &rh->details.open_request.reserve_payment))
    725   {
    726     /* overflow in history already!? inconceivable! Bad exchange! */
    727     GNUNET_break_op (0);
    728     return GNUNET_SYSERR;
    729   }
    730   return GNUNET_OK;
    731 }
    732 
    733 
    734 /**
    735  * Parse "close" reserve close entry.
    736  *
    737  * @param[in,out] rh entry to parse
    738  * @param uc our context
    739  * @param transaction the transaction to parse
    740  * @return #GNUNET_OK on success
    741  */
    742 static enum GNUNET_GenericReturnValue
    743 parse_close (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
    744              struct HistoryParseContext *uc,
    745              const json_t *transaction)
    746 {
    747   struct GNUNET_JSON_Specification close_spec[] = {
    748     GNUNET_JSON_spec_fixed_auto ("reserve_sig",
    749                                  &rh->details.close_request.reserve_sig),
    750     GNUNET_JSON_spec_mark_optional (
    751       GNUNET_JSON_spec_fixed_auto ("h_payto",
    752                                    &rh->details.close_request.
    753                                    target_account_h_payto),
    754       NULL),
    755     GNUNET_JSON_spec_timestamp ("request_timestamp",
    756                                 &rh->details.close_request.request_timestamp),
    757     GNUNET_JSON_spec_end ()
    758   };
    759 
    760   rh->type = TALER_EXCHANGE_RTT_CLOSE;
    761   if (GNUNET_OK !=
    762       GNUNET_JSON_parse (transaction,
    763                          close_spec,
    764                          NULL, NULL))
    765   {
    766     GNUNET_break_op (0);
    767     return GNUNET_SYSERR;
    768   }
    769   /* force amount to invalid */
    770   memset (&rh->amount,
    771           0,
    772           sizeof (rh->amount));
    773   if (GNUNET_OK !=
    774       TALER_wallet_reserve_close_verify (
    775         rh->details.close_request.request_timestamp,
    776         &rh->details.close_request.target_account_h_payto,
    777         uc->reserve_pub,
    778         &rh->details.close_request.reserve_sig))
    779   {
    780     GNUNET_break_op (0);
    781     return GNUNET_SYSERR;
    782   }
    783   return GNUNET_OK;
    784 }
    785 
    786 
    787 static void
    788 free_reserve_history (
    789   unsigned int len,
    790   struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static len])
    791 {
    792   for (unsigned int i = 0; i<len; i++)
    793   {
    794     struct TALER_EXCHANGE_ReserveHistoryEntry *rhi = &rhistory[i];
    795 
    796     switch (rhi->type)
    797     {
    798     case TALER_EXCHANGE_RTT_CREDIT:
    799       GNUNET_free (rhi->details.in_details.sender_url.full_payto);
    800       break;
    801     case TALER_EXCHANGE_RTT_WITHDRAWAL:
    802       break;
    803     case TALER_EXCHANGE_RTT_RECOUP:
    804       break;
    805     case TALER_EXCHANGE_RTT_CLOSING:
    806       GNUNET_free (
    807         rhi->details.close_details.receiver_account_details.full_payto);
    808       break;
    809     case TALER_EXCHANGE_RTT_MERGE:
    810       break;
    811     case TALER_EXCHANGE_RTT_OPEN:
    812       break;
    813     case TALER_EXCHANGE_RTT_CLOSE:
    814       break;
    815     }
    816   }
    817   GNUNET_free (rhistory);
    818 }
    819 
    820 
    821 /**
    822  * Parse history given in JSON format and return it in binary format.
    823  *
    824  * @param keys exchange keys
    825  * @param history JSON array with the history
    826  * @param reserve_pub public key of the reserve to inspect
    827  * @param currency currency we expect the balance to be in
    828  * @param[out] total_in set to value of credits to reserve
    829  * @param[out] total_out set to value of debits from reserve
    830  * @param history_length number of entries in @a history
    831  * @param[out] rhistory array of length @a history_length, set to the
    832  *             parsed history entries
    833  * @return #GNUNET_OK if history was valid and @a rhistory and @a balance
    834  *         were set,
    835  *         #GNUNET_SYSERR if there was a protocol violation in @a history
    836  */
    837 static enum GNUNET_GenericReturnValue
    838 parse_reserve_history (
    839   const struct TALER_EXCHANGE_Keys *keys,
    840   const json_t *history,
    841   const struct TALER_ReservePublicKeyP *reserve_pub,
    842   const char *currency,
    843   struct TALER_Amount *total_in,
    844   struct TALER_Amount *total_out,
    845   unsigned int history_length,
    846   struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static history_length])
    847 {
    848   const struct
    849   {
    850     const char *type;
    851     ParseHelper helper;
    852   } map[] = {
    853     { "CREDIT", &parse_credit },
    854     { "WITHDRAW", &parse_withdraw },
    855     { "RECOUP", &parse_recoup },
    856     { "MERGE", &parse_merge },
    857     { "CLOSING", &parse_closing },
    858     { "OPEN", &parse_open },
    859     { "CLOSE", &parse_close },
    860     { NULL, NULL }
    861   };
    862   struct GNUNET_HashCode *uuid
    863     = GNUNET_new_array (history_length,
    864                         struct GNUNET_HashCode);
    865   struct HistoryParseContext uc = {
    866     .keys = keys,
    867     .reserve_pub = reserve_pub,
    868     .uuids = uuid,
    869     .total_in = total_in,
    870     .total_out = total_out
    871   };
    872 
    873   GNUNET_assert (GNUNET_OK ==
    874                  TALER_amount_set_zero (currency,
    875                                         total_in));
    876   GNUNET_assert (GNUNET_OK ==
    877                  TALER_amount_set_zero (currency,
    878                                         total_out));
    879   for (unsigned int off = 0; off<history_length; off++)
    880   {
    881     struct TALER_EXCHANGE_ReserveHistoryEntry *rh = &rhistory[off];
    882     json_t *transaction;
    883     const char *type;
    884     bool no_amount = false;
    885     struct GNUNET_JSON_Specification hist_spec[] = {
    886       GNUNET_JSON_spec_string ("type",
    887                                &type),
    888       /* 'OPEN' and 'CLOSE' entries have no 'amount'! */
    889       GNUNET_JSON_spec_mark_optional (
    890         TALER_JSON_spec_amount_any ("amount",
    891                                     &rh->amount),
    892         &no_amount),
    893       GNUNET_JSON_spec_uint64 ("history_offset",
    894                                &rh->history_offset),
    895       /* 'wire' and 'signature' are optional depending on 'type'! */
    896       GNUNET_JSON_spec_end ()
    897     };
    898     bool found = false;
    899 
    900     transaction = json_array_get (history,
    901                                   off);
    902     if (GNUNET_OK !=
    903         GNUNET_JSON_parse (transaction,
    904                            hist_spec,
    905                            NULL, NULL))
    906     {
    907       GNUNET_break_op (0);
    908 #if DEBUG
    909       json_dumpf (transaction,
    910                   stderr,
    911                   JSON_INDENT (2));
    912 #endif
    913       GNUNET_free (uuid);
    914       return GNUNET_SYSERR;
    915     }
    916     if (no_amount)
    917     {
    918       /* only 'OPEN' and 'CLOSE' entries may omit the amount */
    919       if ( (0 != strcasecmp ("OPEN",
    920                              type)) &&
    921            (0 != strcasecmp ("CLOSE",
    922                              type)) )
    923       {
    924         GNUNET_break_op (0);
    925         GNUNET_free (uuid);
    926         return GNUNET_SYSERR;
    927       }
    928       memset (&rh->amount,
    929               0,
    930               sizeof (rh->amount));
    931     }
    932     else if (GNUNET_YES !=
    933              TALER_amount_cmp_currency (&rh->amount,
    934                                         total_in))
    935     {
    936       GNUNET_break_op (0);
    937       GNUNET_free (uuid);
    938       return GNUNET_SYSERR;
    939     }
    940     for (unsigned int i = 0; NULL != map[i].type; i++)
    941     {
    942       if (0 == strcasecmp (map[i].type,
    943                            type))
    944       {
    945         found = true;
    946         if (GNUNET_OK !=
    947             map[i].helper (rh,
    948                            &uc,
    949                            transaction))
    950         {
    951           GNUNET_break_op (0);
    952           GNUNET_free (uuid);
    953           return GNUNET_SYSERR;
    954         }
    955         break;
    956       }
    957     }
    958     if (! found)
    959     {
    960       /* unexpected 'type', protocol incompatibility, complain! */
    961       GNUNET_break_op (0);
    962       GNUNET_free (uuid);
    963       return GNUNET_SYSERR;
    964     }
    965   }
    966   GNUNET_free (uuid);
    967   return GNUNET_OK;
    968 }
    969 
    970 
    971 /**
    972  * Handle HTTP header received by curl.
    973  *
    974  * @param buffer one line of HTTP header data
    975  * @param size size of an item
    976  * @param nitems number of items passed
    977  * @param userdata our `struct TALER_EXCHANGE_GetReservesHistoryHandle *`
    978  * @return `size * nitems`
    979  */
    980 static size_t
    981 handle_header (char *buffer,
    982                size_t size,
    983                size_t nitems,
    984                void *userdata)
    985 {
    986   struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh = userdata;
    987   size_t total = size * nitems;
    988   char *ndup;
    989   const char *hdr_type;
    990   char *hdr_val;
    991   char *sp;
    992 
    993   ndup = GNUNET_strndup (buffer,
    994                          total);
    995   hdr_type = strtok_r (ndup,
    996                        ":",
    997                        &sp);
    998   if (NULL == hdr_type)
    999   {
   1000     GNUNET_free (ndup);
   1001     return total;
   1002   }
   1003   hdr_val = strtok_r (NULL,
   1004                       "\n\r",
   1005                       &sp);
   1006   if (NULL == hdr_val)
   1007   {
   1008     GNUNET_free (ndup);
   1009     return total;
   1010   }
   1011   if (' ' == *hdr_val)
   1012     hdr_val++;
   1013   if (0 == strcasecmp (hdr_type,
   1014                        MHD_HTTP_HEADER_ETAG))
   1015   {
   1016     unsigned long long tval;
   1017     char dummy;
   1018 
   1019     if (1 !=
   1020         sscanf (hdr_val,
   1021                 "\"%llu\"%c",
   1022                 &tval,
   1023                 &dummy))
   1024     {
   1025       GNUNET_break_op (0);
   1026       GNUNET_free (ndup);
   1027       return 0;
   1028     }
   1029     grhh->etag = (uint64_t) tval;
   1030   }
   1031   GNUNET_free (ndup);
   1032   return total;
   1033 }
   1034 
   1035 
   1036 /**
   1037  * We received an #MHD_HTTP_OK status code. Handle the JSON response.
   1038  *
   1039  * @param grhh handle of the request
   1040  * @param j JSON response
   1041  * @return #GNUNET_OK on success
   1042  */
   1043 static enum GNUNET_GenericReturnValue
   1044 handle_reserves_history_ok (
   1045   struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh,
   1046   const json_t *j)
   1047 {
   1048   const json_t *history;
   1049   unsigned int len;
   1050   struct TALER_EXCHANGE_GetReservesHistoryResponse rs = {
   1051     .hr.reply = j,
   1052     .hr.http_status = MHD_HTTP_OK,
   1053     .details.ok.etag = grhh->etag
   1054   };
   1055   struct GNUNET_JSON_Specification spec[] = {
   1056     TALER_JSON_spec_amount_any ("balance",
   1057                                 &rs.details.ok.balance),
   1058     GNUNET_JSON_spec_array_const ("history",
   1059                                   &history),
   1060     GNUNET_JSON_spec_end ()
   1061   };
   1062 
   1063   if (GNUNET_OK !=
   1064       GNUNET_JSON_parse (j,
   1065                          spec,
   1066                          NULL,
   1067                          NULL))
   1068   {
   1069     GNUNET_break_op (0);
   1070     return GNUNET_SYSERR;
   1071   }
   1072   len = json_array_size (history);
   1073   {
   1074     struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory;
   1075 
   1076     rhistory = GNUNET_new_array (len,
   1077                                  struct TALER_EXCHANGE_ReserveHistoryEntry);
   1078     if (GNUNET_OK !=
   1079         parse_reserve_history (grhh->keys,
   1080                                history,
   1081                                &grhh->reserve_pub,
   1082                                rs.details.ok.balance.currency,
   1083                                &rs.details.ok.total_in,
   1084                                &rs.details.ok.total_out,
   1085                                len,
   1086                                rhistory))
   1087     {
   1088       GNUNET_break_op (0);
   1089       free_reserve_history (len,
   1090                             rhistory);
   1091       GNUNET_JSON_parse_free (spec);
   1092       return GNUNET_SYSERR;
   1093     }
   1094     if (NULL != grhh->cb)
   1095     {
   1096       rs.details.ok.history = rhistory;
   1097       rs.details.ok.history_len = len;
   1098       grhh->cb (grhh->cb_cls,
   1099                 &rs);
   1100       grhh->cb = NULL;
   1101     }
   1102     free_reserve_history (len,
   1103                           rhistory);
   1104   }
   1105   return GNUNET_OK;
   1106 }
   1107 
   1108 
   1109 /**
   1110  * Function called when we're done processing the
   1111  * HTTP GET /reserves/$RESERVE_PUB/history request.
   1112  *
   1113  * @param cls the `struct TALER_EXCHANGE_GetReservesHistoryHandle`
   1114  * @param response_code HTTP response code, 0 on error
   1115  * @param response parsed JSON result, NULL on error
   1116  */
   1117 static void
   1118 handle_reserves_history_finished (void *cls,
   1119                                   long response_code,
   1120                                   const void *response)
   1121 {
   1122   struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh = cls;
   1123   const json_t *j = response;
   1124   struct TALER_EXCHANGE_GetReservesHistoryResponse rs = {
   1125     .hr.reply = j,
   1126     .hr.http_status = (unsigned int) response_code
   1127   };
   1128 
   1129   grhh->job = NULL;
   1130   switch (response_code)
   1131   {
   1132   case 0:
   1133     rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
   1134     break;
   1135   case MHD_HTTP_OK:
   1136     if (GNUNET_OK !=
   1137         handle_reserves_history_ok (grhh,
   1138                                     j))
   1139     {
   1140       rs.hr.http_status = 0;
   1141       rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
   1142     }
   1143     break;
   1144   case MHD_HTTP_BAD_REQUEST:
   1145     /* This should never happen, either us or the exchange is buggy
   1146        (or API version conflict); just pass JSON reply to the application */
   1147     GNUNET_break (0);
   1148     rs.hr.ec = TALER_JSON_get_error_code (j);
   1149     rs.hr.hint = TALER_JSON_get_error_hint (j);
   1150     break;
   1151   case MHD_HTTP_FORBIDDEN:
   1152     /* This should never happen, either us or the exchange is buggy
   1153        (or API version conflict); just pass JSON reply to the application */
   1154     GNUNET_break (0);
   1155     rs.hr.ec = TALER_JSON_get_error_code (j);
   1156     rs.hr.hint = TALER_JSON_get_error_hint (j);
   1157     break;
   1158   case MHD_HTTP_NOT_FOUND:
   1159     /* Nothing really to verify, this should never
   1160        happen, we should pass the JSON reply to the application */
   1161     rs.hr.ec = TALER_JSON_get_error_code (j);
   1162     rs.hr.hint = TALER_JSON_get_error_hint (j);
   1163     break;
   1164   case MHD_HTTP_INTERNAL_SERVER_ERROR:
   1165     /* Server had an internal issue; we should retry, but this API
   1166        leaves this to the application */
   1167     rs.hr.ec = TALER_JSON_get_error_code (j);
   1168     rs.hr.hint = TALER_JSON_get_error_hint (j);
   1169     break;
   1170   default:
   1171     /* unexpected response code */
   1172     GNUNET_break_op (0);
   1173     rs.hr.ec = TALER_JSON_get_error_code (j);
   1174     rs.hr.hint = TALER_JSON_get_error_hint (j);
   1175     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1176                 "Unexpected response code %u/%d for reserves history\n",
   1177                 (unsigned int) response_code,
   1178                 (int) rs.hr.ec);
   1179     break;
   1180   }
   1181   if (NULL != grhh->cb)
   1182   {
   1183     grhh->cb (grhh->cb_cls,
   1184               &rs);
   1185     grhh->cb = NULL;
   1186   }
   1187   TALER_EXCHANGE_get_reserves_history_cancel (grhh);
   1188 }
   1189 
   1190 
   1191 struct TALER_EXCHANGE_GetReservesHistoryHandle *
   1192 TALER_EXCHANGE_get_reserves_history_create (
   1193   struct GNUNET_CURL_Context *ctx,
   1194   const char *url,
   1195   struct TALER_EXCHANGE_Keys *keys,
   1196   const struct TALER_ReservePrivateKeyP *reserve_priv)
   1197 {
   1198   struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh;
   1199 
   1200   grhh = GNUNET_new (struct TALER_EXCHANGE_GetReservesHistoryHandle);
   1201   grhh->ctx = ctx;
   1202   grhh->base_url = GNUNET_strdup (url);
   1203   grhh->keys = TALER_EXCHANGE_keys_incref (keys);
   1204   grhh->reserve_priv = *reserve_priv;
   1205   GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
   1206                                       &grhh->reserve_pub.eddsa_pub);
   1207   return grhh;
   1208 }
   1209 
   1210 
   1211 enum GNUNET_GenericReturnValue
   1212 TALER_EXCHANGE_get_reserves_history_set_options_ (
   1213   struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh,
   1214   unsigned int num_options,
   1215   const struct TALER_EXCHANGE_GetReservesHistoryOptionValue *options)
   1216 {
   1217   for (unsigned int i = 0; i < num_options; i++)
   1218   {
   1219     switch (options[i].option)
   1220     {
   1221     case TALER_EXCHANGE_GET_RESERVES_HISTORY_OPTION_END:
   1222       return GNUNET_OK;
   1223     case TALER_EXCHANGE_GET_RESERVES_HISTORY_OPTION_START_OFF:
   1224       grhh->options.start_off = options[i].details.start_off;
   1225       break;
   1226     default:
   1227       GNUNET_break (0);
   1228       return GNUNET_SYSERR;
   1229     }
   1230   }
   1231   return GNUNET_OK;
   1232 }
   1233 
   1234 
   1235 enum TALER_ErrorCode
   1236 TALER_EXCHANGE_get_reserves_history_start (
   1237   struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh,
   1238   TALER_EXCHANGE_GetReservesHistoryCallback cb,
   1239   TALER_EXCHANGE_GET_RESERVES_HISTORY_RESULT_CLOSURE *cb_cls)
   1240 {
   1241   char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
   1242   struct curl_slist *job_headers;
   1243   CURL *eh;
   1244 
   1245   if (NULL != grhh->job)
   1246   {
   1247     GNUNET_break (0);
   1248     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
   1249   }
   1250   grhh->cb = cb;
   1251   grhh->cb_cls = cb_cls;
   1252   {
   1253     char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
   1254     char *end;
   1255     char start_off_str[32];
   1256 
   1257     end = GNUNET_STRINGS_data_to_string (
   1258       &grhh->reserve_pub,
   1259       sizeof (grhh->reserve_pub),
   1260       pub_str,
   1261       sizeof (pub_str));
   1262     *end = '\0';
   1263     GNUNET_snprintf (arg_str,
   1264                      sizeof (arg_str),
   1265                      "reserves/%s/history",
   1266                      pub_str);
   1267     GNUNET_snprintf (start_off_str,
   1268                      sizeof (start_off_str),
   1269                      "%llu",
   1270                      (unsigned long long) grhh->options.start_off);
   1271     grhh->url = TALER_url_join (grhh->base_url,
   1272                                 arg_str,
   1273                                 "start",
   1274                                 (0 != grhh->options.start_off)
   1275                                  ? start_off_str
   1276                                  : NULL,
   1277                                 NULL);
   1278   }
   1279   if (NULL == grhh->url)
   1280     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
   1281   eh = TALER_EXCHANGE_curl_easy_get_ (grhh->url);
   1282   if (NULL == eh)
   1283   {
   1284     GNUNET_free (grhh->url);
   1285     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
   1286   }
   1287   GNUNET_assert (CURLE_OK ==
   1288                  curl_easy_setopt (eh,
   1289                                    CURLOPT_HEADERFUNCTION,
   1290                                    &handle_header));
   1291   GNUNET_assert (CURLE_OK ==
   1292                  curl_easy_setopt (eh,
   1293                                    CURLOPT_HEADERDATA,
   1294                                    grhh));
   1295   {
   1296     struct TALER_ReserveSignatureP reserve_sig;
   1297     char *sig_hdr;
   1298     char *hdr;
   1299 
   1300     TALER_wallet_reserve_history_sign (grhh->options.start_off,
   1301                                        &grhh->reserve_priv,
   1302                                        &reserve_sig);
   1303     sig_hdr = GNUNET_STRINGS_data_to_string_alloc (
   1304       &reserve_sig,
   1305       sizeof (reserve_sig));
   1306     GNUNET_asprintf (&hdr,
   1307                      "%s: %s",
   1308                      TALER_RESERVE_HISTORY_SIGNATURE_HEADER,
   1309                      sig_hdr);
   1310     GNUNET_free (sig_hdr);
   1311     job_headers = curl_slist_append (NULL,
   1312                                      hdr);
   1313     GNUNET_free (hdr);
   1314     if (NULL == job_headers)
   1315     {
   1316       GNUNET_break (0);
   1317       curl_easy_cleanup (eh);
   1318       return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
   1319     }
   1320   }
   1321   grhh->job = GNUNET_CURL_job_add2 (grhh->ctx,
   1322                                     eh,
   1323                                     job_headers,
   1324                                     &handle_reserves_history_finished,
   1325                                     grhh);
   1326   curl_slist_free_all (job_headers);
   1327   if (NULL == grhh->job)
   1328     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
   1329   return TALER_EC_NONE;
   1330 }
   1331 
   1332 
   1333 void
   1334 TALER_EXCHANGE_get_reserves_history_cancel (
   1335   struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh)
   1336 {
   1337   if (NULL != grhh->job)
   1338   {
   1339     GNUNET_CURL_job_cancel (grhh->job);
   1340     grhh->job = NULL;
   1341   }
   1342   GNUNET_free (grhh->url);
   1343   GNUNET_free (grhh->base_url);
   1344   TALER_EXCHANGE_keys_decref (grhh->keys);
   1345   GNUNET_free (grhh);
   1346 }
   1347 
   1348 
   1349 /* end of exchange_api_get-reserves-RESERVE_PUB-history.c */