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


      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   if (! no_selected_h)
    329     rh->details.withdraw.selected_h = selected_h;
    330   rh->details.withdraw.noreveal_index = noreveal_index;
    331   rh->details.withdraw.no_blinding_seed = no_blinding_seed;
    332   rh->details.withdraw.reserve_sig = reserve_sig;
    333   if (! no_blinding_seed)
    334     rh->details.withdraw.blinding_seed = blinding_seed;
    335 
    336   /* check that withdraw fee matches expectations! */
    337   {
    338     const struct TALER_EXCHANGE_Keys *key_state;
    339     struct TALER_Amount fee_acc;
    340     struct TALER_Amount amount_acc;
    341 
    342     GNUNET_assert (GNUNET_OK ==
    343                    TALER_amount_set_zero (withdraw_amount.currency,
    344                                           &fee_acc));
    345     GNUNET_assert (GNUNET_OK ==
    346                    TALER_amount_set_zero (withdraw_amount.currency,
    347                                           &amount_acc));
    348 
    349     key_state = uc->keys;
    350 
    351     /* accumulate the withdraw fees */
    352     for (size_t i=0; i < num_coins; i++)
    353     {
    354       const struct TALER_EXCHANGE_DenomPublicKey *dki;
    355 
    356       dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state,
    357                                                          &denom_pub_hashes[i]);
    358       if (NULL == dki)
    359       {
    360         GNUNET_break_op (0);
    361         GNUNET_JSON_parse_free (withdraw_spec);
    362         return GNUNET_SYSERR;
    363       }
    364       if ( (0 >
    365             TALER_amount_add (&fee_acc,
    366                               &fee_acc,
    367                               &dki->fees.withdraw)) ||
    368            (0 >
    369             TALER_amount_add (&amount_acc,
    370                               &amount_acc,
    371                               &dki->value)) )
    372       {
    373         GNUNET_break_op (0);
    374         GNUNET_JSON_parse_free (withdraw_spec);
    375         return GNUNET_SYSERR;
    376       }
    377     }
    378 
    379     if ( (GNUNET_YES !=
    380           TALER_amount_cmp_currency (&fee_acc,
    381                                      &withdraw_fee)) ||
    382          (0 !=
    383           TALER_amount_cmp (&fee_acc,
    384                             &withdraw_fee)) ||
    385          (0 !=
    386           TALER_amount_cmp (&amount_acc,
    387                             &amount_without_fee)) )
    388     {
    389       GNUNET_break_op (0);
    390       GNUNET_JSON_parse_free (withdraw_spec);
    391       return GNUNET_SYSERR;
    392     }
    393     rh->details.withdraw.fee = withdraw_fee;
    394   }
    395 
    396   /* Check check that the same withdraw transaction
    397        isn't listed twice by the exchange. We use the
    398        "uuid" array to remember the hashes of all
    399        signatures, and compare the hashes to find
    400        duplicates. */
    401   GNUNET_CRYPTO_hash (&reserve_sig,
    402                       sizeof (reserve_sig),
    403                       &uc->uuids[uc->uuid_off]);
    404   for (unsigned int i = 0; i<uc->uuid_off; i++)
    405   {
    406     if (0 == GNUNET_memcmp (&uc->uuids[uc->uuid_off],
    407                             &uc->uuids[i]))
    408     {
    409       GNUNET_break_op (0);
    410       GNUNET_JSON_parse_free (withdraw_spec);
    411       return GNUNET_SYSERR;
    412     }
    413   }
    414   uc->uuid_off++;
    415 
    416   if (0 >
    417       TALER_amount_add (uc->total_out,
    418                         uc->total_out,
    419                         &rh->amount))
    420   {
    421     /* overflow in history already!? inconceivable! Bad exchange! */
    422     GNUNET_break_op (0);
    423     GNUNET_JSON_parse_free (withdraw_spec);
    424     return GNUNET_SYSERR;
    425   }
    426   GNUNET_JSON_parse_free (withdraw_spec);
    427   return GNUNET_OK;
    428 }
    429 
    430 
    431 /**
    432  * Parse "recoup" reserve history entry.
    433  *
    434  * @param[in,out] rh entry to parse
    435  * @param uc our context
    436  * @param transaction the transaction to parse
    437  * @return #GNUNET_OK on success
    438  */
    439 static enum GNUNET_GenericReturnValue
    440 parse_recoup (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
    441               struct HistoryParseContext *uc,
    442               const json_t *transaction)
    443 {
    444   const struct TALER_EXCHANGE_Keys *key_state;
    445   struct GNUNET_JSON_Specification recoup_spec[] = {
    446     GNUNET_JSON_spec_fixed_auto ("coin_pub",
    447                                  &rh->details.recoup_details.coin_pub),
    448     GNUNET_JSON_spec_fixed_auto ("exchange_sig",
    449                                  &rh->details.recoup_details.exchange_sig),
    450     GNUNET_JSON_spec_fixed_auto ("exchange_pub",
    451                                  &rh->details.recoup_details.exchange_pub),
    452     GNUNET_JSON_spec_timestamp ("timestamp",
    453                                 &rh->details.recoup_details.timestamp),
    454     GNUNET_JSON_spec_fixed_auto ("planchets_h",
    455                                  &rh->details.recoup_details.planchets_h),
    456     GNUNET_JSON_spec_end ()
    457   };
    458 
    459   rh->type = TALER_EXCHANGE_RTT_RECOUP;
    460   if (GNUNET_OK !=
    461       GNUNET_JSON_parse (transaction,
    462                          recoup_spec,
    463                          NULL, NULL))
    464   {
    465     GNUNET_break_op (0);
    466     return GNUNET_SYSERR;
    467   }
    468   key_state = uc->keys;
    469   if (GNUNET_OK !=
    470       TALER_EXCHANGE_test_signing_key (key_state,
    471                                        &rh->details.
    472                                        recoup_details.exchange_pub))
    473   {
    474     GNUNET_break_op (0);
    475     return GNUNET_SYSERR;
    476   }
    477   if (GNUNET_OK !=
    478       TALER_exchange_online_confirm_recoup_verify (
    479         rh->details.recoup_details.timestamp,
    480         &rh->amount,
    481         &rh->details.recoup_details.coin_pub,
    482         uc->reserve_pub,
    483         &rh->details.recoup_details.exchange_pub,
    484         &rh->details.recoup_details.exchange_sig))
    485   {
    486     GNUNET_break_op (0);
    487     return GNUNET_SYSERR;
    488   }
    489   if (0 >
    490       TALER_amount_add (uc->total_in,
    491                         uc->total_in,
    492                         &rh->amount))
    493   {
    494     /* overflow in history already!? inconceivable! Bad exchange! */
    495     GNUNET_break_op (0);
    496     return GNUNET_SYSERR;
    497   }
    498   return GNUNET_OK;
    499 }
    500 
    501 
    502 /**
    503  * Parse "closing" reserve history entry.
    504  *
    505  * @param[in,out] rh entry to parse
    506  * @param uc our context
    507  * @param transaction the transaction to parse
    508  * @return #GNUNET_OK on success
    509  */
    510 static enum GNUNET_GenericReturnValue
    511 parse_closing (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
    512                struct HistoryParseContext *uc,
    513                const json_t *transaction)
    514 {
    515   const struct TALER_EXCHANGE_Keys *key_state;
    516   struct TALER_FullPayto receiver_uri;
    517   struct GNUNET_JSON_Specification closing_spec[] = {
    518     TALER_JSON_spec_full_payto_uri (
    519       "receiver_account_details",
    520       &receiver_uri),
    521     GNUNET_JSON_spec_fixed_auto ("wtid",
    522                                  &rh->details.close_details.wtid),
    523     GNUNET_JSON_spec_fixed_auto ("exchange_sig",
    524                                  &rh->details.close_details.exchange_sig),
    525     GNUNET_JSON_spec_fixed_auto ("exchange_pub",
    526                                  &rh->details.close_details.exchange_pub),
    527     TALER_JSON_spec_amount_any ("closing_fee",
    528                                 &rh->details.close_details.fee),
    529     GNUNET_JSON_spec_timestamp ("timestamp",
    530                                 &rh->details.close_details.timestamp),
    531     GNUNET_JSON_spec_end ()
    532   };
    533 
    534   rh->type = TALER_EXCHANGE_RTT_CLOSING;
    535   if (GNUNET_OK !=
    536       GNUNET_JSON_parse (transaction,
    537                          closing_spec,
    538                          NULL, NULL))
    539   {
    540     GNUNET_break_op (0);
    541     return GNUNET_SYSERR;
    542   }
    543   key_state = uc->keys;
    544   if (GNUNET_OK !=
    545       TALER_EXCHANGE_test_signing_key (
    546         key_state,
    547         &rh->details.close_details.exchange_pub))
    548   {
    549     GNUNET_break_op (0);
    550     return GNUNET_SYSERR;
    551   }
    552   if (GNUNET_OK !=
    553       TALER_exchange_online_reserve_closed_verify (
    554         rh->details.close_details.timestamp,
    555         &rh->amount,
    556         &rh->details.close_details.fee,
    557         receiver_uri,
    558         &rh->details.close_details.wtid,
    559         uc->reserve_pub,
    560         &rh->details.close_details.exchange_pub,
    561         &rh->details.close_details.exchange_sig))
    562   {
    563     GNUNET_break_op (0);
    564     return GNUNET_SYSERR;
    565   }
    566   if (0 >
    567       TALER_amount_add (uc->total_out,
    568                         uc->total_out,
    569                         &rh->amount))
    570   {
    571     /* overflow in history already!? inconceivable! Bad exchange! */
    572     GNUNET_break_op (0);
    573     return GNUNET_SYSERR;
    574   }
    575   rh->details.close_details.receiver_account_details.full_payto
    576     = GNUNET_strdup (receiver_uri.full_payto);
    577   return GNUNET_OK;
    578 }
    579 
    580 
    581 /**
    582  * Parse "merge" reserve history entry.
    583  *
    584  * @param[in,out] rh entry to parse
    585  * @param uc our context
    586  * @param transaction the transaction to parse
    587  * @return #GNUNET_OK on success
    588  */
    589 static enum GNUNET_GenericReturnValue
    590 parse_merge (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
    591              struct HistoryParseContext *uc,
    592              const json_t *transaction)
    593 {
    594   uint32_t flags32;
    595   struct GNUNET_JSON_Specification merge_spec[] = {
    596     GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
    597                                  &rh->details.merge_details.h_contract_terms),
    598     GNUNET_JSON_spec_fixed_auto ("merge_pub",
    599                                  &rh->details.merge_details.merge_pub),
    600     GNUNET_JSON_spec_fixed_auto ("purse_pub",
    601                                  &rh->details.merge_details.purse_pub),
    602     GNUNET_JSON_spec_uint32 ("min_age",
    603                              &rh->details.merge_details.min_age),
    604     GNUNET_JSON_spec_uint32 ("flags",
    605                              &flags32),
    606     GNUNET_JSON_spec_fixed_auto ("reserve_sig",
    607                                  &rh->details.merge_details.reserve_sig),
    608     TALER_JSON_spec_amount_any ("purse_fee",
    609                                 &rh->details.merge_details.purse_fee),
    610     GNUNET_JSON_spec_timestamp ("merge_timestamp",
    611                                 &rh->details.merge_details.merge_timestamp),
    612     GNUNET_JSON_spec_timestamp ("purse_expiration",
    613                                 &rh->details.merge_details.purse_expiration),
    614     GNUNET_JSON_spec_bool ("merged",
    615                            &rh->details.merge_details.merged),
    616     GNUNET_JSON_spec_end ()
    617   };
    618 
    619   rh->type = TALER_EXCHANGE_RTT_MERGE;
    620   if (GNUNET_OK !=
    621       GNUNET_JSON_parse (transaction,
    622                          merge_spec,
    623                          NULL, NULL))
    624   {
    625     GNUNET_break_op (0);
    626     return GNUNET_SYSERR;
    627   }
    628   rh->details.merge_details.flags =
    629     (enum TALER_WalletAccountMergeFlags) flags32;
    630   if (GNUNET_OK !=
    631       TALER_wallet_account_merge_verify (
    632         rh->details.merge_details.merge_timestamp,
    633         &rh->details.merge_details.purse_pub,
    634         rh->details.merge_details.purse_expiration,
    635         &rh->details.merge_details.h_contract_terms,
    636         &rh->amount,
    637         &rh->details.merge_details.purse_fee,
    638         rh->details.merge_details.min_age,
    639         rh->details.merge_details.flags,
    640         uc->reserve_pub,
    641         &rh->details.merge_details.reserve_sig))
    642   {
    643     GNUNET_break_op (0);
    644     return GNUNET_SYSERR;
    645   }
    646   if (rh->details.merge_details.merged)
    647   {
    648     if (0 >
    649         TALER_amount_add (uc->total_in,
    650                           uc->total_in,
    651                           &rh->amount))
    652     {
    653       /* overflow in history already!? inconceivable! Bad exchange! */
    654       GNUNET_break_op (0);
    655       return GNUNET_SYSERR;
    656     }
    657   }
    658   else
    659   {
    660     if (0 >
    661         TALER_amount_add (uc->total_out,
    662                           uc->total_out,
    663                           &rh->details.merge_details.purse_fee))
    664     {
    665       /* overflow in history already!? inconceivable! Bad exchange! */
    666       GNUNET_break_op (0);
    667       return GNUNET_SYSERR;
    668     }
    669   }
    670   return GNUNET_OK;
    671 }
    672 
    673 
    674 /**
    675  * Parse "open" reserve open entry.
    676  *
    677  * @param[in,out] rh entry to parse
    678  * @param uc our context
    679  * @param transaction the transaction to parse
    680  * @return #GNUNET_OK on success
    681  */
    682 static enum GNUNET_GenericReturnValue
    683 parse_open (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
    684             struct HistoryParseContext *uc,
    685             const json_t *transaction)
    686 {
    687   struct GNUNET_JSON_Specification open_spec[] = {
    688     GNUNET_JSON_spec_fixed_auto ("reserve_sig",
    689                                  &rh->details.open_request.reserve_sig),
    690     TALER_JSON_spec_amount_any ("open_fee",
    691                                 &rh->details.open_request.reserve_payment),
    692     GNUNET_JSON_spec_uint32 ("requested_min_purses",
    693                              &rh->details.open_request.purse_limit),
    694     GNUNET_JSON_spec_timestamp ("request_timestamp",
    695                                 &rh->details.open_request.request_timestamp),
    696     GNUNET_JSON_spec_timestamp ("requested_expiration",
    697                                 &rh->details.open_request.reserve_expiration),
    698     GNUNET_JSON_spec_end ()
    699   };
    700 
    701   rh->type = TALER_EXCHANGE_RTT_OPEN;
    702   if (GNUNET_OK !=
    703       GNUNET_JSON_parse (transaction,
    704                          open_spec,
    705                          NULL, NULL))
    706   {
    707     GNUNET_break_op (0);
    708     return GNUNET_SYSERR;
    709   }
    710   /* the entry has no "amount" of its own; the amount debited from
    711      the reserve is the reserve payment for the open request */
    712   rh->amount = rh->details.open_request.reserve_payment;
    713   if (GNUNET_OK !=
    714       TALER_wallet_reserve_open_verify (
    715         &rh->details.open_request.reserve_payment,
    716         rh->details.open_request.request_timestamp,
    717         rh->details.open_request.reserve_expiration,
    718         rh->details.open_request.purse_limit,
    719         uc->reserve_pub,
    720         &rh->details.open_request.reserve_sig))
    721   {
    722     GNUNET_break_op (0);
    723     return GNUNET_SYSERR;
    724   }
    725   if (0 >
    726       TALER_amount_add (uc->total_out,
    727                         uc->total_out,
    728                         &rh->details.open_request.reserve_payment))
    729   {
    730     /* overflow in history already!? inconceivable! Bad exchange! */
    731     GNUNET_break_op (0);
    732     return GNUNET_SYSERR;
    733   }
    734   return GNUNET_OK;
    735 }
    736 
    737 
    738 /**
    739  * Parse "close" reserve close entry.
    740  *
    741  * @param[in,out] rh entry to parse
    742  * @param uc our context
    743  * @param transaction the transaction to parse
    744  * @return #GNUNET_OK on success
    745  */
    746 static enum GNUNET_GenericReturnValue
    747 parse_close (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
    748              struct HistoryParseContext *uc,
    749              const json_t *transaction)
    750 {
    751   struct GNUNET_JSON_Specification close_spec[] = {
    752     GNUNET_JSON_spec_fixed_auto ("reserve_sig",
    753                                  &rh->details.close_request.reserve_sig),
    754     GNUNET_JSON_spec_mark_optional (
    755       GNUNET_JSON_spec_fixed_auto ("h_payto",
    756                                    &rh->details.close_request.
    757                                    target_account_h_payto),
    758       NULL),
    759     GNUNET_JSON_spec_timestamp ("request_timestamp",
    760                                 &rh->details.close_request.request_timestamp),
    761     GNUNET_JSON_spec_end ()
    762   };
    763 
    764   rh->type = TALER_EXCHANGE_RTT_CLOSE;
    765   if (GNUNET_OK !=
    766       GNUNET_JSON_parse (transaction,
    767                          close_spec,
    768                          NULL, NULL))
    769   {
    770     GNUNET_break_op (0);
    771     return GNUNET_SYSERR;
    772   }
    773   /* force amount to invalid */
    774   memset (&rh->amount,
    775           0,
    776           sizeof (rh->amount));
    777   if (GNUNET_OK !=
    778       TALER_wallet_reserve_close_verify (
    779         rh->details.close_request.request_timestamp,
    780         &rh->details.close_request.target_account_h_payto,
    781         uc->reserve_pub,
    782         &rh->details.close_request.reserve_sig))
    783   {
    784     GNUNET_break_op (0);
    785     return GNUNET_SYSERR;
    786   }
    787   return GNUNET_OK;
    788 }
    789 
    790 
    791 static void
    792 free_reserve_history (
    793   unsigned int len,
    794   struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static len])
    795 {
    796   for (unsigned int i = 0; i<len; i++)
    797   {
    798     struct TALER_EXCHANGE_ReserveHistoryEntry *rhi = &rhistory[i];
    799 
    800     switch (rhi->type)
    801     {
    802     case TALER_EXCHANGE_RTT_CREDIT:
    803       GNUNET_free (rhi->details.in_details.sender_url.full_payto);
    804       break;
    805     case TALER_EXCHANGE_RTT_WITHDRAWAL:
    806       break;
    807     case TALER_EXCHANGE_RTT_RECOUP:
    808       break;
    809     case TALER_EXCHANGE_RTT_CLOSING:
    810       GNUNET_free (
    811         rhi->details.close_details.receiver_account_details.full_payto);
    812       break;
    813     case TALER_EXCHANGE_RTT_MERGE:
    814       break;
    815     case TALER_EXCHANGE_RTT_OPEN:
    816       break;
    817     case TALER_EXCHANGE_RTT_CLOSE:
    818       break;
    819     }
    820   }
    821   GNUNET_free (rhistory);
    822 }
    823 
    824 
    825 /**
    826  * Parse history given in JSON format and return it in binary format.
    827  *
    828  * @param keys exchange keys
    829  * @param history JSON array with the history
    830  * @param reserve_pub public key of the reserve to inspect
    831  * @param currency currency we expect the balance to be in
    832  * @param[out] total_in set to value of credits to reserve
    833  * @param[out] total_out set to value of debits from reserve
    834  * @param history_length number of entries in @a history
    835  * @param[out] rhistory array of length @a history_length, set to the
    836  *             parsed history entries
    837  * @return #GNUNET_OK if history was valid and @a rhistory and @a balance
    838  *         were set,
    839  *         #GNUNET_SYSERR if there was a protocol violation in @a history
    840  */
    841 static enum GNUNET_GenericReturnValue
    842 parse_reserve_history (
    843   const struct TALER_EXCHANGE_Keys *keys,
    844   const json_t *history,
    845   const struct TALER_ReservePublicKeyP *reserve_pub,
    846   const char *currency,
    847   struct TALER_Amount *total_in,
    848   struct TALER_Amount *total_out,
    849   unsigned int history_length,
    850   struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static history_length])
    851 {
    852   const struct
    853   {
    854     const char *type;
    855     ParseHelper helper;
    856   } map[] = {
    857     { "CREDIT", &parse_credit },
    858     { "WITHDRAW", &parse_withdraw },
    859     { "RECOUP", &parse_recoup },
    860     { "MERGE", &parse_merge },
    861     { "CLOSING", &parse_closing },
    862     { "OPEN", &parse_open },
    863     { "CLOSE", &parse_close },
    864     { NULL, NULL }
    865   };
    866   struct GNUNET_HashCode *uuid
    867     = GNUNET_new_array (history_length,
    868                         struct GNUNET_HashCode);
    869   struct HistoryParseContext uc = {
    870     .keys = keys,
    871     .reserve_pub = reserve_pub,
    872     .uuids = uuid,
    873     .total_in = total_in,
    874     .total_out = total_out
    875   };
    876 
    877   GNUNET_assert (GNUNET_OK ==
    878                  TALER_amount_set_zero (currency,
    879                                         total_in));
    880   GNUNET_assert (GNUNET_OK ==
    881                  TALER_amount_set_zero (currency,
    882                                         total_out));
    883   for (unsigned int off = 0; off<history_length; off++)
    884   {
    885     struct TALER_EXCHANGE_ReserveHistoryEntry *rh = &rhistory[off];
    886     json_t *transaction;
    887     const char *type;
    888     bool no_amount = false;
    889     struct GNUNET_JSON_Specification hist_spec[] = {
    890       GNUNET_JSON_spec_string ("type",
    891                                &type),
    892       /* 'OPEN' and 'CLOSE' entries have no 'amount'! */
    893       GNUNET_JSON_spec_mark_optional (
    894         TALER_JSON_spec_amount_any ("amount",
    895                                     &rh->amount),
    896         &no_amount),
    897       GNUNET_JSON_spec_uint64 ("history_offset",
    898                                &rh->history_offset),
    899       /* 'wire' and 'signature' are optional depending on 'type'! */
    900       GNUNET_JSON_spec_end ()
    901     };
    902     bool found = false;
    903 
    904     transaction = json_array_get (history,
    905                                   off);
    906     if (GNUNET_OK !=
    907         GNUNET_JSON_parse (transaction,
    908                            hist_spec,
    909                            NULL, NULL))
    910     {
    911       GNUNET_break_op (0);
    912 #if DEBUG
    913       json_dumpf (transaction,
    914                   stderr,
    915                   JSON_INDENT (2));
    916 #endif
    917       GNUNET_free (uuid);
    918       return GNUNET_SYSERR;
    919     }
    920     if (no_amount)
    921     {
    922       /* only 'OPEN' and 'CLOSE' entries may omit the amount */
    923       if ( (0 != strcasecmp ("OPEN",
    924                              type)) &&
    925            (0 != strcasecmp ("CLOSE",
    926                              type)) )
    927       {
    928         GNUNET_break_op (0);
    929         GNUNET_free (uuid);
    930         return GNUNET_SYSERR;
    931       }
    932       memset (&rh->amount,
    933               0,
    934               sizeof (rh->amount));
    935     }
    936     else if (GNUNET_YES !=
    937              TALER_amount_cmp_currency (&rh->amount,
    938                                         total_in))
    939     {
    940       GNUNET_break_op (0);
    941       GNUNET_free (uuid);
    942       return GNUNET_SYSERR;
    943     }
    944     for (unsigned int i = 0; NULL != map[i].type; i++)
    945     {
    946       if (0 == strcasecmp (map[i].type,
    947                            type))
    948       {
    949         found = true;
    950         if (GNUNET_OK !=
    951             map[i].helper (rh,
    952                            &uc,
    953                            transaction))
    954         {
    955           GNUNET_break_op (0);
    956           GNUNET_free (uuid);
    957           return GNUNET_SYSERR;
    958         }
    959         break;
    960       }
    961     }
    962     if (! found)
    963     {
    964       /* unexpected 'type', protocol incompatibility, complain! */
    965       GNUNET_break_op (0);
    966       GNUNET_free (uuid);
    967       return GNUNET_SYSERR;
    968     }
    969   }
    970   GNUNET_free (uuid);
    971   return GNUNET_OK;
    972 }
    973 
    974 
    975 /**
    976  * Handle HTTP header received by curl.
    977  *
    978  * @param buffer one line of HTTP header data
    979  * @param size size of an item
    980  * @param nitems number of items passed
    981  * @param userdata our `struct TALER_EXCHANGE_GetReservesHistoryHandle *`
    982  * @return `size * nitems`
    983  */
    984 static size_t
    985 handle_header (char *buffer,
    986                size_t size,
    987                size_t nitems,
    988                void *userdata)
    989 {
    990   struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh = userdata;
    991   size_t total = size * nitems;
    992   char *ndup;
    993   const char *hdr_type;
    994   char *hdr_val;
    995   char *sp;
    996 
    997   ndup = GNUNET_strndup (buffer,
    998                          total);
    999   hdr_type = strtok_r (ndup,
   1000                        ":",
   1001                        &sp);
   1002   if (NULL == hdr_type)
   1003   {
   1004     GNUNET_free (ndup);
   1005     return total;
   1006   }
   1007   hdr_val = strtok_r (NULL,
   1008                       "\n\r",
   1009                       &sp);
   1010   if (NULL == hdr_val)
   1011   {
   1012     GNUNET_free (ndup);
   1013     return total;
   1014   }
   1015   if (' ' == *hdr_val)
   1016     hdr_val++;
   1017   if (0 == strcasecmp (hdr_type,
   1018                        MHD_HTTP_HEADER_ETAG))
   1019   {
   1020     unsigned long long tval;
   1021     char dummy;
   1022 
   1023     if (1 !=
   1024         sscanf (hdr_val,
   1025                 "\"%llu\"%c",
   1026                 &tval,
   1027                 &dummy))
   1028     {
   1029       /* Not an ETag we understand (weak validators are permitted by
   1030          RFC 9110, Section 8.8.3); ignore it instead of aborting the
   1031          transfer with a write error. */
   1032       GNUNET_break_op (0);
   1033       GNUNET_free (ndup);
   1034       return total;
   1035     }
   1036     grhh->etag = (uint64_t) tval;
   1037   }
   1038   GNUNET_free (ndup);
   1039   return total;
   1040 }
   1041 
   1042 
   1043 /**
   1044  * We received an #MHD_HTTP_OK status code. Handle the JSON response.
   1045  *
   1046  * @param grhh handle of the request
   1047  * @param j JSON response
   1048  * @return #GNUNET_OK on success
   1049  */
   1050 static enum GNUNET_GenericReturnValue
   1051 handle_reserves_history_ok (
   1052   struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh,
   1053   const json_t *j)
   1054 {
   1055   const json_t *history;
   1056   unsigned int len;
   1057   struct TALER_EXCHANGE_GetReservesHistoryResponse rs = {
   1058     .hr.reply = j,
   1059     .hr.http_status = MHD_HTTP_OK,
   1060     .details.ok.etag = grhh->etag
   1061   };
   1062   struct GNUNET_JSON_Specification spec[] = {
   1063     TALER_JSON_spec_amount_any ("balance",
   1064                                 &rs.details.ok.balance),
   1065     GNUNET_JSON_spec_array_const ("history",
   1066                                   &history),
   1067     GNUNET_JSON_spec_end ()
   1068   };
   1069 
   1070   if (GNUNET_OK !=
   1071       GNUNET_JSON_parse (j,
   1072                          spec,
   1073                          NULL,
   1074                          NULL))
   1075   {
   1076     GNUNET_break_op (0);
   1077     return GNUNET_SYSERR;
   1078   }
   1079   len = json_array_size (history);
   1080   {
   1081     struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory;
   1082 
   1083     rhistory = GNUNET_new_array (len,
   1084                                  struct TALER_EXCHANGE_ReserveHistoryEntry);
   1085     if (GNUNET_OK !=
   1086         parse_reserve_history (grhh->keys,
   1087                                history,
   1088                                &grhh->reserve_pub,
   1089                                rs.details.ok.balance.currency,
   1090                                &rs.details.ok.total_in,
   1091                                &rs.details.ok.total_out,
   1092                                len,
   1093                                rhistory))
   1094     {
   1095       GNUNET_break_op (0);
   1096       free_reserve_history (len,
   1097                             rhistory);
   1098       GNUNET_JSON_parse_free (spec);
   1099       return GNUNET_SYSERR;
   1100     }
   1101     if (NULL != grhh->cb)
   1102     {
   1103       rs.details.ok.history = rhistory;
   1104       rs.details.ok.history_len = len;
   1105       grhh->cb (grhh->cb_cls,
   1106                 &rs);
   1107       grhh->cb = NULL;
   1108     }
   1109     free_reserve_history (len,
   1110                           rhistory);
   1111   }
   1112   return GNUNET_OK;
   1113 }
   1114 
   1115 
   1116 /**
   1117  * Function called when we're done processing the
   1118  * HTTP GET /reserves/$RESERVE_PUB/history request.
   1119  *
   1120  * @param cls the `struct TALER_EXCHANGE_GetReservesHistoryHandle`
   1121  * @param response_code HTTP response code, 0 on error
   1122  * @param response parsed JSON result, NULL on error
   1123  */
   1124 static void
   1125 handle_reserves_history_finished (void *cls,
   1126                                   long response_code,
   1127                                   const void *response)
   1128 {
   1129   struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh = cls;
   1130   const json_t *j = response;
   1131   struct TALER_EXCHANGE_GetReservesHistoryResponse rs = {
   1132     .hr.reply = j,
   1133     .hr.http_status = (unsigned int) response_code
   1134   };
   1135 
   1136   grhh->job = NULL;
   1137   switch (response_code)
   1138   {
   1139   case 0:
   1140     rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
   1141     break;
   1142   case MHD_HTTP_OK:
   1143     if (GNUNET_OK !=
   1144         handle_reserves_history_ok (grhh,
   1145                                     j))
   1146     {
   1147       rs.hr.http_status = 0;
   1148       rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
   1149     }
   1150     break;
   1151   case MHD_HTTP_BAD_REQUEST:
   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_FORBIDDEN:
   1159     /* This should never happen, either us or the exchange is buggy
   1160        (or API version conflict); just pass JSON reply to the application */
   1161     GNUNET_break (0);
   1162     rs.hr.ec = TALER_JSON_get_error_code (j);
   1163     rs.hr.hint = TALER_JSON_get_error_hint (j);
   1164     break;
   1165   case MHD_HTTP_NOT_FOUND:
   1166     /* Nothing really to verify, this should never
   1167        happen, we should pass the JSON reply to the application */
   1168     rs.hr.ec = TALER_JSON_get_error_code (j);
   1169     rs.hr.hint = TALER_JSON_get_error_hint (j);
   1170     break;
   1171   case MHD_HTTP_INTERNAL_SERVER_ERROR:
   1172     /* Server had an internal issue; we should retry, but this API
   1173        leaves this to the application */
   1174     rs.hr.ec = TALER_JSON_get_error_code (j);
   1175     rs.hr.hint = TALER_JSON_get_error_hint (j);
   1176     break;
   1177   default:
   1178     /* unexpected response code */
   1179     GNUNET_break_op (0);
   1180     rs.hr.ec = TALER_JSON_get_error_code (j);
   1181     rs.hr.hint = TALER_JSON_get_error_hint (j);
   1182     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1183                 "Unexpected response code %u/%d for reserves history\n",
   1184                 (unsigned int) response_code,
   1185                 (int) rs.hr.ec);
   1186     break;
   1187   }
   1188   if (NULL != grhh->cb)
   1189   {
   1190     grhh->cb (grhh->cb_cls,
   1191               &rs);
   1192     grhh->cb = NULL;
   1193   }
   1194   TALER_EXCHANGE_get_reserves_history_cancel (grhh);
   1195 }
   1196 
   1197 
   1198 struct TALER_EXCHANGE_GetReservesHistoryHandle *
   1199 TALER_EXCHANGE_get_reserves_history_create (
   1200   struct GNUNET_CURL_Context *ctx,
   1201   const char *url,
   1202   struct TALER_EXCHANGE_Keys *keys,
   1203   const struct TALER_ReservePrivateKeyP *reserve_priv)
   1204 {
   1205   struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh;
   1206 
   1207   grhh = GNUNET_new (struct TALER_EXCHANGE_GetReservesHistoryHandle);
   1208   grhh->ctx = ctx;
   1209   grhh->base_url = GNUNET_strdup (url);
   1210   grhh->keys = TALER_EXCHANGE_keys_incref (keys);
   1211   grhh->reserve_priv = *reserve_priv;
   1212   GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
   1213                                       &grhh->reserve_pub.eddsa_pub);
   1214   return grhh;
   1215 }
   1216 
   1217 
   1218 enum GNUNET_GenericReturnValue
   1219 TALER_EXCHANGE_get_reserves_history_set_options_ (
   1220   struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh,
   1221   unsigned int num_options,
   1222   const struct TALER_EXCHANGE_GetReservesHistoryOptionValue *options)
   1223 {
   1224   for (unsigned int i = 0; i < num_options; i++)
   1225   {
   1226     switch (options[i].option)
   1227     {
   1228     case TALER_EXCHANGE_GET_RESERVES_HISTORY_OPTION_END:
   1229       return GNUNET_OK;
   1230     case TALER_EXCHANGE_GET_RESERVES_HISTORY_OPTION_START_OFF:
   1231       grhh->options.start_off = options[i].details.start_off;
   1232       break;
   1233     default:
   1234       GNUNET_break (0);
   1235       return GNUNET_SYSERR;
   1236     }
   1237   }
   1238   return GNUNET_OK;
   1239 }
   1240 
   1241 
   1242 enum TALER_ErrorCode
   1243 TALER_EXCHANGE_get_reserves_history_start (
   1244   struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh,
   1245   TALER_EXCHANGE_GetReservesHistoryCallback cb,
   1246   TALER_EXCHANGE_GET_RESERVES_HISTORY_RESULT_CLOSURE *cb_cls)
   1247 {
   1248   char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
   1249   struct curl_slist *job_headers;
   1250   CURL *eh;
   1251 
   1252   if (NULL != grhh->job)
   1253   {
   1254     GNUNET_break (0);
   1255     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
   1256   }
   1257   grhh->cb = cb;
   1258   grhh->cb_cls = cb_cls;
   1259   {
   1260     char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
   1261     char *end;
   1262     char start_off_str[32];
   1263 
   1264     end = GNUNET_STRINGS_data_to_string (
   1265       &grhh->reserve_pub,
   1266       sizeof (grhh->reserve_pub),
   1267       pub_str,
   1268       sizeof (pub_str));
   1269     *end = '\0';
   1270     GNUNET_snprintf (arg_str,
   1271                      sizeof (arg_str),
   1272                      "reserves/%s/history",
   1273                      pub_str);
   1274     GNUNET_snprintf (start_off_str,
   1275                      sizeof (start_off_str),
   1276                      "%llu",
   1277                      (unsigned long long) grhh->options.start_off);
   1278     grhh->url = TALER_url_join (grhh->base_url,
   1279                                 arg_str,
   1280                                 "start",
   1281                                 (0 != grhh->options.start_off)
   1282                                  ? start_off_str
   1283                                  : NULL,
   1284                                 NULL);
   1285   }
   1286   if (NULL == grhh->url)
   1287     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
   1288   eh = TALER_EXCHANGE_curl_easy_get_ (grhh->url);
   1289   if (NULL == eh)
   1290   {
   1291     GNUNET_free (grhh->url);
   1292     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
   1293   }
   1294   GNUNET_assert (CURLE_OK ==
   1295                  curl_easy_setopt (eh,
   1296                                    CURLOPT_HEADERFUNCTION,
   1297                                    &handle_header));
   1298   GNUNET_assert (CURLE_OK ==
   1299                  curl_easy_setopt (eh,
   1300                                    CURLOPT_HEADERDATA,
   1301                                    grhh));
   1302   {
   1303     struct TALER_ReserveSignatureP reserve_sig;
   1304     char *sig_hdr;
   1305     char *hdr;
   1306 
   1307     TALER_wallet_reserve_history_sign (grhh->options.start_off,
   1308                                        &grhh->reserve_priv,
   1309                                        &reserve_sig);
   1310     sig_hdr = GNUNET_STRINGS_data_to_string_alloc (
   1311       &reserve_sig,
   1312       sizeof (reserve_sig));
   1313     GNUNET_asprintf (&hdr,
   1314                      "%s: %s",
   1315                      TALER_RESERVE_HISTORY_SIGNATURE_HEADER,
   1316                      sig_hdr);
   1317     GNUNET_free (sig_hdr);
   1318     job_headers = curl_slist_append (NULL,
   1319                                      hdr);
   1320     GNUNET_free (hdr);
   1321     if (NULL == job_headers)
   1322     {
   1323       GNUNET_break (0);
   1324       curl_easy_cleanup (eh);
   1325       return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
   1326     }
   1327   }
   1328   grhh->job = GNUNET_CURL_job_add2 (grhh->ctx,
   1329                                     eh,
   1330                                     job_headers,
   1331                                     &handle_reserves_history_finished,
   1332                                     grhh);
   1333   curl_slist_free_all (job_headers);
   1334   if (NULL == grhh->job)
   1335     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
   1336   return TALER_EC_NONE;
   1337 }
   1338 
   1339 
   1340 void
   1341 TALER_EXCHANGE_get_reserves_history_cancel (
   1342   struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh)
   1343 {
   1344   if (NULL != grhh->job)
   1345   {
   1346     GNUNET_CURL_job_cancel (grhh->job);
   1347     grhh->job = NULL;
   1348   }
   1349   GNUNET_free (grhh->url);
   1350   GNUNET_free (grhh->base_url);
   1351   TALER_EXCHANGE_keys_decref (grhh->keys);
   1352   GNUNET_free (grhh);
   1353 }
   1354 
   1355 
   1356 /* end of exchange_api_get-reserves-RESERVE_PUB-history.c */