gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-exchange] branch master updated (5406d564 -> dee45bf0)


From: gnunet
Subject: [taler-exchange] branch master updated (5406d564 -> dee45bf0)
Date: Sun, 20 Mar 2022 09:44:52 +0100

This is an automated email from the git hooks/post-receive script.

grothoff pushed a change to branch master
in repository exchange.

    from 5406d564 -style fixes
     new 69927847 -style fixes
     new c83892ba -style fixes
     new 1bb5a77c add new reserve status/history signatures
     new dee45bf0 return new global fees from /keys

The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 src/exchange/taler-exchange-httpd_keys.c           | 140 ++++++++++++++-
 .../taler-exchange-httpd_management_extensions.c   |   6 +-
 .../taler-exchange-httpd_management_global_fees.c  |   3 +-
 src/exchangedb/plugin_exchangedb_postgres.c        | 188 ++++++++++++++++++--
 src/include/taler_crypto_lib.h                     |  66 ++++++-
 src/include/taler_exchange_service.h               |  59 +++++++
 src/include/taler_exchangedb_plugin.h              |   2 +-
 src/include/taler_json_lib.h                       |  24 +++
 src/include/taler_signatures.h                     |  55 ++++++
 src/lib/exchange_api_handle.c                      | 111 +++++++++++-
 src/lib/exchange_api_reserves_get.c                |   5 +-
 ...erves_get.c => exchange_api_reserves_history.c} | 196 +++++++++++++--------
 ...serves_get.c => exchange_api_reserves_status.c} | 182 +++++++++++--------
 src/util/wallet_signatures.c                       |  82 +++++++++
 14 files changed, 937 insertions(+), 182 deletions(-)
 copy src/lib/{exchange_api_reserves_get.c => exchange_api_reserves_history.c} 
(55%)
 copy src/lib/{exchange_api_reserves_get.c => exchange_api_reserves_status.c} 
(59%)

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
 {
 
@@ -296,12 +321,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;
@@ -2201,6 +2255,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.
  *
@@ -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 1b370ff3..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",
@@ -7818,6 +7840,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.
  *
@@ -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,
@@ -8729,7 +8888,7 @@ struct RefreshsSerialContext
   /**
    * Status code, set to #GNUNET_SYSERR on hard errors.
    */
-  int status;
+  enum GNUNET_GenericReturnValue status;
 };
 
 
@@ -8781,7 +8940,7 @@ refreshs_serial_helper_cb (void *cls,
                                             &rc),
       GNUNET_PQ_result_spec_end
     };
-    int ret;
+    enum GNUNET_GenericReturnValue ret;
 
     if (GNUNET_OK !=
         GNUNET_PQ_extract_result (result,
@@ -8874,7 +9033,7 @@ struct RefundsSerialContext
   /**
    * Status code, set to #GNUNET_SYSERR on hard errors.
    */
-  int status;
+  enum GNUNET_GenericReturnValue status;
 };
 
 
@@ -9010,7 +9169,7 @@ struct ReservesInSerialContext
   /**
    * Status code, set to #GNUNET_SYSERR on hard errors.
    */
-  int status;
+  enum GNUNET_GenericReturnValue status;
 };
 
 
@@ -9053,7 +9212,7 @@ reserves_in_serial_helper_cb (void *cls,
                                     &rowid),
       GNUNET_PQ_result_spec_end
     };
-    int ret;
+    enum GNUNET_GenericReturnValue ret;
 
     if (GNUNET_OK !=
         GNUNET_PQ_extract_result (result,
@@ -9187,7 +9346,7 @@ struct ReservesOutSerialContext
   /**
    * Status code, set to #GNUNET_SYSERR on hard errors.
    */
-  int status;
+  enum GNUNET_GenericReturnValue status;
 };
 
 
@@ -9233,7 +9392,7 @@ reserves_out_serial_helper_cb (void *cls,
                                     &rowid),
       GNUNET_PQ_result_spec_end
     };
-    int ret;
+    enum GNUNET_GenericReturnValue ret;
 
     if (GNUNET_OK !=
         GNUNET_PQ_extract_result (result,
@@ -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_crypto_lib.h b/src/include/taler_crypto_lib.h
index 5f091e77..8786a378 100644
--- a/src/include/taler_crypto_lib.h
+++ b/src/include/taler_crypto_lib.h
@@ -2778,7 +2778,7 @@ TALER_wallet_recoup_verify (
  * @param h_denom_pub hash of the denomiantion public key of the coin
  * @param coin_bks blinding factor used when withdrawing the coin
  * @param coin_priv coin key of the coin to be recouped
- * @param coin_sig resulting signature
+ * @param[out] coin_sig resulting signature
  */
 void
 TALER_wallet_recoup_sign (
@@ -2811,7 +2811,7 @@ TALER_wallet_recoup_refresh_verify (
  * @param h_denom_pub hash of the denomiantion public key of the coin
  * @param coin_bks blinding factor used when withdrawing the coin
  * @param coin_priv coin key of the coin to be recouped
- * @param coin_sig resulting signature
+ * @param[out] coin_sig resulting signature
  */
 void
 TALER_wallet_recoup_refresh_sign (
@@ -2821,6 +2821,68 @@ TALER_wallet_recoup_refresh_sign (
   struct TALER_CoinSpendSignatureP *coin_sig);
 
 
+/**
+ * Verify reserve history request signature.
+ *
+ * @param ts timestamp used
+ * @param history_fee how much did the wallet say it would pay
+ * @param reserve_pub reserve the history request was for
+ * @param reserve_sig resulting signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_reserve_history_verify (
+  const struct GNUNET_TIME_Timestamp ts,
+  const struct TALER_Amount *history_fee,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_ReserveSignatureP *reserve_sig);
+
+
+/**
+ * Create reserve history request signature.
+ *
+ * @param ts timestamp used
+ * @param history_fee how much do we expect to pay
+ * @param reserve_pub reserve the history request is for
+ * @param[out] reserve_sig resulting signature
+ */
+void
+TALER_wallet_reserve_history_sign (
+  const struct GNUNET_TIME_Timestamp ts,
+  const struct TALER_Amount *history_fee,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  struct TALER_ReserveSignatureP *reserve_sig);
+
+
+/**
+ * Verify reserve status request signature.
+ *
+ * @param ts timestamp used
+ * @param reserve_pub reserve the status request was for
+ * @param reserve_sig resulting signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_reserve_status_verify (
+  const struct GNUNET_TIME_Timestamp ts,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_ReserveSignatureP *reserve_sig);
+
+
+/**
+ * Create reserve status request signature.
+ *
+ * @param ts timestamp used
+ * @param reserve_pub reserve the status request is for
+ * @param[out] reserve_sig resulting signature
+ */
+void
+TALER_wallet_reserve_status_sign (
+  const struct GNUNET_TIME_Timestamp ts,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  struct TALER_ReserveSignatureP *reserve_sig);
+
+
 /* ********************* merchant signing ************************** */
 
 
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
@@ -203,6 +203,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.
  */
@@ -229,6 +278,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
@@ -272,6 +326,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).
    */
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
@@ -279,6 +279,30 @@ TALER_JSON_spec_amount_any_nbo (const char *name,
   TALER_JSON_pack_amount (pfx "_refund", &(dfs)->refund)
 
 
+/**
+ * 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.
  *
diff --git a/src/include/taler_signatures.h b/src/include/taler_signatures.h
index 5c5aaeeb..2aa82620 100644
--- a/src/include/taler_signatures.h
+++ b/src/include/taler_signatures.h
@@ -283,6 +283,16 @@
  */
 #define TALER_SIGNATURE_WALLET_AGE_ATTESTATION 1207
 
+/**
+ * Request full reserve history and pay for it.
+ */
+#define TALER_SIGNATURE_WALLET_RESERVE_HISTORY 1208
+
+/**
+ * Request detailed account status (for free).
+ */
+#define TALER_SIGNATURE_WALLET_RESERVE_STATUS 1209
+
 
 /******************************/
 /* Security module signatures */
@@ -443,6 +453,51 @@ struct TALER_LinkDataPS
 };
 
 
+/**
+ * Response by which a wallet requests an account status.
+ */
+struct TALER_ReserveStatusRequestPS
+{
+
+  /**
+   * Purpose is #TALER_SIGNATURE_WALLET_RESERVE_STATUS
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * When did the wallet make the requst.
+   */
+  struct GNUNET_TIME_TimestampNBO request_timestamp;
+
+};
+
+
+/**
+ * Response by which a wallet requests a full
+ * reserve history and indicates it is willing
+ * to pay for it.
+ */
+struct TALER_ReserveHistoryRequestPS
+{
+
+  /**
+   * Purpose is #TALER_SIGNATURE_WALLET_RESERVE_HISTORY
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * When did the wallet make the requst.
+   */
+  struct GNUNET_TIME_TimestampNBO request_timestamp;
+
+  /**
+   * How much does the exchange charge for the history?
+   */
+  struct TALER_AmountNBO history_fee;
+
+};
+
+
 /**
  * @brief Format used for to generate the signature on a request to withdraw
  * coins from a reserve.
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)
 {
@@ -504,6 +504,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_get.c 
b/src/lib/exchange_api_reserves_get.c
index 3bdfbd28..a25350e5 100644
--- a/src/lib/exchange_api_reserves_get.c
+++ b/src/lib/exchange_api_reserves_get.c
@@ -79,7 +79,7 @@ struct TALER_EXCHANGE_ReservesGetHandle
  * @param j JSON response
  * @return #GNUNET_OK on success
  */
-static int
+static enum GNUNET_GenericReturnValue
 handle_reserves_get_ok (struct TALER_EXCHANGE_ReservesGetHandle *rgh,
                         const json_t *j)
 {
@@ -88,7 +88,8 @@ handle_reserves_get_ok (struct 
TALER_EXCHANGE_ReservesGetHandle *rgh,
   struct TALER_Amount balance;
   struct TALER_Amount balance_from_history;
   struct GNUNET_JSON_Specification spec[] = {
-    TALER_JSON_spec_amount_any ("balance", &balance),
+    TALER_JSON_spec_amount_any ("balance",
+                                &balance),
     GNUNET_JSON_spec_end ()
   };
   struct TALER_EXCHANGE_HttpResponse hr = {
diff --git a/src/lib/exchange_api_reserves_get.c 
b/src/lib/exchange_api_reserves_history.c
similarity index 55%
copy from src/lib/exchange_api_reserves_get.c
copy to src/lib/exchange_api_reserves_history.c
index 3bdfbd28..f7191b2a 100644
--- a/src/lib/exchange_api_reserves_get.c
+++ b/src/lib/exchange_api_reserves_history.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 by the Free Software
@@ -15,13 +15,13 @@
   <http://www.gnu.org/licenses/>
 */
 /**
- * @file lib/exchange_api_reserves_get.c
- * @brief Implementation of the GET /reserves/$RESERVE_PUB requests
+ * @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 status codes */
+#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>
@@ -33,9 +33,9 @@
 
 
 /**
- * @brief A /reserves/ GET Handle
+ * @brief A /reserves/$RID/history Handle
  */
-struct TALER_EXCHANGE_ReservesGetHandle
+struct TALER_EXCHANGE_ReservesHistoryHandle
 {
 
   /**
@@ -53,10 +53,16 @@ struct TALER_EXCHANGE_ReservesGetHandle
    */
   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_ReservesGetCallback cb;
+  TALER_EXCHANGE_ReservesHistoryCallback cb;
 
   /**
    * Public key of the reserve we are querying.
@@ -72,28 +78,37 @@ struct TALER_EXCHANGE_ReservesGetHandle
 
 
 /**
- * We received an #MHD_HTTP_OK status code. Handle the JSON
+ * 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 int
-handle_reserves_get_ok (struct TALER_EXCHANGE_ReservesGetHandle *rgh,
-                        const json_t *j)
+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),
+    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
+    .http_history = MHD_HTTP_OK
   };
 
   if (GNUNET_OK !=
@@ -105,13 +120,6 @@ handle_reserves_get_ok (struct 
TALER_EXCHANGE_ReservesGetHandle *rgh,
     GNUNET_break_op (0);
     return GNUNET_SYSERR;
   }
-  history = json_object_get (j,
-                             "history");
-  if (NULL == history)
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
   len = json_array_size (history);
   {
     struct TALER_EXCHANGE_ReserveHistory *rhistory;
@@ -130,6 +138,7 @@ handle_reserves_get_ok (struct 
TALER_EXCHANGE_ReservesGetHandle *rgh,
       GNUNET_break_op (0);
       TALER_EXCHANGE_free_reserve_history (rhistory,
                                            len);
+      GNUNET_JSON_parse_free (spec);
       return GNUNET_SYSERR;
     }
     if (0 !=
@@ -140,6 +149,7 @@ handle_reserves_get_ok (struct 
TALER_EXCHANGE_ReservesGetHandle *rgh,
       GNUNET_break_op (0);
       TALER_EXCHANGE_free_reserve_history (rhistory,
                                            len);
+      GNUNET_JSON_parse_free (spec);
       return GNUNET_SYSERR;
     }
     if (NULL != rgh->cb)
@@ -154,28 +164,29 @@ handle_reserves_get_ok (struct 
TALER_EXCHANGE_ReservesGetHandle *rgh,
     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/ GET request.
+ * HTTP /reserves/$RID/history request.
  *
- * @param cls the `struct TALER_EXCHANGE_ReservesGetHandle`
+ * @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_get_finished (void *cls,
-                              long response_code,
-                              const void *response)
+handle_reserves_history_finished (void *cls,
+                                  long response_code,
+                                  const void *response)
 {
-  struct TALER_EXCHANGE_ReservesGetHandle *rgh = cls;
+  struct TALER_EXCHANGE_ReservesHistoryHandle *rgh = cls;
   const json_t *j = response;
   struct TALER_EXCHANGE_HttpResponse hr = {
     .reply = j,
-    .http_status = (unsigned int) response_code
+    .http_history = (unsigned int) response_code
   };
 
   rgh->job = NULL;
@@ -186,38 +197,46 @@ handle_reserves_get_finished (void *cls,
     break;
   case MHD_HTTP_OK:
     if (GNUNET_OK !=
-        handle_reserves_get_ok (rgh,
-                                j))
+        handle_reserves_history_ok (rgh,
+                                    j))
     {
-      hr.http_status = 0;
+      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 */
-    hr.ec = TALER_JSON_get_error_code (j);
-    hr.hint = TALER_JSON_get_error_hint (j);
+    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_get_error_code (j);
-    hr.hint = TALER_JSON_get_error_hint (j);
+    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_get_error_code (j);
-    hr.hint = TALER_JSON_get_error_hint (j);
+    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_get_error_code (j);
-    hr.hint = TALER_JSON_get_error_hint (j);
+    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 get\n",
+                "Unexpected response code %u/%d for reserves history\n",
                 (unsigned int) response_code,
                 (int) hr.ec);
     break;
@@ -227,25 +246,29 @@ handle_reserves_get_finished (void *cls,
     rgh->cb (rgh->cb_cls,
              &hr,
              NULL,
-             0, NULL);
+             0,
+             NULL);
     rgh->cb = NULL;
   }
-  TALER_EXCHANGE_reserves_get_cancel (rgh);
+  TALER_EXCHANGE_reserves_history_cancel (rgh);
 }
 
 
-struct TALER_EXCHANGE_ReservesGetHandle *
-TALER_EXCHANGE_reserves_get (
+struct TALER_EXCHANGE_ReservesHistoryHandle *
+TALER_EXCHANGE_reserves_history (
   struct TALER_EXCHANGE_Handle *exchange,
-  const struct TALER_ReservePublicKeyP *reserve_pub,
-  struct GNUNET_TIME_Relative timeout,
-  TALER_EXCHANGE_ReservesGetCallback cb,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  TALER_EXCHANGE_ReservesHistoryCallback cb,
   void *cb_cls)
 {
-  struct TALER_EXCHANGE_ReservesGetHandle *rgh;
+  struct TALER_EXCHANGE_ReservesHistoryHandle *rgh;
   struct GNUNET_CURL_Context *ctx;
   CURL *eh;
-  char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 16 + 32];
+  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))
@@ -253,40 +276,30 @@ TALER_EXCHANGE_reserves_get (
     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;
-    char timeout_str[32];
 
     end = GNUNET_STRINGS_data_to_string (
-      reserve_pub,
-      sizeof (*reserve_pub),
+      &rgh->reserve_pub,
+      sizeof (rgh->reserve_pub),
       pub_str,
       sizeof (pub_str));
     *end = '\0';
-    GNUNET_snprintf (timeout_str,
-                     sizeof (timeout_str),
-                     "%llu",
-                     (unsigned long long)
-                     (timeout.rel_value_us
-                      / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us));
-    if (GNUNET_TIME_relative_is_zero (timeout))
-      GNUNET_snprintf (arg_str,
-                       sizeof (arg_str),
-                       "/reserves/%s",
-                       pub_str);
-    else
-      GNUNET_snprintf (arg_str,
-                       sizeof (arg_str),
-                       "/reserves/%s?timeout_ms=%s",
-                       pub_str,
-                       timeout_str);
+    GNUNET_snprintf (arg_str,
+                     sizeof (arg_str),
+                     "/reserves/%s/history",
+                     pub_str);
   }
-  rgh = GNUNET_new (struct TALER_EXCHANGE_ReservesGetHandle);
-  rgh->exchange = exchange;
-  rgh->cb = cb;
-  rgh->cb_cls = cb_cls;
-  rgh->reserve_pub = *reserve_pub;
   rgh->url = TEAH_path_to_url (exchange,
                                arg_str);
   if (NULL == rgh->url)
@@ -294,7 +307,7 @@ TALER_EXCHANGE_reserves_get (
     GNUNET_free (rgh);
     return NULL;
   }
-  eh = TALER_EXCHANGE_curl_easy_get_ (rgh->url);
+  eh = TALER_EXCHANGE_curl_easy_history_ (rgh->url);
   if (NULL == eh)
   {
     GNUNET_break (0);
@@ -302,27 +315,54 @@ TALER_EXCHANGE_reserves_get (
     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_get_finished,
+                                  &handle_reserves_history_finished,
                                   rgh);
   return rgh;
 }
 
 
 void
-TALER_EXCHANGE_reserves_get_cancel (
-  struct TALER_EXCHANGE_ReservesGetHandle *rgh)
+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_get.c */
+/* end of exchange_api_reserves_history.c */
diff --git a/src/lib/exchange_api_reserves_get.c 
b/src/lib/exchange_api_reserves_status.c
similarity index 59%
copy from src/lib/exchange_api_reserves_get.c
copy to src/lib/exchange_api_reserves_status.c
index 3bdfbd28..2758a3a2 100644
--- a/src/lib/exchange_api_reserves_get.c
+++ b/src/lib/exchange_api_reserves_status.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 by the Free Software
@@ -15,8 +15,8 @@
   <http://www.gnu.org/licenses/>
 */
 /**
- * @file lib/exchange_api_reserves_get.c
- * @brief Implementation of the GET /reserves/$RESERVE_PUB requests
+ * @file lib/exchange_api_reserves_status.c
+ * @brief Implementation of the POST /reserves/$RESERVE_PUB/status requests
  * @author Christian Grothoff
  */
 #include "platform.h"
@@ -33,9 +33,9 @@
 
 
 /**
- * @brief A /reserves/ GET Handle
+ * @brief A /reserves/$RID/status Handle
  */
-struct TALER_EXCHANGE_ReservesGetHandle
+struct TALER_EXCHANGE_ReservesStatusHandle
 {
 
   /**
@@ -53,10 +53,16 @@ struct TALER_EXCHANGE_ReservesGetHandle
    */
   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_ReservesGetCallback cb;
+  TALER_EXCHANGE_ReservesStatusCallback cb;
 
   /**
    * Public key of the reserve we are querying.
@@ -79,16 +85,25 @@ struct TALER_EXCHANGE_ReservesGetHandle
  * @param j JSON response
  * @return #GNUNET_OK on success
  */
-static int
-handle_reserves_get_ok (struct TALER_EXCHANGE_ReservesGetHandle *rgh,
-                        const json_t *j)
+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),
+    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 = {
@@ -105,13 +120,6 @@ handle_reserves_get_ok (struct 
TALER_EXCHANGE_ReservesGetHandle *rgh,
     GNUNET_break_op (0);
     return GNUNET_SYSERR;
   }
-  history = json_object_get (j,
-                             "history");
-  if (NULL == history)
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
   len = json_array_size (history);
   {
     struct TALER_EXCHANGE_ReserveHistory *rhistory;
@@ -130,8 +138,11 @@ handle_reserves_get_ok (struct 
TALER_EXCHANGE_ReservesGetHandle *rgh,
       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))
@@ -140,6 +151,7 @@ handle_reserves_get_ok (struct 
TALER_EXCHANGE_ReservesGetHandle *rgh,
       GNUNET_break_op (0);
       TALER_EXCHANGE_free_reserve_history (rhistory,
                                            len);
+      GNUNET_JSON_parse_free (spec);
       return GNUNET_SYSERR;
     }
     if (NULL != rgh->cb)
@@ -154,24 +166,25 @@ handle_reserves_get_ok (struct 
TALER_EXCHANGE_ReservesGetHandle *rgh,
     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/ GET request.
+ * HTTP /reserves/$RID/status request.
  *
- * @param cls the `struct TALER_EXCHANGE_ReservesGetHandle`
+ * @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_get_finished (void *cls,
-                              long response_code,
-                              const void *response)
+handle_reserves_status_finished (void *cls,
+                                 long response_code,
+                                 const void *response)
 {
-  struct TALER_EXCHANGE_ReservesGetHandle *rgh = cls;
+  struct TALER_EXCHANGE_ReservesStatusHandle *rgh = cls;
   const json_t *j = response;
   struct TALER_EXCHANGE_HttpResponse hr = {
     .reply = j,
@@ -186,8 +199,8 @@ handle_reserves_get_finished (void *cls,
     break;
   case MHD_HTTP_OK:
     if (GNUNET_OK !=
-        handle_reserves_get_ok (rgh,
-                                j))
+        handle_reserves_status_ok (rgh,
+                                   j))
     {
       hr.http_status = 0;
       hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
@@ -196,28 +209,36 @@ handle_reserves_get_finished (void *cls,
   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 */
-    hr.ec = TALER_JSON_get_error_code (j);
-    hr.hint = TALER_JSON_get_error_hint (j);
+    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_get_error_code (j);
-    hr.hint = TALER_JSON_get_error_hint (j);
+    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_get_error_code (j);
-    hr.hint = TALER_JSON_get_error_hint (j);
+    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_get_error_code (j);
-    hr.hint = TALER_JSON_get_error_hint (j);
+    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 get\n",
+                "Unexpected response code %u/%d for reserves status\n",
                 (unsigned int) response_code,
                 (int) hr.ec);
     break;
@@ -227,25 +248,27 @@ handle_reserves_get_finished (void *cls,
     rgh->cb (rgh->cb_cls,
              &hr,
              NULL,
-             0, NULL);
+             0,
+             NULL);
     rgh->cb = NULL;
   }
-  TALER_EXCHANGE_reserves_get_cancel (rgh);
+  TALER_EXCHANGE_reserves_status_cancel (rgh);
 }
 
 
-struct TALER_EXCHANGE_ReservesGetHandle *
-TALER_EXCHANGE_reserves_get (
+struct TALER_EXCHANGE_ReservesStatusHandle *
+TALER_EXCHANGE_reserves_status (
   struct TALER_EXCHANGE_Handle *exchange,
-  const struct TALER_ReservePublicKeyP *reserve_pub,
-  struct GNUNET_TIME_Relative timeout,
-  TALER_EXCHANGE_ReservesGetCallback cb,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  TALER_EXCHANGE_ReservesStatusCallback cb,
   void *cb_cls)
 {
-  struct TALER_EXCHANGE_ReservesGetHandle *rgh;
+  struct TALER_EXCHANGE_ReservesStatusHandle *rgh;
   struct GNUNET_CURL_Context *ctx;
   CURL *eh;
-  char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 16 + 32];
+  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))
@@ -253,40 +276,27 @@ TALER_EXCHANGE_reserves_get (
     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;
-    char timeout_str[32];
 
     end = GNUNET_STRINGS_data_to_string (
-      reserve_pub,
-      sizeof (*reserve_pub),
+      &rgh->reserve_pub,
+      sizeof (rgh->reserve_pub),
       pub_str,
       sizeof (pub_str));
     *end = '\0';
-    GNUNET_snprintf (timeout_str,
-                     sizeof (timeout_str),
-                     "%llu",
-                     (unsigned long long)
-                     (timeout.rel_value_us
-                      / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us));
-    if (GNUNET_TIME_relative_is_zero (timeout))
-      GNUNET_snprintf (arg_str,
-                       sizeof (arg_str),
-                       "/reserves/%s",
-                       pub_str);
-    else
-      GNUNET_snprintf (arg_str,
-                       sizeof (arg_str),
-                       "/reserves/%s?timeout_ms=%s",
-                       pub_str,
-                       timeout_str);
+    GNUNET_snprintf (arg_str,
+                     sizeof (arg_str),
+                     "/reserves/%s/status",
+                     pub_str);
   }
-  rgh = GNUNET_new (struct TALER_EXCHANGE_ReservesGetHandle);
-  rgh->exchange = exchange;
-  rgh->cb = cb;
-  rgh->cb_cls = cb_cls;
-  rgh->reserve_pub = *reserve_pub;
   rgh->url = TEAH_path_to_url (exchange,
                                arg_str);
   if (NULL == rgh->url)
@@ -294,7 +304,7 @@ TALER_EXCHANGE_reserves_get (
     GNUNET_free (rgh);
     return NULL;
   }
-  eh = TALER_EXCHANGE_curl_easy_get_ (rgh->url);
+  eh = TALER_EXCHANGE_curl_easy_status_ (rgh->url);
   if (NULL == eh)
   {
     GNUNET_break (0);
@@ -302,27 +312,53 @@ TALER_EXCHANGE_reserves_get (
     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_get_finished,
+                                  &handle_reserves_status_finished,
                                   rgh);
   return rgh;
 }
 
 
 void
-TALER_EXCHANGE_reserves_get_cancel (
-  struct TALER_EXCHANGE_ReservesGetHandle *rgh)
+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_get.c */
+/* end of exchange_api_reserves_status.c */
diff --git a/src/util/wallet_signatures.c b/src/util/wallet_signatures.c
index 69a26520..ef887044 100644
--- a/src/util/wallet_signatures.c
+++ b/src/util/wallet_signatures.c
@@ -373,4 +373,86 @@ TALER_wallet_account_setup_sign (
 }
 
 
+enum GNUNET_GenericReturnValue
+TALER_wallet_reserve_history_verify (
+  const struct GNUNET_TIME_Timestamp ts,
+  const struct TALER_Amount *history_fee,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_ReserveSignatureP *reserve_sig)
+{
+  struct TALER_ReserveHistoryRequestPS rhr = {
+    .purpose.size = htonl (sizeof (rhr)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_HISTORY),
+    .request_timestamp = GNUNET_TIME_timestamp_hton (ts)
+  };
+
+  TALER_amount_hton (&rhr.history_fee,
+                     history_fee);
+  return GNUNET_CRYPTO_eddsa_verify (
+    TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW,
+    &rhr,
+    &reserve_sig->eddsa_signature,
+    &reserve_pub->eddsa_pub);
+}
+
+
+void
+TALER_wallet_reserve_history_sign (
+  const struct GNUNET_TIME_Timestamp ts,
+  const struct TALER_Amount *history_fee,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  struct TALER_ReserveSignatureP *reserve_sig)
+{
+  struct TALER_ReserveHistoryRequestPS rhr = {
+    .purpose.size = htonl (sizeof (rhr)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_HISTORY),
+    .request_timestamp = GNUNET_TIME_timestamp_hton (ts)
+  };
+
+  TALER_amount_hton (&rhr.history_fee,
+                     history_fee);
+  GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv,
+                            &rhr,
+                            &reserve_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_reserve_status_verify (
+  const struct GNUNET_TIME_Timestamp ts,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_ReserveSignatureP *reserve_sig)
+{
+  struct TALER_ReserveStatusRequestPS rsr = {
+    .purpose.size = htonl (sizeof (rsr)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_STATUS),
+    .request_timestamp = GNUNET_TIME_timestamp_hton (ts)
+  };
+
+  return GNUNET_CRYPTO_eddsa_verify (
+    TALER_SIGNATURE_WALLET_RESERVE_STATUS,
+    &rsr,
+    &reserve_sig->eddsa_signature,
+    &reserve_pub->eddsa_pub);
+}
+
+
+void
+TALER_wallet_reserve_status_sign (
+  const struct GNUNET_TIME_Timestamp ts,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  struct TALER_ReserveSignatureP *reserve_sig)
+{
+  struct TALER_ReserveStatusRequestPS rsr = {
+    .purpose.size = htonl (sizeof (rsr)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_STATUS),
+    .request_timestamp = GNUNET_TIME_timestamp_hton (ts)
+  };
+
+  GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv,
+                            &rsr,
+                            &reserve_sig->eddsa_signature);
+}
+
+
 /* end of wallet_signatures.c */

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]