diff options
| -rw-r--r-- | src/exchange/taler-exchange-httpd_keys.c | 140 | ||||
| -rw-r--r-- | src/exchange/taler-exchange-httpd_management_extensions.c | 6 | ||||
| -rw-r--r-- | src/exchange/taler-exchange-httpd_management_global_fees.c | 3 | ||||
| -rw-r--r-- | src/exchangedb/plugin_exchangedb_postgres.c | 174 | ||||
| -rw-r--r-- | src/include/taler_exchange_service.h | 59 | ||||
| -rw-r--r-- | src/include/taler_exchangedb_plugin.h | 2 | ||||
| -rw-r--r-- | src/include/taler_json_lib.h | 24 | ||||
| -rw-r--r-- | src/lib/exchange_api_handle.c | 111 | ||||
| -rw-r--r-- | src/lib/exchange_api_reserves_history.c | 368 | ||||
| -rw-r--r-- | src/lib/exchange_api_reserves_status.c | 364 | 
10 files changed, 1231 insertions, 20 deletions
| diff --git a/src/exchange/taler-exchange-httpd_keys.c b/src/exchange/taler-exchange-httpd_keys.c index 95278ab8..1012a8c0 100644 --- a/src/exchange/taler-exchange-httpd_keys.c +++ b/src/exchange/taler-exchange-httpd_keys.c @@ -57,7 +57,7 @@   * #TALER_PROTOCOL_CURRENT and #TALER_PROTOCOL_AGE in   * exchange_api_handle.c!   */ -#define EXCHANGE_PROTOCOL_VERSION "12:0:0" +#define EXCHANGE_PROTOCOL_VERSION "13:0:1"  /** @@ -280,6 +280,31 @@ struct SigningKey  }; +/** + * Set of global fees (and options) for a time range. + */ +struct GlobalFee +{ +  /** +   * Kept in a DLL. +   */ +  struct GlobalFee *next; + +  /** +   * Kept in a DLL. +   */ +  struct GlobalFee *prev; + +  struct GNUNET_TIME_Timestamp start_date; +  struct GNUNET_TIME_Timestamp end_date; +  struct GNUNET_TIME_Relative purse_timeout; +  struct GNUNET_TIME_Relative kyc_timeout; +  struct GNUNET_TIME_Relative history_expiration; +  struct TALER_MasterSignatureP master_sig; +  struct TALER_GlobalFeeSet fees; +  uint32_t purse_account_limit; +}; +  struct TEH_KeyStateHandle  { @@ -297,12 +322,28 @@ struct TEH_KeyStateHandle    struct GNUNET_CONTAINER_MultiPeerMap *signkey_map;    /** +   * Head of DLL of our global fees. +   */ +  struct GlobalFee *gf_head; + +  /** +   * Tail of DLL of our global fees. +   */ +  struct GlobalFee *gf_tail; + +  /**     * json array with the auditors of this exchange. Contains exactly     * the information needed for the "auditors" field of the /keys response.     */    json_t *auditors;    /** +   * json array with the global fees of this exchange. Contains exactly +   * the information needed for the "global_fees" field of the /keys response. +   */ +  json_t *global_fees; + +  /**     * Sorted array of responses to /keys (MUST be sorted by cherry-picking date) of     * length @e krd_array_length;     */ @@ -548,7 +589,7 @@ suspend_request (struct MHD_Connection *connection)   * @param value a `struct TEH_DenominationKey`   * @return #GNUNET_OK   */ -static int +static enum GNUNET_GenericReturnValue  check_dk (void *cls,            const struct GNUNET_HashCode *hc,            void *value) @@ -1174,7 +1215,16 @@ static void  destroy_key_state (struct TEH_KeyStateHandle *ksh,                     bool free_helper)  { +  struct GlobalFee *gf; +    clear_response_cache (ksh); +  while (NULL != (gf = ksh->gf_head)) +  { +    GNUNET_CONTAINER_DLL_remove (ksh->gf_head, +                                 ksh->gf_tail, +                                 gf); +    GNUNET_free (gf); +  }    GNUNET_CONTAINER_multihashmap_iterate (ksh->denomkey_map,                                           &clear_denomination_cb,                                           ksh); @@ -1185,6 +1235,8 @@ destroy_key_state (struct TEH_KeyStateHandle *ksh,    GNUNET_CONTAINER_multipeermap_destroy (ksh->signkey_map);    json_decref (ksh->auditors);    ksh->auditors = NULL; +  json_decref (ksh->global_fees); +  ksh->global_fees = NULL;    if (free_helper)    {      destroy_key_helpers (ksh->helpers); @@ -1817,6 +1869,8 @@ create_krd (struct TEH_KeyStateHandle *ksh,                                     denoms),      GNUNET_JSON_pack_array_incref ("auditors",                                     ksh->auditors), +    GNUNET_JSON_pack_array_incref ("global_fees", +                                   ksh->global_fees),      GNUNET_JSON_pack_timestamp ("list_issue_date",                                  last_cpd),      GNUNET_JSON_pack_data_auto ("eddsa_pub", @@ -1825,7 +1879,7 @@ create_krd (struct TEH_KeyStateHandle *ksh,                                  &exchange_sig));    GNUNET_assert (NULL != keys); -  // Set wallet limit if KYC is configured +  /* Set wallet limit if KYC is configured */    if ( (TEH_KYC_NONE != TEH_kyc_config.mode) &&         (GNUNET_OK ==          TALER_amount_is_valid (&TEH_kyc_config.wallet_balance_limit)) ) @@ -1839,7 +1893,7 @@ create_krd (struct TEH_KeyStateHandle *ksh,            &TEH_kyc_config.wallet_balance_limit)));    } -  // Signal support for the configured, enabled extensions. +  /* Signal support for the configured, enabled extensions. */    {      json_t *extensions = json_object ();      bool has_extensions = false; @@ -2202,6 +2256,70 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)  /** + * Called with information about global fees. + * + * @param cls `struct TEH_KeyStateHandle *` we are building + * @param fees the global fees we charge + * @param purse_timeout when do purses time out + * @param kyc_timeout when do reserves without KYC time out + * @param history_expiration how long are account histories preserved + * @param purse_account_limit how many purses are free per account + * @param start_date from when are these fees valid (start date) + * @param end_date until when are these fees valid (end date, exclusive) + * @param master_sig master key signature affirming that this is the correct + *                   fee (of purpose #TALER_SIGNATURE_MASTER_GLOBAL_FEES) + */ +static void +global_fee_info_cb ( +  void *cls, +  const struct TALER_GlobalFeeSet *fees, +  struct GNUNET_TIME_Relative purse_timeout, +  struct GNUNET_TIME_Relative kyc_timeout, +  struct GNUNET_TIME_Relative history_expiration, +  uint32_t purse_account_limit, +  struct GNUNET_TIME_Timestamp start_date, +  struct GNUNET_TIME_Timestamp end_date, +  const struct TALER_MasterSignatureP *master_sig) +{ +  struct TEH_KeyStateHandle *ksh = cls; +  struct GlobalFee *gf; + +  gf = GNUNET_new (struct GlobalFee); +  gf->start_date = start_date; +  gf->end_date = end_date; +  gf->fees = *fees; +  gf->purse_timeout = purse_timeout; +  gf->kyc_timeout = kyc_timeout; +  gf->history_expiration = history_expiration; +  gf->purse_account_limit = purse_account_limit; +  gf->master_sig = *master_sig; +  GNUNET_CONTAINER_DLL_insert (ksh->gf_head, +                               ksh->gf_tail, +                               gf); +  GNUNET_assert ( +    0 == +    json_array_append_new ( +      ksh->global_fees, +      GNUNET_JSON_PACK ( +        GNUNET_JSON_pack_timestamp ("start_date", +                                    start_date), +        GNUNET_JSON_pack_timestamp ("end_date", +                                    end_date), +        TALER_JSON_PACK_GLOBAL_FEES (fees), +        GNUNET_JSON_pack_time_rel ("history_expiration", +                                   history_expiration), +        GNUNET_JSON_pack_time_rel ("account_kyc_timeout", +                                   kyc_timeout), +        GNUNET_JSON_pack_time_rel ("purse_timeout", +                                   purse_timeout), +        GNUNET_JSON_pack_uint64 ("purse_account_limit", +                                 purse_account_limit), +        GNUNET_JSON_pack_data_auto ("master_sig", +                                    master_sig)))); +} + + +/**   * Create a key state.   *   * @param[in] hs helper state to (re)use, NULL if not available @@ -2246,6 +2364,20 @@ build_key_state (struct HelperState *hs,    /* NOTE: fetches master-signed signkeys, but ALSO those that were revoked! */    GNUNET_break (GNUNET_OK ==                  TEH_plugin->preflight (TEH_plugin->cls)); +  if (NULL != ksh->global_fees) +    json_decref (ksh->global_fees); +  ksh->global_fees = json_array (); +  qs = TEH_plugin->get_global_fees (TEH_plugin->cls, +                                    &global_fee_info_cb, +                                    ksh); +  if (qs < 0) +  { +    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); +    GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs); +    destroy_key_state (ksh, +                       true); +    return NULL; +  }    qs = TEH_plugin->iterate_denominations (TEH_plugin->cls,                                            &denomination_info_cb,                                            ksh); diff --git a/src/exchange/taler-exchange-httpd_management_extensions.c b/src/exchange/taler-exchange-httpd_management_extensions.c index 109e863d..ce151e2e 100644 --- a/src/exchange/taler-exchange-httpd_management_extensions.c +++ b/src/exchange/taler-exchange-httpd_management_extensions.c @@ -126,6 +126,11 @@ set_extensions (void *cls,          .size = htons (sizeof (ev)),          .type = htons (TALER_DBEVENT_EXCHANGE_EXTENSIONS_UPDATED)        }; + +      // FIXME-Oec: bug: convert type to NBO first! +      // FIXME-Oec: bug: sizeof enum is ill-defined... +      // FIXME-Oec: bug: don't see /keys listening to the event +      // FIXME-Oec: why is   TEH_keys_update_states (); not enough?        TEH_plugin->event_notify (TEH_plugin->cls,                                  &ev,                                  type, @@ -294,7 +299,6 @@ TEH_handler_management_post_extensions (      NULL,      0); -  CLEANUP:    for (unsigned int i = 0; i < sec.num_extensions; i++)    { diff --git a/src/exchange/taler-exchange-httpd_management_global_fees.c b/src/exchange/taler-exchange-httpd_management_global_fees.c index eb3aa599..37bb40d9 100644 --- a/src/exchange/taler-exchange-httpd_management_global_fees.c +++ b/src/exchange/taler-exchange-httpd_management_global_fees.c @@ -27,6 +27,7 @@  #include "taler_json_lib.h"  #include "taler_mhd_lib.h"  #include "taler_signatures.h" +#include "taler-exchange-httpd_keys.h"  #include "taler-exchange-httpd_management.h"  #include "taler-exchange-httpd_responses.h" @@ -256,7 +257,7 @@ TEH_handler_management_post_global_fees (      if (GNUNET_SYSERR == res)        return ret;    } -  //  TEH_global_update_state (); // FIXME: trigger! +  TEH_keys_update_states ();    return TALER_MHD_reply_static (      connection,      MHD_HTTP_NO_CONTENT, diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index 2a955e15..bb6f46f5 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -1385,6 +1385,28 @@ prepare_statements (struct PostgresClosure *pg)        " WHERE start_date <= $1"        "   AND end_date > $1;",        1), +    /* Used in #postgres_get_global_fees() */ +    GNUNET_PQ_make_prepare ( +      "get_global_fees", +      "SELECT " +      " start_date" +      ",end_date" +      ",history_fee_val" +      ",history_fee_frac" +      ",kyc_fee_val" +      ",kyc_fee_frac" +      ",account_fee_val" +      ",account_fee_frac" +      ",purse_fee_val" +      ",purse_fee_frac" +      ",purse_timeout" +      ",kyc_timeout" +      ",history_expiration" +      ",purse_account_limit" +      ",master_sig" +      " FROM global_fee" +      " WHERE start_date >= $1", +      1),      /* Used in #postgres_insert_wire_fee */      GNUNET_PQ_make_prepare (        "insert_wire_fee", @@ -7819,6 +7841,142 @@ postgres_get_global_fee (void *cls,  /** + * Closure for #global_fees_cb(). + */ +struct GlobalFeeContext +{ +  /** +   * Function to call for each global fee block. +   */ +  TALER_EXCHANGEDB_GlobalFeeCallback cb; + +  /** +   * Closure to give to @e rec. +   */ +  void *cb_cls; + +  /** +   * Plugin context. +   */ +  struct PostgresClosure *pg; + +  /** +   * Set to #GNUNET_SYSERR on error. +   */ +  enum GNUNET_GenericReturnValue status; +}; + + +/** + * Function to be called with the results of a SELECT statement + * that has returned @a num_results results. + * + * @param cls closure + * @param result the postgres result + * @param num_results the number of results in @a result + */ +static void +global_fees_cb (void *cls, +                PGresult *result, +                unsigned int num_results) +{ +  struct GlobalFeeContext *gctx = cls; +  struct PostgresClosure *pg = gctx->pg; + +  for (unsigned int i = 0; i<num_results; i++) +  { +    struct TALER_GlobalFeeSet fees; +    struct GNUNET_TIME_Relative purse_timeout; +    struct GNUNET_TIME_Relative kyc_timeout; +    struct GNUNET_TIME_Relative history_expiration; +    uint32_t purse_account_limit; +    struct GNUNET_TIME_Timestamp start_date; +    struct GNUNET_TIME_Timestamp end_date; +    struct TALER_MasterSignatureP master_sig; +    struct GNUNET_PQ_ResultSpec rs[] = { +      GNUNET_PQ_result_spec_timestamp ("start_date", +                                       &start_date), +      GNUNET_PQ_result_spec_timestamp ("end_date", +                                       &end_date), +      TALER_PQ_RESULT_SPEC_AMOUNT ("history_fee", +                                   &fees.history), +      TALER_PQ_RESULT_SPEC_AMOUNT ("kyc_fee", +                                   &fees.kyc), +      TALER_PQ_RESULT_SPEC_AMOUNT ("account_fee", +                                   &fees.account), +      TALER_PQ_RESULT_SPEC_AMOUNT ("purse_fee", +                                   &fees.purse), +      GNUNET_PQ_result_spec_relative_time ("purse_timeout", +                                           &purse_timeout), +      GNUNET_PQ_result_spec_relative_time ("kyc_timeout", +                                           &kyc_timeout), +      GNUNET_PQ_result_spec_relative_time ("history_expiration", +                                           &history_expiration), +      GNUNET_PQ_result_spec_uint32 ("purse_account_limit", +                                    &purse_account_limit), +      GNUNET_PQ_result_spec_auto_from_type ("master_sig", +                                            &master_sig), +      GNUNET_PQ_result_spec_end +    }; +    if (GNUNET_OK != +        GNUNET_PQ_extract_result (result, +                                  rs, +                                  i)) +    { +      GNUNET_break (0); +      gctx->status = GNUNET_SYSERR; +      break; +    } +    gctx->cb (gctx->cb_cls, +              &fees, +              purse_timeout, +              kyc_timeout, +              history_expiration, +              purse_account_limit, +              start_date, +              end_date, +              &master_sig); +    GNUNET_PQ_cleanup_result (rs); +  } +} + + +/** + * Obtain global fees from database. + * + * @param cls closure + * @param cb function to call on each fee entry + * @param cb_cls closure for @a cb + * @return status of the transaction + */ +static enum GNUNET_DB_QueryStatus +postgres_get_global_fees (void *cls, +                          TALER_EXCHANGEDB_GlobalFeeCallback cb, +                          void *cb_cls) +{ +  struct PostgresClosure *pg = cls; +  struct GNUNET_TIME_Timestamp date +    = GNUNET_TIME_timestamp_get (); +  struct GNUNET_PQ_QueryParam params[] = { +    GNUNET_PQ_query_param_timestamp (&date), +    GNUNET_PQ_query_param_end +  }; +  struct GlobalFeeContext gctx = { +    .cb = cb, +    .cb_cls = cb_cls, +    .pg = pg, +    .status = GNUNET_OK +  }; + +  return GNUNET_PQ_eval_prepared_multi_select (pg->conn, +                                               "get_global_fees", +                                               params, +                                               &global_fees_cb, +                                               &gctx); +} + + +/**   * Insert wire transfer fee into database.   *   * @param cls closure @@ -8034,7 +8192,7 @@ struct ExpiredReserveContext    /**     * Set to #GNUNET_SYSERR on error.     */ -  int status; +  enum GNUNET_GenericReturnValue status;  }; @@ -8053,7 +8211,7 @@ reserve_expired_cb (void *cls,  {    struct ExpiredReserveContext *erc = cls;    struct PostgresClosure *pg = erc->pg; -  int ret; +  enum GNUNET_GenericReturnValue ret;    ret = GNUNET_OK;    for (unsigned int i = 0; i<num_results; i++) @@ -8117,13 +8275,14 @@ postgres_get_expired_reserves (void *cls,      GNUNET_PQ_query_param_timestamp (&now),      GNUNET_PQ_query_param_end    }; -  struct ExpiredReserveContext ectx; +  struct ExpiredReserveContext ectx = { +    .rec = rec, +    .rec_cls = rec_cls, +    .pg = pg, +    .status = GNUNET_OK +  };    enum GNUNET_DB_QueryStatus qs; -  ectx.rec = rec; -  ectx.rec_cls = rec_cls; -  ectx.pg = pg; -  ectx.status = GNUNET_OK;    qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,                                               "get_expired_reserves",                                               params, @@ -12371,6 +12530,7 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)    plugin->insert_global_fee = &postgres_insert_global_fee;    plugin->get_wire_fee = &postgres_get_wire_fee;    plugin->get_global_fee = &postgres_get_global_fee; +  plugin->get_global_fees = &postgres_get_global_fees;    plugin->get_expired_reserves = &postgres_get_expired_reserves;    plugin->insert_reserve_closed = &postgres_insert_reserve_closed;    plugin->wire_prepare_data_insert = &postgres_wire_prepare_data_insert; diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h index 48272a6b..56940669 100644 --- a/src/include/taler_exchange_service.h +++ b/src/include/taler_exchange_service.h @@ -204,6 +204,55 @@ struct TALER_EXCHANGE_AuditorInformation  /** + * Global fees and options of an exchange for a given time period. + */ +struct TALER_EXCHANGE_GlobalFee +{ + +  /** +   * Signature affirming all of the data. +   */ +  struct TALER_MasterSignatureP master_sig; + +  /** +   * Starting time of the validity period (inclusive). +   */ +  struct GNUNET_TIME_Timestamp start_date; + +  /** +   * End time of the validity period (exclusive). +   */ +  struct GNUNET_TIME_Timestamp end_date; + +  /** +   * Unmerged purses will be timed out after at most this time. +   */ +  struct GNUNET_TIME_Relative purse_timeout; + +  /** +   * Accounts without KYC will be closed after this time. +   */ +  struct GNUNET_TIME_Relative kyc_timeout; + +  /** +   * Account history is limited to this timeframe. +   */ +  struct GNUNET_TIME_Relative history_expiration; + +  /** +   * Fees that apply globally, independent of denomination +   * and wire method. +   */ +  struct TALER_GlobalFeeSet fees; + +  /** +   * Number of free purses per account. +   */ +  uint32_t purse_account_limit; +}; + + +/**   * @brief Information about keys from the exchange.   */  struct TALER_EXCHANGE_Keys @@ -230,6 +279,11 @@ struct TALER_EXCHANGE_Keys    struct TALER_EXCHANGE_AuditorInformation *auditors;    /** +   * Array with the global fees of the exchange. +   */ +  struct TALER_EXCHANGE_GlobalFee *global_fees; + +  /**     * Supported Taler protocol version by the exchange.     * String in the format current:revision:age using the     * semantics of GNU libtool.  See @@ -273,6 +327,11 @@ struct TALER_EXCHANGE_Keys    struct TALER_AgeMask age_mask;    /** +   * Length of the @e global_fees array. +   */ +  unsigned int num_global_fees; + +  /**     * Length of the @e sign_keys array (number of valid entries).     */    unsigned int num_sign_keys; diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index 7696b607..fc909a1b 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -3968,7 +3968,7 @@ struct TALER_EXCHANGEDB_Plugin     * Obtain information about the global fee structure of the exchange.     *     * @param cls closure -   * @param cb function to call on each account +   * @param cb function to call on each fee entry     * @param cb_cls closure for @a cb     * @return transaction status code     */ diff --git a/src/include/taler_json_lib.h b/src/include/taler_json_lib.h index 6238c07d..b4f99900 100644 --- a/src/include/taler_json_lib.h +++ b/src/include/taler_json_lib.h @@ -280,6 +280,30 @@ TALER_JSON_spec_amount_any_nbo (const char *name,  /** + * Generate specification to parse all global fees. + * + * @param currency which currency to expect + * @param[out] gfs a `struct TALER_GlobalFeeSet` to initialize + */ +#define TALER_JSON_SPEC_GLOBAL_FEES(currency,gfs) \ +  TALER_JSON_spec_amount ("kyc_fee", (currency), &(gfs)->kyc), \ +  TALER_JSON_spec_amount ("history_fee", (currency), &(gfs)->history),   \ +  TALER_JSON_spec_amount ("account_fee", (currency), &(gfs)->account),   \ +  TALER_JSON_spec_amount ("purse_fee", (currency), &(gfs)->purse) + +/** + * Macro to pack all of the global fees. + * + * @param gfs a `struct TALER_GlobalFeeSet` to pack + */ +#define TALER_JSON_PACK_GLOBAL_FEES(gfs) \ +  TALER_JSON_pack_amount ("kyc_fee", &(gfs)->kyc),   \ +  TALER_JSON_pack_amount ("history_fee", &(gfs)->history),     \ +  TALER_JSON_pack_amount ("account_fee", &(gfs)->account),     \ +  TALER_JSON_pack_amount ("purse_fee", &(gfs)->purse) + + +/**   * Generate line in parser specification for denomination public key.   *   * @param field name of the field diff --git a/src/lib/exchange_api_handle.c b/src/lib/exchange_api_handle.c index a9713a45..f7e87791 100644 --- a/src/lib/exchange_api_handle.c +++ b/src/lib/exchange_api_handle.c @@ -1,6 +1,6 @@  /*    This file is part of TALER -  Copyright (C) 2014-2021 Taler Systems SA +  Copyright (C) 2014-2022 Taler Systems SA    TALER is free software; you can redistribute it and/or modify it    under the terms of the GNU General Public License as published @@ -40,7 +40,7 @@   * Which version of the Taler protocol is implemented   * by this library?  Used to determine compatibility.   */ -#define EXCHANGE_PROTOCOL_CURRENT 12 +#define EXCHANGE_PROTOCOL_CURRENT 13  /**   * How many versions are we backwards compatible with? @@ -255,7 +255,7 @@ free_keys_request (struct KeysRequest *kr)   */  static enum GNUNET_GenericReturnValue  parse_json_signkey (struct TALER_EXCHANGE_SigningPublicKey *sign_key, -                    int check_sigs, +                    bool check_sigs,                      json_t *sign_key_obj,                      const struct TALER_MasterPublicKeyP *master_key)  { @@ -317,7 +317,7 @@ parse_json_signkey (struct TALER_EXCHANGE_SigningPublicKey *sign_key,  static enum GNUNET_GenericReturnValue  parse_json_denomkey (const char *currency,                       struct TALER_EXCHANGE_DenomPublicKey *denom_key, -                     int check_sigs, +                     bool check_sigs,                       json_t *denom_key_obj,                       struct TALER_MasterPublicKeyP *master_key,                       struct GNUNET_HashContext *hash_context) @@ -394,7 +394,7 @@ EXITIF_exit:   */  static enum GNUNET_GenericReturnValue  parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor, -                    int check_sigs, +                    bool check_sigs,                      json_t *auditor_obj,                      const struct TALER_EXCHANGE_Keys *key_data)  { @@ -505,6 +505,79 @@ parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor,  /** + * Parse a exchange's global fee information encoded in JSON. + * + * @param[out] gf where to return the result + * @param check_sigs should we check signatures + * @param[in] fee_obj json to parse + * @param key_data already parsed information about the exchange + * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is + *        invalid or the json malformed. + */ +static enum GNUNET_GenericReturnValue +parse_global_fee (struct TALER_EXCHANGE_GlobalFee *gf, +                  bool check_sigs, +                  json_t *fee_obj, +                  const struct TALER_EXCHANGE_Keys *key_data) +{ +  struct GNUNET_JSON_Specification spec[] = { +    GNUNET_JSON_spec_timestamp ("start_time", +                                &gf->start_date), +    GNUNET_JSON_spec_timestamp ("end_time", +                                &gf->end_date), +    GNUNET_JSON_spec_relative_time ("purse_timeout", +                                    &gf->purse_timeout), +    GNUNET_JSON_spec_relative_time ("kyc_timeout", +                                    &gf->kyc_timeout), +    GNUNET_JSON_spec_relative_time ("history_expiration", +                                    &gf->history_expiration), +    GNUNET_JSON_spec_uint32 ("purse_account_limit", +                             &gf->purse_account_limit), +    TALER_JSON_SPEC_GLOBAL_FEES (key_data->currency, +                                 &gf->fees), +    GNUNET_JSON_spec_fixed_auto ("master_sig", +                                 &gf->master_sig), +    GNUNET_JSON_spec_end () +  }; + +  if (GNUNET_OK != +      GNUNET_JSON_parse (fee_obj, +                         spec, +                         NULL, NULL)) +  { +    GNUNET_break_op (0); +#if DEBUG +    json_dumpf (fee_obj, +                stderr, +                JSON_INDENT (2)); +#endif +    return GNUNET_SYSERR; +  } +  if (check_sigs) +  { +    if (GNUNET_OK != +        TALER_exchange_offline_global_fee_verify ( +          gf->start_date, +          gf->end_date, +          &gf->fees, +          gf->purse_timeout, +          gf->kyc_timeout, +          gf->history_expiration, +          gf->purse_account_limit, +          &key_data->master_pub, +          &gf->master_sig)) +    { +      GNUNET_break_op (0); +      GNUNET_JSON_parse_free (spec); +      return GNUNET_SYSERR; +    } +  } +  GNUNET_JSON_parse_free (spec); +  return GNUNET_OK; +} + + +/**   * Function called with information about the auditor.  Marks an   * auditor as 'up'.   * @@ -691,7 +764,7 @@ decode_keys_json (const json_t *resp_obj,                stderr,                JSON_INDENT (2));  #endif -  /* check the version */ +  /* check the version first */    {      const char *ver;      unsigned int age; @@ -762,6 +835,32 @@ decode_keys_json (const json_t *resp_obj,      hash_context_restricted = GNUNET_CRYPTO_hash_context_start ();    } +  /* parse the global fees */ +  { +    json_t *global_fees; +    json_t *global_fee; +    unsigned int index; + +    EXITIF (NULL == (global_fees = +                       json_object_get (resp_obj, +                                        "global_fees"))); +    EXITIF (! json_is_array (global_fees)); +    if (0 != (key_data->num_global_fees = +                json_array_size (global_fees))) +    { +      key_data->global_fees +        = GNUNET_new_array (key_data->num_global_fees, +                            struct TALER_EXCHANGE_GlobalFee); +      json_array_foreach (global_fees, index, global_fee) { +        EXITIF (GNUNET_SYSERR == +                parse_global_fee (&key_data->global_fees[index], +                                  check_sig, +                                  global_fee, +                                  key_data)); +      } +    } +  } +    /* parse the signing keys */    {      json_t *sign_keys_array; diff --git a/src/lib/exchange_api_reserves_history.c b/src/lib/exchange_api_reserves_history.c new file mode 100644 index 00000000..f7191b2a --- /dev/null +++ b/src/lib/exchange_api_reserves_history.c @@ -0,0 +1,368 @@ +/* +  This file is part of TALER +  Copyright (C) 2014-2022 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify it under the +  terms of the GNU General Public License as published by the Free Software +  Foundation; either version 3, or (at your option) any later version. + +  TALER is distributed in the hope that it will be useful, but WITHOUT ANY +  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +  A PARTICULAR PURPOSE.  See the GNU General Public License for more details. + +  You should have received a copy of the GNU General Public License along with +  TALER; see the file COPYING.  If not, see +  <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_reserves_history.c + * @brief Implementation of the POST /reserves/$RESERVE_PUB/history requests + * @author Christian Grothoff + */ +#include "platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP history codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_exchange_service.h" +#include "taler_json_lib.h" +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A /reserves/$RID/history Handle + */ +struct TALER_EXCHANGE_ReservesHistoryHandle +{ + +  /** +   * The connection to exchange this request handle will use +   */ +  struct TALER_EXCHANGE_Handle *exchange; + +  /** +   * The url for this request. +   */ +  char *url; + +  /** +   * Handle for the request. +   */ +  struct GNUNET_CURL_Job *job; + +  /** +   * Context for #TEH_curl_easy_post(). Keeps the data that must +   * persist for Curl to make the upload. +   */ +  struct TALER_CURL_PostContext post_ctx; + +  /** +   * Function to call with the result. +   */ +  TALER_EXCHANGE_ReservesHistoryCallback cb; + +  /** +   * Public key of the reserve we are querying. +   */ +  struct TALER_ReservePublicKeyP reserve_pub; + +  /** +   * Closure for @a cb. +   */ +  void *cb_cls; + +}; + + +/** + * We received an #MHD_HTTP_OK history code. Handle the JSON + * response. + * + * @param rgh handle of the request + * @param j JSON response + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +handle_reserves_history_ok (struct TALER_EXCHANGE_ReservesHistoryHandle *rgh, +                            const json_t *j) +{ +  json_t *history; +  unsigned int len; +  bool kyc_ok; +  bool kyc_required; +  struct TALER_Amount balance; +  struct TALER_Amount balance_from_history; +  struct GNUNET_JSON_Specification spec[] = { +    TALER_JSON_spec_amount_any ("balance", +                                &balance), +    GNUNET_JSON_spec_bool ("kyc_passed", +                           &kyc_ok), +    GNUNET_JSON_spec_bool ("kyc_required", +                           &kyc_required), +    GNUNET_JSON_spec_json ("history", +                           &history), +    GNUNET_JSON_spec_end () +  }; +  struct TALER_EXCHANGE_HttpResponse hr = { +    .reply = j, +    .http_history = MHD_HTTP_OK +  }; + +  if (GNUNET_OK != +      GNUNET_JSON_parse (j, +                         spec, +                         NULL, +                         NULL)) +  { +    GNUNET_break_op (0); +    return GNUNET_SYSERR; +  } +  len = json_array_size (history); +  { +    struct TALER_EXCHANGE_ReserveHistory *rhistory; + +    rhistory = GNUNET_new_array (len, +                                 struct TALER_EXCHANGE_ReserveHistory); +    if (GNUNET_OK != +        TALER_EXCHANGE_parse_reserve_history (rgh->exchange, +                                              history, +                                              &rgh->reserve_pub, +                                              balance.currency, +                                              &balance_from_history, +                                              len, +                                              rhistory)) +    { +      GNUNET_break_op (0); +      TALER_EXCHANGE_free_reserve_history (rhistory, +                                           len); +      GNUNET_JSON_parse_free (spec); +      return GNUNET_SYSERR; +    } +    if (0 != +        TALER_amount_cmp (&balance_from_history, +                          &balance)) +    { +      /* exchange cannot add up balances!? */ +      GNUNET_break_op (0); +      TALER_EXCHANGE_free_reserve_history (rhistory, +                                           len); +      GNUNET_JSON_parse_free (spec); +      return GNUNET_SYSERR; +    } +    if (NULL != rgh->cb) +    { +      rgh->cb (rgh->cb_cls, +               &hr, +               &balance, +               len, +               rhistory); +      rgh->cb = NULL; +    } +    TALER_EXCHANGE_free_reserve_history (rhistory, +                                         len); +  } +  GNUNET_JSON_parse_free (spec); +  return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /reserves/$RID/history request. + * + * @param cls the `struct TALER_EXCHANGE_ReservesHistoryHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_reserves_history_finished (void *cls, +                                  long response_code, +                                  const void *response) +{ +  struct TALER_EXCHANGE_ReservesHistoryHandle *rgh = cls; +  const json_t *j = response; +  struct TALER_EXCHANGE_HttpResponse hr = { +    .reply = j, +    .http_history = (unsigned int) response_code +  }; + +  rgh->job = NULL; +  switch (response_code) +  { +  case 0: +    hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; +    break; +  case MHD_HTTP_OK: +    if (GNUNET_OK != +        handle_reserves_history_ok (rgh, +                                    j)) +    { +      hr.http_history = 0; +      hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; +    } +    break; +  case MHD_HTTP_BAD_REQUEST: +    /* This should never happen, either us or the exchange is buggy +       (or API version conflict); just pass JSON reply to the application */ +    GNUNET_break (0); +    hr.ec = TALER_JSON_history_error_code (j); +    hr.hint = TALER_JSON_history_error_hint (j); +    break; +  case MHD_HTTP_FORBIDDEN: +    /* This should never happen, either us or the exchange is buggy +       (or API version conflict); just pass JSON reply to the application */ +    GNUNET_break (0); +    hr.ec = TALER_JSON_history_error_code (j); +    hr.hint = TALER_JSON_history_error_hint (j); +    break; +  case MHD_HTTP_NOT_FOUND: +    /* Nothing really to verify, this should never +       happen, we should pass the JSON reply to the application */ +    hr.ec = TALER_JSON_history_error_code (j); +    hr.hint = TALER_JSON_history_error_hint (j); +    break; +  case MHD_HTTP_INTERNAL_SERVER_ERROR: +    /* Server had an internal issue; we should retry, but this API +       leaves this to the application */ +    hr.ec = TALER_JSON_history_error_code (j); +    hr.hint = TALER_JSON_history_error_hint (j); +    break; +  default: +    /* unexpected response code */ +    GNUNET_break_op (0); +    hr.ec = TALER_JSON_history_error_code (j); +    hr.hint = TALER_JSON_history_error_hint (j); +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected response code %u/%d for reserves history\n", +                (unsigned int) response_code, +                (int) hr.ec); +    break; +  } +  if (NULL != rgh->cb) +  { +    rgh->cb (rgh->cb_cls, +             &hr, +             NULL, +             0, +             NULL); +    rgh->cb = NULL; +  } +  TALER_EXCHANGE_reserves_history_cancel (rgh); +} + + +struct TALER_EXCHANGE_ReservesHistoryHandle * +TALER_EXCHANGE_reserves_history ( +  struct TALER_EXCHANGE_Handle *exchange, +  const struct TALER_ReservePrivateKeyP *reserve_priv, +  TALER_EXCHANGE_ReservesHistoryCallback cb, +  void *cb_cls) +{ +  struct TALER_EXCHANGE_ReservesHistoryHandle *rgh; +  struct GNUNET_CURL_Context *ctx; +  CURL *eh; +  char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32]; +  const struct TALER_Amount *history_fee; +  const struct TALER_EXCHANGE_Keys *keys; +  struct GNUNET_TIME_Timestamp ts +    = GNUNET_TIME_timestamp_get (); + +  if (GNUNET_YES != +      TEAH_handle_is_ready (exchange)) +  { +    GNUNET_break (0); +    return NULL; +  } +  keys = TALER_EXCHANGE_get_keys (exchange); +  // FIXME: extract history_fee from keys! +  history_fee = FIXME; +  rgh = GNUNET_new (struct TALER_EXCHANGE_ReservesHistoryHandle); +  rgh->exchange = exchange; +  rgh->cb = cb; +  rgh->cb_cls = cb_cls; +  GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, +                                      &rgh->reserve_pub.eddsa_pub); +  { +    char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2]; +    char *end; + +    end = GNUNET_STRINGS_data_to_string ( +      &rgh->reserve_pub, +      sizeof (rgh->reserve_pub), +      pub_str, +      sizeof (pub_str)); +    *end = '\0'; +    GNUNET_snprintf (arg_str, +                     sizeof (arg_str), +                     "/reserves/%s/history", +                     pub_str); +  } +  rgh->url = TEAH_path_to_url (exchange, +                               arg_str); +  if (NULL == rgh->url) +  { +    GNUNET_free (rgh); +    return NULL; +  } +  eh = TALER_EXCHANGE_curl_easy_history_ (rgh->url); +  if (NULL == eh) +  { +    GNUNET_break (0); +    GNUNET_free (rgh->url); +    GNUNET_free (rgh); +    return NULL; +  } +  TALER_wallet_reserve_history_sign (ts, +                                     history_fee, +                                     reserve_priv, +                                     &reserve_sig); +  { +    json_t *history_obj = GNUNET_JSON_PACK ( +      GNUNET_JSON_pack_timestamp ("request_timestamp", +                                  &ts), +      GNUNET_JSON_pack_data_auto ("reserve_sig", +                                  &reserve_sig)); + +    if (GNUNET_OK != +        TALER_curl_easy_post (&rgh->post_ctx, +                              eh, +                              history_obj)) +      ) +      { +        GNUNET_break (0); +        curl_easy_cleanup (eh); +        json_decref (history_obj); +        GNUNET_free (rgh->url); +        GNUNET_free (rgh); +        return NULL; +      } +      json_decref (history_obj); +  } +  ctx = TEAH_handle_to_context (exchange); +  rgh->job = GNUNET_CURL_job_add (ctx, +                                  eh, +                                  &handle_reserves_history_finished, +                                  rgh); +  return rgh; +} + + +void +TALER_EXCHANGE_reserves_history_cancel ( +  struct TALER_EXCHANGE_ReservesHistoryHandle *rgh) +{ +  if (NULL != rgh->job) +  { +    GNUNET_CURL_job_cancel (rgh->job); +    rgh->job = NULL; +  } +  TALER_curl_easy_post_finished (&rgh->post_ctx); +  GNUNET_free (rgh->url); +  GNUNET_free (rgh); +} + + +/* end of exchange_api_reserves_history.c */ diff --git a/src/lib/exchange_api_reserves_status.c b/src/lib/exchange_api_reserves_status.c new file mode 100644 index 00000000..2758a3a2 --- /dev/null +++ b/src/lib/exchange_api_reserves_status.c @@ -0,0 +1,364 @@ +/* +  This file is part of TALER +  Copyright (C) 2014-2022 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify it under the +  terms of the GNU General Public License as published by the Free Software +  Foundation; either version 3, or (at your option) any later version. + +  TALER is distributed in the hope that it will be useful, but WITHOUT ANY +  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +  A PARTICULAR PURPOSE.  See the GNU General Public License for more details. + +  You should have received a copy of the GNU General Public License along with +  TALER; see the file COPYING.  If not, see +  <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_reserves_status.c + * @brief Implementation of the POST /reserves/$RESERVE_PUB/status requests + * @author Christian Grothoff + */ +#include "platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_exchange_service.h" +#include "taler_json_lib.h" +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A /reserves/$RID/status Handle + */ +struct TALER_EXCHANGE_ReservesStatusHandle +{ + +  /** +   * The connection to exchange this request handle will use +   */ +  struct TALER_EXCHANGE_Handle *exchange; + +  /** +   * The url for this request. +   */ +  char *url; + +  /** +   * Handle for the request. +   */ +  struct GNUNET_CURL_Job *job; + +  /** +   * Context for #TEH_curl_easy_post(). Keeps the data that must +   * persist for Curl to make the upload. +   */ +  struct TALER_CURL_PostContext post_ctx; + +  /** +   * Function to call with the result. +   */ +  TALER_EXCHANGE_ReservesStatusCallback cb; + +  /** +   * Public key of the reserve we are querying. +   */ +  struct TALER_ReservePublicKeyP reserve_pub; + +  /** +   * Closure for @a cb. +   */ +  void *cb_cls; + +}; + + +/** + * We received an #MHD_HTTP_OK status code. Handle the JSON + * response. + * + * @param rgh handle of the request + * @param j JSON response + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +handle_reserves_status_ok (struct TALER_EXCHANGE_ReservesStatusHandle *rgh, +                           const json_t *j) +{ +  json_t *history; +  unsigned int len; +  bool kyc_ok; +  bool kyc_required; +  struct TALER_Amount balance; +  struct TALER_Amount balance_from_history; +  struct GNUNET_JSON_Specification spec[] = { +    TALER_JSON_spec_amount_any ("balance", +                                &balance), +    GNUNET_JSON_spec_bool ("kyc_passed", +                           &kyc_ok), +    GNUNET_JSON_spec_bool ("kyc_required", +                           &kyc_required), +    GNUNET_JSON_spec_json ("history", +                           &history), +    GNUNET_JSON_spec_end () +  }; +  struct TALER_EXCHANGE_HttpResponse hr = { +    .reply = j, +    .http_status = MHD_HTTP_OK +  }; + +  if (GNUNET_OK != +      GNUNET_JSON_parse (j, +                         spec, +                         NULL, +                         NULL)) +  { +    GNUNET_break_op (0); +    return GNUNET_SYSERR; +  } +  len = json_array_size (history); +  { +    struct TALER_EXCHANGE_ReserveHistory *rhistory; + +    rhistory = GNUNET_new_array (len, +                                 struct TALER_EXCHANGE_ReserveHistory); +    if (GNUNET_OK != +        TALER_EXCHANGE_parse_reserve_history (rgh->exchange, +                                              history, +                                              &rgh->reserve_pub, +                                              balance.currency, +                                              &balance_from_history, +                                              len, +                                              rhistory)) +    { +      GNUNET_break_op (0); +      TALER_EXCHANGE_free_reserve_history (rhistory, +                                           len); +      GNUNET_JSON_parse_free (spec); +      return GNUNET_SYSERR; +    } +    // FIXME: status history is allowed to be +    // partial, so this is NOT ok... +    if (0 != +        TALER_amount_cmp (&balance_from_history, +                          &balance)) +    { +      /* exchange cannot add up balances!? */ +      GNUNET_break_op (0); +      TALER_EXCHANGE_free_reserve_history (rhistory, +                                           len); +      GNUNET_JSON_parse_free (spec); +      return GNUNET_SYSERR; +    } +    if (NULL != rgh->cb) +    { +      rgh->cb (rgh->cb_cls, +               &hr, +               &balance, +               len, +               rhistory); +      rgh->cb = NULL; +    } +    TALER_EXCHANGE_free_reserve_history (rhistory, +                                         len); +  } +  GNUNET_JSON_parse_free (spec); +  return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /reserves/$RID/status request. + * + * @param cls the `struct TALER_EXCHANGE_ReservesStatusHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_reserves_status_finished (void *cls, +                                 long response_code, +                                 const void *response) +{ +  struct TALER_EXCHANGE_ReservesStatusHandle *rgh = cls; +  const json_t *j = response; +  struct TALER_EXCHANGE_HttpResponse hr = { +    .reply = j, +    .http_status = (unsigned int) response_code +  }; + +  rgh->job = NULL; +  switch (response_code) +  { +  case 0: +    hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; +    break; +  case MHD_HTTP_OK: +    if (GNUNET_OK != +        handle_reserves_status_ok (rgh, +                                   j)) +    { +      hr.http_status = 0; +      hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; +    } +    break; +  case MHD_HTTP_BAD_REQUEST: +    /* This should never happen, either us or the exchange is buggy +       (or API version conflict); just pass JSON reply to the application */ +    GNUNET_break (0); +    hr.ec = TALER_JSON_status_error_code (j); +    hr.hint = TALER_JSON_status_error_hint (j); +    break; +  case MHD_HTTP_FORBIDDEN: +    /* This should never happen, either us or the exchange is buggy +       (or API version conflict); just pass JSON reply to the application */ +    GNUNET_break (0); +    hr.ec = TALER_JSON_status_error_code (j); +    hr.hint = TALER_JSON_status_error_hint (j); +    break; +  case MHD_HTTP_NOT_FOUND: +    /* Nothing really to verify, this should never +       happen, we should pass the JSON reply to the application */ +    hr.ec = TALER_JSON_status_error_code (j); +    hr.hint = TALER_JSON_status_error_hint (j); +    break; +  case MHD_HTTP_INTERNAL_SERVER_ERROR: +    /* Server had an internal issue; we should retry, but this API +       leaves this to the application */ +    hr.ec = TALER_JSON_status_error_code (j); +    hr.hint = TALER_JSON_status_error_hint (j); +    break; +  default: +    /* unexpected response code */ +    GNUNET_break_op (0); +    hr.ec = TALER_JSON_status_error_code (j); +    hr.hint = TALER_JSON_status_error_hint (j); +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected response code %u/%d for reserves status\n", +                (unsigned int) response_code, +                (int) hr.ec); +    break; +  } +  if (NULL != rgh->cb) +  { +    rgh->cb (rgh->cb_cls, +             &hr, +             NULL, +             0, +             NULL); +    rgh->cb = NULL; +  } +  TALER_EXCHANGE_reserves_status_cancel (rgh); +} + + +struct TALER_EXCHANGE_ReservesStatusHandle * +TALER_EXCHANGE_reserves_status ( +  struct TALER_EXCHANGE_Handle *exchange, +  const struct TALER_ReservePrivateKeyP *reserve_priv, +  TALER_EXCHANGE_ReservesStatusCallback cb, +  void *cb_cls) +{ +  struct TALER_EXCHANGE_ReservesStatusHandle *rgh; +  struct GNUNET_CURL_Context *ctx; +  CURL *eh; +  char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32]; +  struct GNUNET_TIME_Timestamp ts +    = GNUNET_TIME_timestamp_get (); + +  if (GNUNET_YES != +      TEAH_handle_is_ready (exchange)) +  { +    GNUNET_break (0); +    return NULL; +  } +  rgh = GNUNET_new (struct TALER_EXCHANGE_ReservesStatusHandle); +  rgh->exchange = exchange; +  rgh->cb = cb; +  rgh->cb_cls = cb_cls; +  GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, +                                      &rgh->reserve_pub.eddsa_pub); +  { +    char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2]; +    char *end; + +    end = GNUNET_STRINGS_data_to_string ( +      &rgh->reserve_pub, +      sizeof (rgh->reserve_pub), +      pub_str, +      sizeof (pub_str)); +    *end = '\0'; +    GNUNET_snprintf (arg_str, +                     sizeof (arg_str), +                     "/reserves/%s/status", +                     pub_str); +  } +  rgh->url = TEAH_path_to_url (exchange, +                               arg_str); +  if (NULL == rgh->url) +  { +    GNUNET_free (rgh); +    return NULL; +  } +  eh = TALER_EXCHANGE_curl_easy_status_ (rgh->url); +  if (NULL == eh) +  { +    GNUNET_break (0); +    GNUNET_free (rgh->url); +    GNUNET_free (rgh); +    return NULL; +  } +  TALER_wallet_reserve_status_sign (ts, +                                    reserve_priv, +                                    &reserve_sig); +  { +    json_t *status_obj = GNUNET_JSON_PACK ( +      GNUNET_JSON_pack_timestamp ("request_timestamp", +                                  &ts), +      GNUNET_JSON_pack_data_auto ("reserve_sig", +                                  &reserve_sig)); + +    if (GNUNET_OK != +        TALER_curl_easy_post (&rgh->post_ctx, +                              eh, +                              status_obj)) +      ) +      { +        GNUNET_break (0); +        curl_easy_cleanup (eh); +        json_decref (status_obj); +        GNUNET_free (rgh->url); +        GNUNET_free (rgh); +        return NULL; +      } +      json_decref (status_obj); +  } +  ctx = TEAH_handle_to_context (exchange); +  rgh->job = GNUNET_CURL_job_add (ctx, +                                  eh, +                                  &handle_reserves_status_finished, +                                  rgh); +  return rgh; +} + + +void +TALER_EXCHANGE_reserves_status_cancel ( +  struct TALER_EXCHANGE_ReservesStatusHandle *rgh) +{ +  if (NULL != rgh->job) +  { +    GNUNET_CURL_job_cancel (rgh->job); +    rgh->job = NULL; +  } +  TALER_curl_easy_post_finished (&rgh->post_ctx); +  GNUNET_free (rgh->url); +  GNUNET_free (rgh); +} + + +/* end of exchange_api_reserves_status.c */ | 
