gnunet-svn
[Top][All Lists]
Advanced

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

[taler-exchange] branch master updated: more work on atomizing exchange


From: gnunet
Subject: [taler-exchange] branch master updated: more work on atomizing exchange API: deposit
Date: Sun, 25 Jun 2023 13:59:54 +0200

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

grothoff pushed a commit to branch master
in repository exchange.

The following commit(s) were added to refs/heads/master by this push:
     new 75733ee0 more work on atomizing exchange API: deposit
75733ee0 is described below

commit 75733ee00efc6d5342ed8b4fccd637efaebdce06
Author: Christian Grothoff <grothoff@gnunet.org>
AuthorDate: Sun Jun 25 13:59:47 2023 +0200

    more work on atomizing exchange API: deposit
---
 contrib/gana                                       |   2 +-
 src/auditor/taler-auditor-httpd.c                  |   2 +
 src/include/taler_exchange_service.h               | 159 +++---------
 src/include/taler_testing_lib.h                    |  12 +
 src/lib/Makefile.am                                |   1 -
 src/lib/exchange_api_batch_deposit.c               | 267 +++++++++++++++------
 src/lib/exchange_api_handle.c                      |  93 ++-----
 src/lib/exchange_api_handle.h                      |  46 +---
 src/testing/test_exchange_api.conf                 |   2 +-
 .../taler/auditor/offline-keys/auditor.priv        |   1 +
 src/testing/testing_api_cmd_batch_deposit.c        |  26 +-
 src/testing/testing_api_cmd_deposit.c              |  34 ++-
 src/testing/testing_api_cmd_get_exchange.c         |   3 +-
 src/testing/testing_api_traits.c                   |  28 +++
 14 files changed, 327 insertions(+), 349 deletions(-)

diff --git a/contrib/gana b/contrib/gana
index 3e5591a7..5f377301 160000
--- a/contrib/gana
+++ b/contrib/gana
@@ -1 +1 @@
-Subproject commit 3e5591a7e3fd93ba46fc2b538c63f0c16336283d
+Subproject commit 5f377301db4d94c485422ecda277fe850e29504a
diff --git a/src/auditor/taler-auditor-httpd.c 
b/src/auditor/taler-auditor-httpd.c
index 68316082..a59ce3de 100644
--- a/src/auditor/taler-auditor-httpd.c
+++ b/src/auditor/taler-auditor-httpd.c
@@ -157,6 +157,8 @@ handle_config (struct TAH_RequestHandler *rh,
   if (NULL == ver)
   {
     ver = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_string ("name",
+                               "taler-auditor"),
       GNUNET_JSON_pack_string ("version",
                                AUDITOR_PROTOCOL_VERSION),
       GNUNET_JSON_pack_string ("currency",
diff --git a/src/include/taler_exchange_service.h 
b/src/include/taler_exchange_service.h
index 065c2dcd..9cb5f083 100644
--- a/src/include/taler_exchange_service.h
+++ b/src/include/taler_exchange_service.h
@@ -573,10 +573,30 @@ TALER_EXCHANGE_disconnect (struct TALER_EXCHANGE_Handle 
*exchange);
  * @param exchange the exchange handle
  * @return the exchange's key set
  */
-const struct TALER_EXCHANGE_Keys *
+struct TALER_EXCHANGE_Keys *
 TALER_EXCHANGE_get_keys (struct TALER_EXCHANGE_Handle *exchange);
 
 
+/**
+ * Increment reference counter for @a keys
+ *
+ * @param[in,out] keys object to increment reference counter for
+ * @return keys, with incremented reference counter
+ */
+struct TALER_EXCHANGE_Keys *
+TALER_EXCHANGE_keys_incref (struct TALER_EXCHANGE_Keys *keys);
+
+
+/**
+ * Deccrement reference counter for @a keys.
+ * Frees @a keys if reference counter becomes zero.
+ *
+ * @param[in,out] keys object to decrement reference counter for
+ */
+void
+TALER_EXCHANGE_keys_decref (struct TALER_EXCHANGE_Keys *keys);
+
+
 /**
  * Let the user set the last valid denomination time manually.
  *
@@ -1151,135 +1171,6 @@ struct TALER_EXCHANGE_DepositContractDetail
 };
 
 
-/**
- * @brief A Deposit Handle
- */
-struct TALER_EXCHANGE_DepositHandle;
-
-
-/**
- * Structure with information about a deposit
- * operation's result.
- */
-struct TALER_EXCHANGE_DepositResult
-{
-  /**
-   * HTTP response data
-   */
-  struct TALER_EXCHANGE_HttpResponse hr;
-
-  union
-  {
-
-    /**
-     * Information returned if the HTTP status is
-     * #MHD_HTTP_OK.
-     */
-    struct
-    {
-      /**
-       * Time when the exchange generated the deposit confirmation
-       */
-      struct GNUNET_TIME_Timestamp deposit_timestamp;
-
-      /**
-       * signature provided by the exchange
-       */
-      const struct TALER_ExchangeSignatureP *exchange_sig;
-
-      /**
-       * exchange key used to sign @a exchange_sig.
-       */
-      const struct TALER_ExchangePublicKeyP *exchange_pub;
-
-      /**
-       * Base URL for looking up wire transfers, or
-       * NULL to use the default base URL.
-       */
-      const char *transaction_base_url;
-
-    } ok;
-
-    /**
-     * Information returned if the HTTP status is
-     * #MHD_HTTP_CONFLICT.
-     */
-    struct
-    {
-      /* TODO: returning full details is not implemented */
-    } conflict;
-
-  } details;
-};
-
-
-/**
- * Callbacks of this type are used to serve the result of submitting a
- * deposit permission request to a exchange.
- *
- * @param cls closure
- * @param dr deposit response details
- */
-typedef void
-(*TALER_EXCHANGE_DepositResultCallback) (
-  void *cls,
-  const struct TALER_EXCHANGE_DepositResult *dr);
-
-
-/**
- * Submit a deposit permission to the exchange and get the exchange's
- * response.  This API is typically used by a merchant.  Note that
- * while we return the response verbatim to the caller for further
- * processing, we do already verify that the response is well-formed
- * (i.e. that signatures included in the response are all valid).  If
- * the exchange's reply is not well-formed, we return an HTTP status code
- * of zero to @a cb.
- *
- * We also verify that the @a cdd.coin_sig is valid for this deposit
- * request, and that the @a cdd.ub_sig is a valid signature for @a
- * coin_pub.  Also, the @a exchange must be ready to operate (i.e.  have
- * finished processing the /keys reply).  If either check fails, we do
- * NOT initiate the transaction with the exchange and instead return NULL.
- *
- * @param exchange the exchange handle; the exchange must be ready to operate
- * @param dcd details about the contract the deposit is for
- * @param cdd details about the coin to be deposited
- * @param cb the callback to call when a reply for this request is available
- * @param cb_cls closure for the above callback
- * @param[out] ec if NULL is returned, set to the error code explaining why 
the operation failed
- * @return a handle for this request; NULL if the inputs are invalid (i.e.
- *         signatures fail to verify).  In this case, the callback is not 
called.
- */
-struct TALER_EXCHANGE_DepositHandle *
-TALER_EXCHANGE_deposit (
-  struct TALER_EXCHANGE_Handle *exchange,
-  const struct TALER_EXCHANGE_DepositContractDetail *dcd,
-  const struct TALER_EXCHANGE_CoinDepositDetail *cdd,
-  TALER_EXCHANGE_DepositResultCallback cb,
-  void *cb_cls,
-  enum TALER_ErrorCode *ec);
-
-
-/**
- * Change the chance that our deposit confirmation will be given to the
- * auditor to 100%.
- *
- * @param deposit the deposit permission request handle
- */
-void
-TALER_EXCHANGE_deposit_force_dc (struct TALER_EXCHANGE_DepositHandle *deposit);
-
-
-/**
- * Cancel a deposit permission request.  This function cannot be used
- * on a request handle if a response is already served for it.
- *
- * @param deposit the deposit permission request handle
- */
-void
-TALER_EXCHANGE_deposit_cancel (struct TALER_EXCHANGE_DepositHandle *deposit);
-
-
 /**
  * @brief A Batch Deposit Handle
  */
@@ -1374,7 +1265,9 @@ typedef void
  * finished processing the /keys reply).  If either check fails, we do
  * NOT initiate the transaction with the exchange and instead return NULL.
  *
- * @param exchange the exchange handle; the exchange must be ready to operate
+ * @param ctx curl context
+ * @param url exchange base URL
+ * @param keys exchange keys
  * @param dcd details about the contract the deposit is for
  * @param num_cdds length of the @a cdds array
  * @param cdds array with details about the coins to be deposited
@@ -1386,7 +1279,9 @@ typedef void
  */
 struct TALER_EXCHANGE_BatchDepositHandle *
 TALER_EXCHANGE_batch_deposit (
-  struct TALER_EXCHANGE_Handle *exchange,
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  struct TALER_EXCHANGE_Keys *keys,
   const struct TALER_EXCHANGE_DepositContractDetail *dcd,
   unsigned int num_cdds,
   const struct TALER_EXCHANGE_CoinDepositDetail *cdds,
diff --git a/src/include/taler_testing_lib.h b/src/include/taler_testing_lib.h
index 6554bc95..2ef7ef60 100644
--- a/src/include/taler_testing_lib.h
+++ b/src/include/taler_testing_lib.h
@@ -2694,6 +2694,7 @@ TALER_TESTING_get_trait (const struct TALER_TESTING_Trait 
*traits,
   op (relative_time, const struct GNUNET_TIME_Relative)            \
   op (exchange, struct TALER_EXCHANGE_Handle)                      \
   op (fakebank, struct TALER_FAKEBANK_Handle)                      \
+  op (keys, struct TALER_EXCHANGE_Keys)                            \
   op (process, struct GNUNET_OS_Process *)
 
 
@@ -2751,4 +2752,15 @@ TALER_TESTING_get_exchange_url (
   struct TALER_TESTING_Interpreter *is);
 
 
+/**
+ * Get exchange keys from interpreter. Convenience function.
+ *
+ * @param is interpreter state.
+ * @return the exchange keys, or NULL on error
+ */
+struct TALER_EXCHANGE_Keys *
+TALER_TESTING_get_keys (
+  struct TALER_TESTING_Interpreter *is);
+
+
 #endif
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
index 2c903142..53190bc5 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -32,7 +32,6 @@ libtalerexchange_la_SOURCES = \
   exchange_api_csr_melt.c \
   exchange_api_csr_withdraw.c \
   exchange_api_handle.c exchange_api_handle.h \
-  exchange_api_deposit.c \
   exchange_api_deposits_get.c \
   exchange_api_kyc_check.c \
   exchange_api_kyc_proof.c \
diff --git a/src/lib/exchange_api_batch_deposit.c 
b/src/lib/exchange_api_batch_deposit.c
index bb17737f..22ae2d58 100644
--- a/src/lib/exchange_api_batch_deposit.c
+++ b/src/lib/exchange_api_batch_deposit.c
@@ -44,6 +44,39 @@
  */
 #define AUDITOR_CHANCE 20
 
+
+/**
+ * Entry in list of ongoing interactions with an auditor.
+ */
+struct TEAH_AuditorInteractionEntry
+{
+  /**
+   * DLL entry.
+   */
+  struct TEAH_AuditorInteractionEntry *next;
+
+  /**
+   * DLL entry.
+   */
+  struct TEAH_AuditorInteractionEntry *prev;
+
+  /**
+   * URL of our auditor. For logging.
+   */
+  const char *auditor_url;
+
+  /**
+   * Interaction state.
+   */
+  struct TALER_AUDITOR_DepositConfirmationHandle *dch;
+
+  /**
+   * Batch deposit this is for.
+   */
+  struct TALER_EXCHANGE_BatchDepositHandle *dh;
+};
+
+
 /**
  * @brief A Deposit Handle
  */
@@ -51,9 +84,9 @@ struct TALER_EXCHANGE_BatchDepositHandle
 {
 
   /**
-   * The connection to exchange this request handle will use
+   * The keys of the exchange.
    */
-  struct TALER_EXCHANGE_Handle *exchange;
+  struct TALER_EXCHANGE_Keys *keys;
 
   /**
    * Context for our curl request(s).
@@ -117,11 +150,31 @@ struct TALER_EXCHANGE_BatchDepositHandle
    */
   struct TALER_ExchangeSignatureP *exchange_sigs;
 
+  /**
+   * Head of DLL of interactions with this auditor.
+   */
+  struct TEAH_AuditorInteractionEntry *ai_head;
+
+  /**
+   * Tail of DLL of interactions with this auditor.
+   */
+  struct TEAH_AuditorInteractionEntry *ai_tail;
+
+  /**
+   * Result to return to the application once @e ai_head is empty.
+   */
+  struct TALER_EXCHANGE_BatchDepositResult dr;
+
   /**
    * Exchange signing public key, set for #auditor_cb.
    */
   struct TALER_ExchangePublicKeyP exchange_pub;
 
+  /**
+   * Response object to free at the end.
+   */
+  json_t *response;
+
   /**
    * Chance that we will inform the auditor about the deposit
    * is 1:n, where the value of this field is "n".
@@ -136,6 +189,52 @@ struct TALER_EXCHANGE_BatchDepositHandle
 };
 
 
+/**
+ * Finish batch deposit operation by calling the callback.
+ *
+ * @param[in] dh handle to finished batch deposit operation
+ */
+static void
+finish_dh (struct TALER_EXCHANGE_BatchDepositHandle *dh)
+{
+  dh->cb (dh->cb_cls,
+          &dh->dr);
+  TALER_EXCHANGE_batch_deposit_cancel (dh);
+}
+
+
+/**
+ * Function called with the result from our call to the
+ * auditor's /deposit-confirmation handler.
+ *
+ * @param cls closure of type `struct TEAH_AuditorInteractionEntry *`
+ * @param dcr response
+ */
+static void
+acc_confirmation_cb (
+  void *cls,
+  const struct TALER_AUDITOR_DepositConfirmationResponse *dcr)
+{
+  struct TEAH_AuditorInteractionEntry *aie = cls;
+  struct TALER_EXCHANGE_BatchDepositHandle *dh = aie->dh;
+
+  if (MHD_HTTP_OK != dcr->hr.http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Failed to submit deposit confirmation to auditor `%s' with 
HTTP status %d (EC: %d). This is acceptable if it does not happen often.\n",
+                aie->auditor_url,
+                dcr->hr.http_status,
+                dcr->hr.ec);
+  }
+  GNUNET_CONTAINER_DLL_remove (dh->ai_head,
+                               dh->ai_tail,
+                               aie);
+  GNUNET_free (aie);
+  if (NULL == dh->ai_head)
+    finish_dh (dh);
+}
+
+
 /**
  * Function called for each auditor to give us a chance to possibly
  * launch a deposit confirmation interaction.
@@ -143,15 +242,13 @@ struct TALER_EXCHANGE_BatchDepositHandle
  * @param cls closure
  * @param auditor_url base URL of the auditor
  * @param auditor_pub public key of the auditor
- * @return NULL if no deposit confirmation interaction was launched
  */
-static struct TEAH_AuditorInteractionEntry *
+static void
 auditor_cb (void *cls,
             const char *auditor_url,
             const struct TALER_AuditorPublicKeyP *auditor_pub)
 {
   struct TALER_EXCHANGE_BatchDepositHandle *dh = cls;
-  const struct TALER_EXCHANGE_Keys *key_state;
   const struct TALER_EXCHANGE_SigningPublicKey *spk;
   struct TEAH_AuditorInteractionEntry *aie;
   struct TALER_Amount amount_without_fee;
@@ -164,29 +261,30 @@ auditor_cb (void *cls,
   {
     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                 "Not providing deposit confirmation to auditor\n");
-    return NULL;
+    return;
   }
   coin = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK,
                                    dh->num_cdds);
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
               "Will provide deposit confirmation to auditor `%s'\n",
               TALER_B2S (auditor_pub));
-  key_state = TALER_EXCHANGE_get_keys (dh->exchange);
-  dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state,
+  dki = TALER_EXCHANGE_get_denomination_key_by_hash (dh->keys,
                                                      
&dh->cdds[coin].h_denom_pub);
   GNUNET_assert (NULL != dki);
-  spk = TALER_EXCHANGE_get_signing_key_info (key_state,
+  spk = TALER_EXCHANGE_get_signing_key_info (dh->keys,
                                              &dh->exchange_pub);
   if (NULL == spk)
   {
     GNUNET_break_op (0);
-    return NULL;
+    return;
   }
   GNUNET_assert (0 <=
                  TALER_amount_subtract (&amount_without_fee,
                                         &dh->cdds[coin].amount,
                                         &dki->fees.deposit));
   aie = GNUNET_new (struct TEAH_AuditorInteractionEntry);
+  aie->dh = dh;
+  aie->auditor_url = auditor_url;
   aie->dch = TALER_AUDITOR_deposit_confirmation (
     dh->ctx,
     auditor_url,
@@ -201,14 +299,16 @@ auditor_cb (void *cls,
     &dh->dcd.merchant_pub,
     &dh->exchange_pub,
     &dh->exchange_sigs[coin],
-    &key_state->master_pub,
+    &dh->keys->master_pub,
     spk->valid_from,
     spk->valid_until,
     spk->valid_legal,
     &spk->master_sig,
-    &TEAH_acc_confirmation_cb,
+    &acc_confirmation_cb,
     aie);
-  return aie;
+  GNUNET_CONTAINER_DLL_insert (dh->ai_head,
+                               dh->ai_tail,
+                               aie);
 }
 
 
@@ -227,22 +327,19 @@ handle_deposit_finished (void *cls,
 {
   struct TALER_EXCHANGE_BatchDepositHandle *dh = cls;
   const json_t *j = response;
-  struct TALER_EXCHANGE_BatchDepositResult dr = {
-    .hr.reply = j,
-    .hr.http_status = (unsigned int) response_code
-  };
-  const struct TALER_EXCHANGE_Keys *keys;
+  struct TALER_EXCHANGE_BatchDepositResult *dr = &dh->dr;
 
   dh->job = NULL;
-  keys = TALER_EXCHANGE_get_keys (dh->exchange);
+  dh->response = json_incref ((json_t*) j);
+  dr->hr.reply = dh->response;
+  dr->hr.http_status = (unsigned int) response_code;
   switch (response_code)
   {
   case 0:
-    dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+    dr->hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     break;
   case MHD_HTTP_OK:
     {
-      const struct TALER_EXCHANGE_Keys *key_state;
       const json_t *sigs;
       json_t *sig;
       unsigned int idx;
@@ -253,7 +350,7 @@ handle_deposit_finished (void *cls,
                                      &dh->exchange_pub),
         GNUNET_JSON_spec_mark_optional (
           GNUNET_JSON_spec_string ("transaction_base_url",
-                                   &dr.details.ok.transaction_base_url),
+                                   &dr->details.ok.transaction_base_url),
           NULL),
         GNUNET_JSON_spec_timestamp ("exchange_timestamp",
                                     &dh->exchange_timestamp),
@@ -266,27 +363,26 @@ handle_deposit_finished (void *cls,
                              NULL, NULL))
       {
         GNUNET_break_op (0);
-        dr.hr.http_status = 0;
-        dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+        dr->hr.http_status = 0;
+        dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
         break;
       }
       if (json_array_size (sigs) != dh->num_cdds)
       {
         GNUNET_break_op (0);
-        dr.hr.http_status = 0;
-        dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+        dr->hr.http_status = 0;
+        dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
         break;
       }
       dh->exchange_sigs = GNUNET_new_array (dh->num_cdds,
                                             struct TALER_ExchangeSignatureP);
-      key_state = TALER_EXCHANGE_get_keys (dh->exchange);
       if (GNUNET_OK !=
-          TALER_EXCHANGE_test_signing_key (key_state,
+          TALER_EXCHANGE_test_signing_key (dh->keys,
                                            &dh->exchange_pub))
       {
         GNUNET_break_op (0);
-        dr.hr.http_status = 0;
-        dr.hr.ec = TALER_EC_EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE;
+        dr->hr.http_status = 0;
+        dr->hr.ec = TALER_EC_EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE;
         break;
       }
       json_array_foreach (sigs, idx, sig)
@@ -305,11 +401,11 @@ handle_deposit_finished (void *cls,
                                NULL, NULL))
         {
           GNUNET_break_op (0);
-          dr.hr.http_status = 0;
-          dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+          dr->hr.http_status = 0;
+          dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
           break;
         }
-        dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state,
+        dki = TALER_EXCHANGE_get_denomination_key_by_hash (dh->keys,
                                                            &dh->cdds[idx].
                                                            h_denom_pub);
         GNUNET_assert (NULL != dki);
@@ -333,42 +429,41 @@ handle_deposit_finished (void *cls,
               &dh->exchange_sigs[idx]))
         {
           GNUNET_break_op (0);
-          dr.hr.http_status = 0;
-          dr.hr.ec = TALER_EC_EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE;
+          dr->hr.http_status = 0;
+          dr->hr.ec = TALER_EC_EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE;
           break;
         }
       }
-      TEAH_get_auditors_for_dc (dh->exchange,
+      TEAH_get_auditors_for_dc (dh->keys,
                                 &auditor_cb,
                                 dh);
     }
-    dr.details.ok.exchange_sigs = dh->exchange_sigs;
-    dr.details.ok.exchange_pub = &dh->exchange_pub;
-    dr.details.ok.deposit_timestamp = dh->exchange_timestamp;
-    dr.details.ok.num_signatures = dh->num_cdds;
+    dr->details.ok.exchange_sigs = dh->exchange_sigs;
+    dr->details.ok.exchange_pub = &dh->exchange_pub;
+    dr->details.ok.deposit_timestamp = dh->exchange_timestamp;
+    dr->details.ok.num_signatures = dh->num_cdds;
     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 */
-    dr.hr.ec = TALER_JSON_get_error_code (j);
-    dr.hr.hint = TALER_JSON_get_error_hint (j);
+    dr->hr.ec = TALER_JSON_get_error_code (j);
+    dr->hr.hint = TALER_JSON_get_error_hint (j);
     break;
   case MHD_HTTP_FORBIDDEN:
-    dr.hr.ec = TALER_JSON_get_error_code (j);
-    dr.hr.hint = TALER_JSON_get_error_hint (j);
+    dr->hr.ec = TALER_JSON_get_error_code (j);
+    dr->hr.hint = TALER_JSON_get_error_hint (j);
     /* Nothing really to verify, exchange says one of the signatures is
        invalid; as we checked them, this should never happen, we
        should pass the JSON reply to the application */
     break;
   case MHD_HTTP_NOT_FOUND:
-    dr.hr.ec = TALER_JSON_get_error_code (j);
-    dr.hr.hint = TALER_JSON_get_error_hint (j);
+    dr->hr.ec = TALER_JSON_get_error_code (j);
+    dr->hr.hint = TALER_JSON_get_error_hint (j);
     /* Nothing really to verify, this should never
        happen, we should pass the JSON reply to the application */
     break;
   case MHD_HTTP_CONFLICT:
     {
-      const struct TALER_EXCHANGE_Keys *key_state;
       struct TALER_CoinSpendPublicKeyP coin_pub;
       struct GNUNET_JSON_Specification spec[] = {
         GNUNET_JSON_spec_fixed_auto ("coin_pub",
@@ -384,8 +479,8 @@ handle_deposit_finished (void *cls,
                              NULL, NULL))
       {
         GNUNET_break_op (0);
-        dr.hr.http_status = 0;
-        dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+        dr->hr.http_status = 0;
+        dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
         break;
       }
       for (unsigned int i = 0; i<dh->num_cdds; i++)
@@ -394,14 +489,13 @@ handle_deposit_finished (void *cls,
             GNUNET_memcmp (&coin_pub,
                            &dh->cdds[i].coin_pub))
           continue;
-        key_state = TALER_EXCHANGE_get_keys (dh->exchange);
-        dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state,
+        dki = TALER_EXCHANGE_get_denomination_key_by_hash (dh->keys,
                                                            &dh->cdds[i].
                                                            h_denom_pub);
         GNUNET_assert (NULL != dki);
         if (GNUNET_OK !=
             TALER_EXCHANGE_check_coin_conflict_ (
-              keys,
+              dh->keys,
               j,
               dki,
               &dh->cdds[i].coin_pub,
@@ -409,8 +503,8 @@ handle_deposit_finished (void *cls,
               &dh->cdds[i].amount))
         {
           GNUNET_break_op (0);
-          dr.hr.http_status = 0;
-          dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+          dr->hr.http_status = 0;
+          dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
           break;
         }
         found = true;
@@ -419,12 +513,12 @@ handle_deposit_finished (void *cls,
       if (! found)
       {
         GNUNET_break_op (0);
-        dr.hr.http_status = 0;
-        dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+        dr->hr.http_status = 0;
+        dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
         break;
       }
-      dr.hr.ec = TALER_JSON_get_error_code (j);
-      dr.hr.hint = TALER_JSON_get_error_hint (j);
+      dr->hr.ec = TALER_JSON_get_error_code (j);
+      dr->hr.hint = TALER_JSON_get_error_hint (j);
     }
     break;
   case MHD_HTTP_GONE:
@@ -432,35 +526,37 @@ handle_deposit_finished (void *cls,
     /* Note: one might want to check /keys for revocation
        signature here, alas tricky in case our /keys
        is outdated => left to clients */
-    dr.hr.ec = TALER_JSON_get_error_code (j);
-    dr.hr.hint = TALER_JSON_get_error_hint (j);
+    dr->hr.ec = TALER_JSON_get_error_code (j);
+    dr->hr.hint = TALER_JSON_get_error_hint (j);
     break;
   case MHD_HTTP_INTERNAL_SERVER_ERROR:
-    dr.hr.ec = TALER_JSON_get_error_code (j);
-    dr.hr.hint = TALER_JSON_get_error_hint (j);
+    dr->hr.ec = TALER_JSON_get_error_code (j);
+    dr->hr.hint = TALER_JSON_get_error_hint (j);
     /* Server had an internal issue; we should retry, but this API
        leaves this to the application */
     break;
   default:
     /* unexpected response code */
-    dr.hr.ec = TALER_JSON_get_error_code (j);
-    dr.hr.hint = TALER_JSON_get_error_hint (j);
+    dr->hr.ec = TALER_JSON_get_error_code (j);
+    dr->hr.hint = TALER_JSON_get_error_hint (j);
     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                 "Unexpected response code %u/%d for exchange deposit\n",
                 (unsigned int) response_code,
-                dr.hr.ec);
+                dr->hr.ec);
     GNUNET_break_op (0);
     break;
   }
-  dh->cb (dh->cb_cls,
-          &dr);
-  TALER_EXCHANGE_batch_deposit_cancel (dh);
+  if (NULL != dh->ai_head)
+    return;
+  finish_dh (dh);
 }
 
 
 struct TALER_EXCHANGE_BatchDepositHandle *
 TALER_EXCHANGE_batch_deposit (
-  struct TALER_EXCHANGE_Handle *exchange,
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  struct TALER_EXCHANGE_Keys *keys,
   const struct TALER_EXCHANGE_DepositContractDetail *dcd,
   unsigned int num_cdds,
   const struct TALER_EXCHANGE_CoinDepositDetail *cdds,
@@ -468,15 +564,12 @@ TALER_EXCHANGE_batch_deposit (
   void *cb_cls,
   enum TALER_ErrorCode *ec)
 {
-  const struct TALER_EXCHANGE_Keys *key_state;
   struct TALER_EXCHANGE_BatchDepositHandle *dh;
   json_t *deposit_obj;
   json_t *deposits;
   CURL *eh;
   struct TALER_Amount amount_without_fee;
 
-  GNUNET_assert (GNUNET_YES ==
-                 TEAH_handle_is_ready (exchange));
   if (GNUNET_TIME_timestamp_cmp (dcd->refund_deadline,
                                  >,
                                  dcd->wire_deadline))
@@ -485,10 +578,8 @@ TALER_EXCHANGE_batch_deposit (
     *ec = TALER_EC_EXCHANGE_DEPOSIT_REFUND_DEADLINE_AFTER_WIRE_DEADLINE;
     return NULL;
   }
-  key_state = TALER_EXCHANGE_get_keys (exchange);
   dh = GNUNET_new (struct TALER_EXCHANGE_BatchDepositHandle);
   dh->auditor_chance = AUDITOR_CHANCE;
-  dh->exchange = exchange;
   dh->cb = cb;
   dh->cb_cls = cb_cls;
   dh->cdds = GNUNET_memdup (cdds,
@@ -509,7 +600,7 @@ TALER_EXCHANGE_batch_deposit (
     const struct TALER_EXCHANGE_CoinDepositDetail *cdd = &cdds[i];
     const struct TALER_EXCHANGE_DenomPublicKey *dki;
 
-    dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state,
+    dki = TALER_EXCHANGE_get_denomination_key_by_hash (keys,
                                                        &cdd->h_denom_pub);
     if (NULL == dki)
     {
@@ -568,8 +659,9 @@ TALER_EXCHANGE_batch_deposit (
                                       &cdd->coin_sig)
           )));
   }
-  dh->url = TEAH_path_to_url (exchange,
-                              "/batch-deposit");
+  dh->url = TALER_url_join (url,
+                            "batch-deposit",
+                            NULL);
   if (NULL == dh->url)
   {
     GNUNET_break (0);
@@ -623,8 +715,9 @@ TALER_EXCHANGE_batch_deposit (
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
               "URL for deposit: `%s'\n",
               dh->url);
-  dh->ctx = TEAH_handle_to_context (exchange);
-  dh->job = GNUNET_CURL_job_add2 (dh->ctx,
+  dh->ctx = ctx;
+  dh->keys = TALER_EXCHANGE_keys_incref (keys);
+  dh->job = GNUNET_CURL_job_add2 (ctx,
                                   eh,
                                   dh->post_ctx.headers,
                                   &handle_deposit_finished,
@@ -645,15 +738,31 @@ void
 TALER_EXCHANGE_batch_deposit_cancel (
   struct TALER_EXCHANGE_BatchDepositHandle *deposit)
 {
+  struct TEAH_AuditorInteractionEntry *aie;
+
+  while (NULL != (aie = deposit->ai_head))
+  {
+    GNUNET_assert (aie->dh == deposit);
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Not sending deposit confirmation to auditor `%s' due to 
cancellation\n",
+                aie->auditor_url);
+    TALER_AUDITOR_deposit_confirmation_cancel (aie->dch);
+    GNUNET_CONTAINER_DLL_remove (deposit->ai_head,
+                                 deposit->ai_tail,
+                                 aie);
+    GNUNET_free (aie);
+  }
   if (NULL != deposit->job)
   {
     GNUNET_CURL_job_cancel (deposit->job);
     deposit->job = NULL;
   }
+  TALER_EXCHANGE_keys_decref (deposit->keys);
   GNUNET_free (deposit->url);
   GNUNET_free (deposit->cdds);
   GNUNET_free (deposit->exchange_sigs);
   TALER_curl_easy_post_finished (&deposit->post_ctx);
+  json_decref (deposit->response);
   GNUNET_free (deposit);
 }
 
diff --git a/src/lib/exchange_api_handle.c b/src/lib/exchange_api_handle.c
index 4d245b07..7e302112 100644
--- a/src/lib/exchange_api_handle.c
+++ b/src/lib/exchange_api_handle.c
@@ -112,16 +112,6 @@ struct TEAH_AuditorListEntry
    */
   struct TALER_AUDITOR_GetConfigHandle *ah;
 
-  /**
-   * Head of DLL of interactions with this auditor.
-   */
-  struct TEAH_AuditorInteractionEntry *ai_head;
-
-  /**
-   * Tail of DLL of interactions with this auditor.
-   */
-  struct TEAH_AuditorInteractionEntry *ai_tail;
-
   /**
    * Public key of the auditor.
    */
@@ -168,58 +158,24 @@ struct KeysRequest
 
 
 void
-TEAH_acc_confirmation_cb (
-  void *cls,
-  const struct TALER_AUDITOR_DepositConfirmationResponse *dcr)
-{
-  struct TEAH_AuditorInteractionEntry *aie = cls;
-  struct TEAH_AuditorListEntry *ale = aie->ale;
-
-  if (MHD_HTTP_OK != dcr->hr.http_status)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Failed to submit deposit confirmation to auditor `%s' with 
HTTP status %d (EC: %d). This is acceptable if it does not happen often.\n",
-                ale->auditor_url,
-                dcr->hr.http_status,
-                dcr->hr.ec);
-  }
-  GNUNET_CONTAINER_DLL_remove (ale->ai_head,
-                               ale->ai_tail,
-                               aie);
-  GNUNET_free (aie);
-}
-
-
-void
-TEAH_get_auditors_for_dc (struct TALER_EXCHANGE_Handle *h,
+TEAH_get_auditors_for_dc (struct TALER_EXCHANGE_Keys *keys,
                           TEAH_AuditorCallback ac,
                           void *ac_cls)
 {
-  if (NULL == h->auditors_head)
+  if (0 == keys->num_auditors)
   {
     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "No auditor available for exchange `%s'. Not submitting 
deposit confirmations.\n",
-                h->url);
+                "No auditor available. Not submitting deposit 
confirmations.\n");
     return;
   }
-  for (struct TEAH_AuditorListEntry *ale = h->auditors_head;
-       NULL != ale;
-       ale = ale->next)
+  for (unsigned int i = 0; i<keys->num_auditors; i++)
   {
-    struct TEAH_AuditorInteractionEntry *aie;
+    const struct TALER_EXCHANGE_AuditorInformation *auditor
+      = &keys->auditors[i];
 
-    if (! ale->is_up)
-      continue;
-    aie = ac (ac_cls,
-              ale->auditor_url,
-              &ale->auditor_pub);
-    if (NULL != aie)
-    {
-      aie->ale = ale;
-      GNUNET_CONTAINER_DLL_insert (ale->ai_head,
-                                   ale->ai_tail,
-                                   aie);
-    }
+    ac (ac_cls,
+        auditor->auditor_url,
+        &auditor->auditor_pub);
   }
 }
 
@@ -2121,20 +2077,6 @@ TALER_EXCHANGE_disconnect (struct TALER_EXCHANGE_Handle 
*exchange)
 
   while (NULL != (ale = exchange->auditors_head))
   {
-    struct TEAH_AuditorInteractionEntry *aie;
-
-    while (NULL != (aie = ale->ai_head))
-    {
-      GNUNET_assert (aie->ale == ale);
-      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                  "Not sending deposit confirmation to auditor `%s' due to 
exchange disconnect\n",
-                  ale->auditor_url);
-      TALER_AUDITOR_deposit_confirmation_cancel (aie->dch);
-      GNUNET_CONTAINER_DLL_remove (ale->ai_head,
-                                   ale->ai_tail,
-                                   aie);
-      GNUNET_free (aie);
-    }
     GNUNET_CONTAINER_DLL_remove (exchange->auditors_head,
                                  exchange->auditors_tail,
                                  ale);
@@ -2275,7 +2217,7 @@ TALER_EXCHANGE_get_denomination_key_by_hash (
 }
 
 
-const struct TALER_EXCHANGE_Keys *
+struct TALER_EXCHANGE_Keys *
 TALER_EXCHANGE_get_keys (struct TALER_EXCHANGE_Handle *exchange)
 {
   (void) TALER_EXCHANGE_check_keys_current (exchange,
@@ -2297,4 +2239,19 @@ TALER_EXCHANGE_get_keys_raw (struct 
TALER_EXCHANGE_Handle *exchange)
 }
 
 
+struct TALER_EXCHANGE_Keys *
+TALER_EXCHANGE_keys_incref (struct TALER_EXCHANGE_Keys *keys)
+{
+  // FIXME
+  return keys;
+}
+
+
+void
+TALER_EXCHANGE_keys_decref (struct TALER_EXCHANGE_Keys *keys)
+{
+  // FIXME
+}
+
+
 /* end of exchange_api_handle.c */
diff --git a/src/lib/exchange_api_handle.h b/src/lib/exchange_api_handle.h
index 8e3b1065..6b96e21e 100644
--- a/src/lib/exchange_api_handle.h
+++ b/src/lib/exchange_api_handle.h
@@ -34,32 +34,6 @@
 struct TEAH_AuditorListEntry;
 
 
-/**
- * Entry in list of ongoing interactions with an auditor.
- */
-struct TEAH_AuditorInteractionEntry
-{
-  /**
-   * DLL entry.
-   */
-  struct TEAH_AuditorInteractionEntry *next;
-
-  /**
-   * DLL entry.
-   */
-  struct TEAH_AuditorInteractionEntry *prev;
-
-  /**
-   * Which auditor is this action associated with?
-   */
-  struct TEAH_AuditorListEntry *ale;
-
-  /**
-   * Interaction state.
-   */
-  struct TALER_AUDITOR_DepositConfirmationHandle *dch;
-};
-
 /**
  * Stages of initialization for the `struct TALER_EXCHANGE_Handle`
  */
@@ -180,38 +154,24 @@ struct TALER_EXCHANGE_Handle
  * @param cls closure
  * @param auditor_url base URL of the auditor
  * @param auditor_pub public key of the auditor
- * @return NULL if no deposit confirmation interaction was launched
  */
-typedef struct TEAH_AuditorInteractionEntry *
+typedef void
 (*TEAH_AuditorCallback)(void *cls,
                         const char *auditor_url,
                         const struct TALER_AuditorPublicKeyP *auditor_pub);
 
 
-/**
- * Signature of functions called with the result from our call to the
- * auditor's /deposit-confirmation handler.
- *
- * @param cls closure of type `struct TEAH_AuditorInteractionEntry *`
- * @param dcr response
- */
-void
-TEAH_acc_confirmation_cb (
-  void *cls,
-  const struct TALER_AUDITOR_DepositConfirmationResponse *dcr);
-
-
 /**
  * Iterate over all available auditors for @a h, calling
  * @a ac and giving it a chance to start a deposit
  * confirmation interaction.
  *
- * @param h exchange to go over auditors for
+ * @param keys the keys to go over auditors for
  * @param ac function to call per auditor
  * @param ac_cls closure for @a ac
  */
 void
-TEAH_get_auditors_for_dc (struct TALER_EXCHANGE_Handle *h,
+TEAH_get_auditors_for_dc (struct TALER_EXCHANGE_Keys *keys,
                           TEAH_AuditorCallback ac,
                           void *ac_cls);
 
diff --git a/src/testing/test_exchange_api.conf 
b/src/testing/test_exchange_api.conf
index 9ed12412..b7f8c5c6 100644
--- a/src/testing/test_exchange_api.conf
+++ b/src/testing/test_exchange_api.conf
@@ -11,7 +11,7 @@ CURRENCY_ROUND_UNIT = EUR:0.01
 [auditor]
 BASE_URL = "http://localhost:8083/";
 PORT = 8083
-PUBLIC_KEY = SA7JVMCW3MMN7SYAWJ9AB0BGJDX6MP3PNN2JWQ3T8233MDSQC7Z0
+PUBLIC_KEY = T0XJ9QZ59YDN7QG3RE40SB2HY7W0ASR1EKF4WZDGZ1G159RSQC80
 TINY_AMOUNT = EUR:0.01
 
 [auditordb-postgres]
diff --git 
a/src/testing/test_exchange_api_home/taler/auditor/offline-keys/auditor.priv 
b/src/testing/test_exchange_api_home/taler/auditor/offline-keys/auditor.priv
new file mode 100644
index 00000000..7e712e48
--- /dev/null
+++ b/src/testing/test_exchange_api_home/taler/auditor/offline-keys/auditor.priv
@@ -0,0 +1 @@
+�G,U���{�~#r�-��H���������
\ No newline at end of file
diff --git a/src/testing/testing_api_cmd_batch_deposit.c 
b/src/testing/testing_api_cmd_batch_deposit.c
index 6a05071a..0a4fbd25 100644
--- a/src/testing/testing_api_cmd_batch_deposit.c
+++ b/src/testing/testing_api_cmd_batch_deposit.c
@@ -251,12 +251,15 @@ batch_deposit_run (void *cls,
                                  &wire_salt),
     GNUNET_JSON_spec_end ()
   };
-  struct TALER_EXCHANGE_Handle *exchange
-    = TALER_TESTING_get_exchange (is);
+  const char *exchange_url
+    = TALER_TESTING_get_exchange_url (is);
 
   (void) cmd;
-  if (NULL == exchange)
+  if (NULL == exchange_url)
+  {
+    GNUNET_break (0);
     return;
+  }
   memset (cdds,
           0,
           sizeof (cdds));
@@ -383,13 +386,16 @@ batch_deposit_run (void *cls,
       .refund_deadline = ds->refund_deadline
     };
 
-    ds->dh = TALER_EXCHANGE_batch_deposit (exchange,
-                                           &dcd,
-                                           ds->num_coins,
-                                           cdds,
-                                           &batch_deposit_cb,
-                                           ds,
-                                           &ec);
+    ds->dh = TALER_EXCHANGE_batch_deposit (
+      TALER_TESTING_interpreter_get_context (is),
+      exchange_url,
+      TALER_TESTING_get_keys (is),
+      &dcd,
+      ds->num_coins,
+      cdds,
+      &batch_deposit_cb,
+      ds,
+      &ec);
   }
   if (NULL == ds->dh)
   {
diff --git a/src/testing/testing_api_cmd_deposit.c 
b/src/testing/testing_api_cmd_deposit.c
index 3d5c00ab..f2a3a269 100644
--- a/src/testing/testing_api_cmd_deposit.c
+++ b/src/testing/testing_api_cmd_deposit.c
@@ -98,7 +98,7 @@ struct DepositState
   /**
    * Deposit handle while operation is running.
    */
-  struct TALER_EXCHANGE_DepositHandle *dh;
+  struct TALER_EXCHANGE_BatchDepositHandle *dh;
 
   /**
    * Timestamp of the /deposit operation in the wallet (contract signing time).
@@ -215,7 +215,7 @@ do_retry (void *cls)
  */
 static void
 deposit_cb (void *cls,
-            const struct TALER_EXCHANGE_DepositResult *dr)
+            const struct TALER_EXCHANGE_BatchDepositResult *dr)
 {
   struct DepositState *ds = cls;
 
@@ -254,10 +254,11 @@ deposit_cb (void *cls,
   }
   if (MHD_HTTP_OK == dr->hr.http_status)
   {
+    GNUNET_assert (1 == dr->details.ok.num_signatures);
     ds->deposit_succeeded = GNUNET_YES;
     ds->exchange_timestamp = dr->details.ok.deposit_timestamp;
     ds->exchange_pub = *dr->details.ok.exchange_pub;
-    ds->exchange_sig = *dr->details.ok.exchange_sig;
+    ds->exchange_sig = dr->details.ok.exchange_sigs[0];
   }
   TALER_TESTING_interpreter_next (ds->is);
 }
@@ -296,12 +297,15 @@ deposit_run (void *cls,
                                  &wire_salt),
     GNUNET_JSON_spec_end ()
   };
-  struct TALER_EXCHANGE_Handle *exchange
-    = TALER_TESTING_get_exchange (is);
+  const char *exchange_url
+    = TALER_TESTING_get_exchange_url (is);
 
   (void) cmd;
-  if (NULL == exchange)
+  if (NULL == exchange_url)
+  {
+    GNUNET_break (0);
     return;
+  }
   ds->is = is;
   if (NULL != ds->deposit_reference)
   {
@@ -469,12 +473,16 @@ deposit_run (void *cls,
       .refund_deadline = ds->refund_deadline
     };
 
-    ds->dh = TALER_EXCHANGE_deposit (exchange,
-                                     &dcd,
-                                     &cdd,
-                                     &deposit_cb,
-                                     ds,
-                                     &ec);
+    ds->dh = TALER_EXCHANGE_batch_deposit (
+      TALER_TESTING_interpreter_get_context (is),
+      exchange_url,
+      TALER_TESTING_get_keys (is),
+      &dcd,
+      1,
+      &cdd,
+      &deposit_cb,
+      ds,
+      &ec);
   }
   if (NULL == ds->dh)
   {
@@ -505,7 +513,7 @@ deposit_cleanup (void *cls,
   {
     TALER_TESTING_command_incomplete (ds->is,
                                       cmd->label);
-    TALER_EXCHANGE_deposit_cancel (ds->dh);
+    TALER_EXCHANGE_batch_deposit_cancel (ds->dh);
     ds->dh = NULL;
   }
   if (NULL != ds->retry_task)
diff --git a/src/testing/testing_api_cmd_get_exchange.c 
b/src/testing/testing_api_cmd_get_exchange.c
index 2fc8ba77..2822616c 100644
--- a/src/testing/testing_api_cmd_get_exchange.c
+++ b/src/testing/testing_api_cmd_get_exchange.c
@@ -189,7 +189,7 @@ get_exchange_traits (void *cls,
 {
   struct GetExchangeState *ges = cls;
   unsigned int off = (NULL == ges->master_priv_file) ? 1 : 0;
-  const struct TALER_EXCHANGE_Keys *keys
+  struct TALER_EXCHANGE_Keys *keys
     = TALER_EXCHANGE_get_keys (ges->exchange);
 
   if (NULL != keys)
@@ -198,6 +198,7 @@ get_exchange_traits (void *cls,
       TALER_TESTING_make_trait_master_priv (&ges->master_priv),
       TALER_TESTING_make_trait_master_pub (&keys->master_pub),
       TALER_TESTING_make_trait_exchange (ges->exchange),
+      TALER_TESTING_make_trait_keys (keys),
       TALER_TESTING_make_trait_exchange_url (ges->exchange_url),
       TALER_TESTING_trait_end ()
     };
diff --git a/src/testing/testing_api_traits.c b/src/testing/testing_api_traits.c
index 27eef5a5..d00a8d8c 100644
--- a/src/testing/testing_api_traits.c
+++ b/src/testing/testing_api_traits.c
@@ -129,4 +129,32 @@ TALER_TESTING_get_exchange_url (struct 
TALER_TESTING_Interpreter *is)
 }
 
 
+struct TALER_EXCHANGE_Keys *
+TALER_TESTING_get_keys (
+  struct TALER_TESTING_Interpreter *is)
+{
+  struct TALER_EXCHANGE_Keys *keys;
+  const struct TALER_TESTING_Command *exchange_cmd;
+
+  exchange_cmd
+    = TALER_TESTING_interpreter_get_command (is,
+                                             "exchange");
+  if (NULL == exchange_cmd)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return NULL;
+  }
+  if (GNUNET_OK !=
+      TALER_TESTING_get_trait_keys (exchange_cmd,
+                                    &keys))
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return NULL;
+  }
+  return keys;
+}
+
+
 /* end of testing_api_traits.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]