gnunet-svn
[Top][All Lists]
Advanced

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

[taler-merchant] 131/277: towards POST tips pickup impl


From: gnunet
Subject: [taler-merchant] 131/277: towards POST tips pickup impl
Date: Sun, 05 Jul 2020 20:50:44 +0200

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

grothoff pushed a commit to branch master
in repository merchant.

commit fe19d9ada3f6dfdf3014b9610090b8750de32db8
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Thu May 21 23:06:59 2020 +0200

    towards POST tips pickup impl
---
 .../taler-merchant-httpd_post-tips-ID-pickup.c     | 829 +++++++++++++++++++--
 .../taler-merchant-httpd_private-get-tips-ID.c     |  94 ++-
 src/include/taler_merchantdb_plugin.h              | 149 ++++
 3 files changed, 997 insertions(+), 75 deletions(-)

diff --git a/src/backend/taler-merchant-httpd_post-tips-ID-pickup.c 
b/src/backend/taler-merchant-httpd_post-tips-ID-pickup.c
index 4425834..c24c2c0 100644
--- a/src/backend/taler-merchant-httpd_post-tips-ID-pickup.c
+++ b/src/backend/taler-merchant-httpd_post-tips-ID-pickup.c
@@ -29,13 +29,468 @@
 #include "taler-merchant-httpd_tip-pickup.h"
 
 
+/**
+ * How often do we retry on serialization errors?
+ */
+#define MAX_RETRIES 3
+
+/**
+ * How long do we give the exchange operation to complete withdrawing
+ * all of the planchets?
+ */
+#define EXCHANGE_TIMEOUT GNUNET_TIME_relative_multiply ( \
+    GNUNET_TIME_UNIT_SECONDS, 45)
+
+
+/**
+ * Active pickup operations.
+ */
+struct PickupContext;
+
+
+/**
+ * Handle for an individual planchet we are processing for a tip.
+ */
+struct PlanchetOperation
+{
+  /**
+   * Active pickup operation this planchet belongs with.
+   */
+  struct PickupContext *pc;
+
+  /**
+   * Find operation (while active), later NULL.
+   */
+  struct TMH_EXCHANGES_FindOperation *fo;
+
+  /**
+   * Withdraw handle (NULL while @e fo is active).
+   */
+  struct TALER_EXCHANGE_Withdraw2Handle *w2h;
+
+  /**
+   * Details about the planchet for withdrawing.
+   */
+  struct TALER_PlanchetDetail pd;
+
+  /**
+   * Offset of this planchet in the original request.
+   */
+  unsigned int offset;
+};
+
+
+/**
+ * Active pickup operations.
+ */
+struct PickupContext
+{
+  /**
+   * Kept in a DLL.
+   */
+  struct PickupContext *next;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct PickupContext *prev;
+
+  /**
+   * The connection.
+   */
+  struct MHD_Connection *connection;
+
+  /**
+   * Timeout task.
+   */
+  struct GNUNET_SCHEDULER_Task *tt;
+
+  /**
+   * Head of DLL of exchange operations on planchets.
+   */
+  struct PlanchetOperation *po_head;
+
+  /**
+   * Tail of DLL of exchange operations on planchets.
+   */
+  struct PlanchetOperation *po_tail;
+
+  /**
+   * HTTP response to return (set on errors).
+   */
+  struct MHD_Response *response;
+
+  /**
+   * Find operation (while active), later NULL.
+   */
+  struct TMH_EXCHANGES_FindOperation *fo;
+
+  /**
+   * Which reserve are we draining?
+   */
+  struct TALER_ReservePrivateKey reserve_priv;
+
+  /**
+   * Which tip is being picked up?
+   */
+  struct GNUNET_HashCode tip_id;
+
+  /**
+   * Array of our planchets.
+   */
+  struct TALER_PlanchetDetail *planchets;
+
+  /**
+   * Length of the @e planchets array.
+   */
+  unsigned int planchets_length;
+
+  /**
+   * HTTP status to use (set on errors).
+   */
+  unsigned int http_status;
+
+  /**
+   * Total amount requested in the pick up operation. Computed by
+   * totaling up the amounts of all the @e planchets.
+   */
+  struct TALER_Amount total_requested;
+
+  /**
+   * True if @e total_requested has been initialized.
+   */
+  bool tr_initialized;
+};
+
+
+/**
+ * Head of DLL.
+ */
+static struct PickupContext *pc_head;
+
+/**
+ * Tail of DLL.
+ */
+static struct PickupContext *pc_tail;
+
+
+/**
+ * Stop all ongoing operations associated with @a pc.
+ */
+static void
+stop_operations (struct PickupContext *pc)
+{
+  struct PlanchetOperation *po;
+
+  if (NULL != pc->tt)
+  {
+    GNUNET_SCHEDULER_cancel (pc->tt);
+    pc->tt = NULL;
+  }
+  if (NULL != pc->fo)
+  {
+    TMH_EXCHANGES_find_exchange_cancel (pc->fo);
+    pc->fo = NULL;
+  }
+  while (NULL != (po = pc->po_head))
+  {
+    if (NULL != po->fo)
+    {
+      TMH_EXCHANGES_find_exchange_cancel (po->fo);
+      po->fo = NULL;
+    }
+    if (NULL != po->w2h)
+    {
+      TALER_EXCHANGE_withdraw2_cancel (po->w2h);
+      po->w2h = NULL;
+    }
+    GNUNET_CONTAINER_DLL_remove (pc->po_head,
+                                 pc->po_tail,
+                                 po);
+  }
+}
+
+
+/**
+ * Function called to clean up.
+ *
+ * @param cls a `struct PickupContext *` to clean up
+ */
+static void
+pick_context_cleanup (void *cls)
+{
+  struct PickupContext *pc = cls;
+
+  stop_operations (pc); /* should not be any... */
+  for (unsigned int i = 0; i<pc->planchets_length; i++)
+    GNUNET_free (pc->planchets[i].coin_ev);
+  GNUNET_array_grow (pc->planchets,
+                     pc->planchets_length,
+                     0);
+  GNUNET_free (pc);
+}
+
+
 /**
  * We are shutting down, force resuming all suspended pickup operations.
  */
 void
 TMH_force_tip_pickup_resume ()
 {
-  // FIXME!
+  for (struct PickupContext *pc = pc_head;
+       NULL != pc;
+       pc = pc->next)
+  {
+    stop_operations (pc);
+    MHD_resume_connection (pc->connection);
+  }
+}
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * withdraw request to a exchange without the (un)blinding factor.
+ * We persist the result in the database and, if we were the last
+ * planchet operation, resume HTTP processing.
+ *
+ * @param cls closure with a `struct PlanchetOperation *`
+ * @param hr HTTP response data
+ * @param blind_sig blind signature over the coin, NULL on error
+ */
+static void
+withdraw_cb (void *cls,
+             const struct TALER_EXCHANGE_HttpResponse *hr,
+             const struct GNUNET_CRYPTO_RsaSignature *blind_sig)
+{
+  struct PlanchetOperation *po = cls;
+  struct PickupContext *pc = po->pc;
+
+  GNUNET_CONTAINER_DLL_remove (pc->po_head,
+                               pc->po_tail,
+                               po);
+  if (NULL == blind_sig)
+  {
+    stop_operations (pc);
+    pc->http_status = MHD_HTTP_FAILED_DEPENDENCY;
+    pc->response =
+      TALER_MHD_make_json_pack (
+        "{s:I, s:I, s:I, s:O}",
+        "code", (json_int_t) TALER_EC_TIP_PICKUP_EXCHANGE_ERROR,
+        "exchange_code", (json_int_t) hr->ec,
+        "exchange_http_status", (json_int_t) hr->http_status,
+        "exchange_reply", hr->reply);
+    MHD_resume_connection (pc->connection);
+    return;
+  }
+  qs = TMH_db->insert_pickup_blind_signature (TMH_db->cls,
+                                              &pc->pickup_id,
+                                              po->offset,
+                                              blind_sig);
+  if (qs < 0)
+  {
+    stop_operations (pc);
+    pc->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+    pc->response = TALER_MHD_make_error (
+      TALER_EC_TIP_PICKUP_DB_STORE_HARD_ERROR,
+      "Could not store blind signature in DB");
+    MHD_resume_connection (pc->connection);
+    return;
+  }
+  if (NULL == pc->po_head)
+  {
+    stop_operations (pc); /* stops timeout job */
+    MHD_resume_connection (pc->connection);
+  }
+}
+
+
+/**
+ * Function called with the result of a #TMH_EXCHANGES_find_exchange()
+ * operation as part of a withdraw objective.  If the exchange is ready,
+ * withdraws the planchet from the exchange.
+ *
+ * @param cls closure, with our `struct PlanchetOperation *`
+ * @param hr HTTP response details
+ * @param eh handle to the exchange context
+ * @param payto_uri payto://-URI of the exchange
+ * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if 
not available
+ * @param exchange_trusted true if this exchange is trusted by config
+ */
+static void
+do_withdraw (void *cls,
+             const struct TALER_EXCHANGE_HttpResponse *hr,
+             struct TALER_EXCHANGE_Handle *eh,
+             const char *payto_uri,
+             const struct TALER_Amount *wire_fee,
+             bool exchange_trusted)
+{
+  struct PlanchetOperation *po = cls;
+  struct PickupContext *pc = po->pc;
+
+  po->fo = NULL;
+  if (NULL == eh)
+  {
+    stop_operations (pc);
+    GNUNET_CONTAINER_DLL_remove (pc->po_head,
+                                 pc->po_tail,
+                                 po);
+    pc->http_status = MHD_HTTP_FAILED_DEPENDENCY;
+    pc->response =
+      TALER_MHD_make_json_pack (
+        "{s:I, s:I, s:I, s:O}",
+        "code", (json_int_t) TALER_EC_TIP_PICKUP_CONTACT_EXCHANGE_ERROR,
+        "exchange_code", (json_int_t) hr->ec,
+        "exchange_http_status", (json_int_t) hr->http_status,
+        "exchange_reply", hr->reply);
+    MHD_resume_connection (pc->connection);
+    return;
+  }
+  po->w2h = TALER_EXCHANGE_withdraw2 (eh,
+                                      &po->pd,
+                                      &pc->reserve_priv,
+                                      &withdraw_cb,
+                                      po);
+}
+
+
+/**
+ * Withdraw @a planchet from @a exchange_url for @a pc operation at planchet
+ * @a offset.  Sets up the respective operation and adds it @a pc's operation
+ * list. Once the operation is complete, the resulting blind signature is
+ * committed to the merchant's database. If all planchet operations are
+ * completed, the HTTP processing is resumed.
+ *
+ * @param[in,out] pc a pending pickup operation that includes @a planchet
+ * @param exchange_url identifies an exchange to do the pickup from
+ * @param planchet details about the coin to pick up
+ * @param offset offset of @a planchet in the list, needed to process the reply
+ */
+static void
+try_withdraw (struct PickupContext *pc,
+              const char *exchange_url,
+              const struct TALER_PlanchetDetail *planchet,
+              unsigned int offset)
+{
+  struct PlanchetOperation *po;
+
+  po = GNUNET_new (struct PlanchetOperation);
+  po->pc = pc;
+  po->pd = *planchet;
+  po->offset = offset;
+  po->fo = TMH_EXCHANGES_find_exchange (exchange_url,
+                                        NULL,
+                                        GNUNET_NO,
+                                        &do_withdraw,
+                                        po);
+  GNUNET_assert (NULL != po->fo);
+  GNUNET_CONTAINER_DLL_insert (pc->fo_head,
+                               pc->fo_tail,
+                               fo);
+}
+
+
+/**
+ * Handle timeout for pickup.
+ *
+ * @param cls a `struct PickupContext *`
+ */
+static void
+do_timeout (void *cls)
+{
+  struct PickupContext *pc = cls;
+
+  pc->tt = NULL;
+  stop_operations (pc);
+  pc->http_status = MHD_HTTP_REQUEST_TIMEOUT;
+  pc->response =  TALER_MHD_make_error (
+    TALER_EC_TIP_PICKUP_EXCHANGE_TIMEOUT,
+    "Timeout trying to withdraw from exchange (try again later)");
+  MHD_resume_connection (pc->connection);
+}
+
+
+/**
+ * Function called with the result of a #TMH_EXCHANGES_find_exchange()
+ * operation as part of a withdraw objective.  Here, we initialize
+ * the "total_requested" amount by adding up the cost of the planchets
+ * provided by the client.
+ *
+ * @param cls closure, with our `struct PickupContext *`
+ * @param hr HTTP response details
+ * @param eh handle to the exchange context
+ * @param payto_uri payto://-URI of the exchange
+ * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if 
not available
+ * @param exchange_trusted true if this exchange is trusted by config
+ */
+static void
+compute_total_requested (void *cls,
+                         const struct TALER_EXCHANGE_HttpResponse *hr,
+                         struct TALER_EXCHANGE_Handle *eh,
+                         const char *payto_uri,
+                         const struct TALER_Amount *wire_fee,
+                         bool exchange_trusted)
+{
+  struct PickupContext *pc = cls;
+  const struct TALER_EXCHANGE_Keys *keys;
+
+  pc->fo = NULL;
+  stop_operations (pc); /* stops timeout job */
+  if ( (NULL == eh) ||
+       (NULL == (keys = TALER_EXCHANGE_get_keys (eh))) )
+  {
+    pc->http_status = MHD_HTTP_FAILED_DEPENDENCY;
+    pc->response =
+      TALER_MHD_make_json_pack (
+        "{s:I, s:I, s:I, s:O}",
+        "code", (json_int_t) TALER_EC_TIP_PICKUP_EXCHANGE_KEYS_ERROR,
+        "exchange_code", (json_int_t) hr->ec,
+        "exchange_http_status", (json_int_t) hr->http_status,
+        "exchange_reply", hr->reply);
+    MHD_resume_connection (pc->connection);
+    return;
+  }
+  TALER_amount_get_zero (TMH_currency,
+                         &pc->total_requested);
+  for (unsigned int i = 0; i<pc->planchets_length; i++)
+  {
+    struct TALER_PlanchetDetail *pd = &pc->planchets[i];
+    const struct TALER_EXCHANGE_DenomPublicKey *dpk;
+
+    dpk = TALER_EXCHANGE_get_denomination_key_by_hash (keys,
+                                                       &pd->denom_pub_hash);
+    if (NULL == dpk)
+    {
+      pc->http_status = MHD_HTTP_BAD_REQUEST;
+      pc->response =
+        TALER_MHD_make_json_pack (
+          "{s:I, s:I, s:I, s:O}",
+          "code", (json_int_t) TALER_EC_TIP_PICKUP_DENOMINATION_UNKNOWN,
+          "exchange_code", (json_int_t) hr->ec,
+          "exchange_http_status", (json_int_t) hr->http_status,
+          "exchange_reply", hr->reply);
+      MHD_resume_connection (pc->connection);
+      return;
+    }
+
+    if ( (GNUNET_YES !=
+          TALER_amount_cmp_currency (&pc->total_requested,
+                                     &dpk->value)) ||
+         (0 >
+          TALER_amount_add (&pc->total_requested,
+                            &pc->total_requested,
+                            &dpk->value)) )
+    {
+      pc->http_status = MHD_HTTP_BAD_REQUEST;
+      pc->response =
+        TALER_MHD_make_error (TALER_EC_TIP_PICKUP_SUMMATION_FAILED,
+                              "Could not add up values to compute pickup 
total");
+      MHD_resume_connection (pc->connection);
+      return;
+    }
+  }
+  pc->tr_initialized = true;
+
+  return;
 }
 
 
@@ -53,43 +508,271 @@ TMH_post_tips_ID_pickup (const struct TMH_RequestHandler 
*rh,
                          struct MHD_Connection *connection,
                          struct TMH_HandlerContext *hc)
 {
+  struct PickupContext *pc = hc->ctx;
+
+  char *justification;
   char *exchange_url;
-  struct GNUNET_HashCode tip_id;
-  struct TALER_Amount tip_amount;
-  struct TALER_Amount tip_amount_left;
-  struct GNUNET_TIME_Absolute timestamp;
+
+  struct TALER_Amount total_authorized;
+  struct TALER_Amount total_picked_up;
+  struct TALER_Amount total_remaining;
+  struct GNUNET_TIME_Absolute expiration;
   struct GNUNET_TIME_Absolute timestamp_expire;
+  struct TALER_ReservePrivateKey reserve_priv;
   MHD_RESULT ret;
   enum GNUNET_DB_QueryStatus qs;
+  unsigned int num_coins;
+  unsigned int num_retries;
+
+  if (NULL == pc)
+  {
+    json_t *planchets;
+    json_t *planchet;
+    size_t index;
+
+    pc = GNUNET_new (struct PickupContext);
+    hc->ctx = pc;
+    hc->cc = &pick_context_cleanup;
+
+    GNUNET_assert (NULL != hc->infix);
+    if (GNUNET_OK !=
+        GNUNET_CRYPTO_hash_from_string (hc->infix,
+                                        &pc->tip_id))
+    {
+      /* tip_id has wrong encoding */
+      GNUNET_break_op (0);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_BAD_REQUEST,
+                                         TALER_EC_PARAMETER_MALFORMED,
+                                         "tip_id malformed");
+    }
+
+    {
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_json ("planchets",
+                               &planchets),
+        GNUNET_JSON_spec_end ()
+      };
+      {
+        enum GNUNET_GenericReturnValue res;
+
+        res = TALER_MHD_parse_json_data (connection,
+                                         hc->request_body,
+                                         spec);
+        if (GNUNET_OK != res)
+          return (GNUNET_NO == res)
+                 ? MHD_YES
+                 : MHD_NO;
+      }
+    }
+    if (! json_is_array (planchets))
+    {
+      GNUNET_break_op (0);
+      json_decref (planchets);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_BAD_REQUEST,
+                                         
TALER_EC_PATCH_INSTANCES_BAD_PAYTO_URIS,
+                                         "Invalid bank account information");
+    }
+
+    GNUNET_array_grow (pc->planchets,
+                       pc->planchets_length,
+                       json_array_size (planchets));
+    json_array_foreach (planchets, index, planchet) {
+      struct TALER_PlanchetDetail *pd = &pc->planchets[index];
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
+                                     &pd->denom_pub_hash),
+        GNUNET_JSON_spec_varsize ("coin_ev",
+                                  (void **) &pd->coin_ev,
+                                  &pd->coin_ev_size),
+        GNUNET_JSON_spec_end ()
+      };
+      {
+        enum GNUNET_GenericReturnValue res;
+
+        res = TALER_MHD_parse_json_data (connection,
+                                         planchet,
+                                         spec);
+        if (GNUNET_OK != res)
+        {
+          json_decref (planchets);
+          return (GNUNET_NO == res)
+                 ? MHD_YES
+                 : MHD_NO;
+        }
+      }
+    }
+    json_decref (planchets);
+    {
+      struct GNUNET_HashContext *hc;
+
+      hc = GNUNET_CRYPTO_hash_context_start ();
+      GNUNET_CRYPTO_hash_context_read (hc,
+                                       &pc->tip_id,
+                                       sizeof (pc->tip_id));
+      for (unsigned int i = 0; i<pc->planchets_length; i++)
+      {
+        GNUNET_CRYPTO_hash_context_read (hc,
+                                         &pd->denom_pub_hash,
+                                         sizeof (pd->denom_pub_hash));
+        GNUNET_CRYPTO_hash_context_read (hc,
+                                         pd->coin_ev,
+                                         pd->coin_ev_size);
+      }
+      GNUNET_CRYPTO_hash_context_finish (hc,
+                                         &pc->pickup_id);
+    }
+  }
+
+  if (NULL != pc->response)
+  {
+    MDH_RESULT ret;
+
+    ret = MHD_queue_response (connection,
+                              pc->http_status,
+                              pc->response);
+    pc->response = NULL;
+    return ret;
+  }
+
+  if (! pc->tr_initialized)
+  {
+    MHD_suspend_connection (connection);
+    pc->tt = GNUNET_SCHEDULER_add_delayed (EXCHANGE_TIMEOUT,
+                                           &do_timeout,
+                                           pc);
+    pc->fo = TMH_EXCHANGES_find_exchange (exchange_url,
+                                          NULL,
+                                          GNUNET_NO,
+                                          &compute_total_requested,
+                                          pc);
+    return MHD_YES;
+  }
+
 
-  GNUNET_assert (NULL != hc->infix);
+  TMH_db->preflight (TMH_db->cls);
+  num_retries = 0;
+RETRY:
+  num_retries++;
+  if (num_retries > MAX_RETRIES)
+  {
+    GNUNET_break (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_TIP_PICKUP_DB_ERROR_SOFT,
+                                       "Too many DB serialization failures");
+  }
   if (GNUNET_OK !=
-      GNUNET_CRYPTO_hash_from_string (hc->infix,
-                                      &tip_id))
+      TMH_db->start (TMH_db->cls,
+                     "pickup tip"))
   {
-    /* tip_id has wrong encoding */
-    GNUNET_break_op (0);
+    GNUNET_break (0);
     return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_BAD_REQUEST,
-                                       TALER_EC_PARAMETER_MALFORMED,
-                                       "tip_id malformed");
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_TIP_PICKUP_DB_ERROR_HARD,
+                                       "Could not begin transaction");
   }
+  {
+    struct GNUNET_CRYPTO_RsaSignature *sigs[num_coins];
+
+    qs = TMH_db->lookup_pickup (TMH_db->cls,
+                                hc->instance->settings.id,
+                                &tip_id,
+                                &pickup_id,
+                                &exchange_url,
+                                &pc->reserve_priv;
+                                num_coins,
+                                sigs);
+    if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+    {
+      for (unsigned int i = 0; i<num_coins; i++)
+      {
+        if (NULL == sigs[i])
+        {
+          try_withdraw (pc,
+                        exchange_url,
+                        planchets[i],
+                        i);
+          qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+        }
+      }
+      if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+      {
+        MHD_suspend_connection (connection);
+        GNUNET_CONTAINER_DLL_insert (pc_head,
+                                     pc_tail,
+                                     pc);
+        pc->tt = GNUNET_SCHEDULER_add_delayed (TIMEOUT,
+                                               &do_timeout,
+                                               pc);
+        TMH_db->rollback (TMH_db->cls);
+        return MHD_YES;
+      }
+    }
+    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs)
+    {
+      unsigned int response_code;
+      enum TALER_ErrorCode ec;
 
-  db->preflight (db->cls);
-  // FIXME: logic here is completely bonkers!
-  qs = db->lookup_tip_by_id (db->cls,
-                             &tip_id,
-                             &exchange_url,
-                             &extra,
-                             &tip_amount,
-                             &tip_amount_left,
-                             &timestamp);
+      TMH_db->rollback (TMH_db->cls);
+      switch (qs)
+      {
+      case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+        {
+          json_t *blind_sigs;
+
+          blind_sigs = json_array ();
+          GNUNET_assert (NULL != blind_sigs);
+          for (unsigned int i = 0; i<num_coins; i++)
+          {
+            GNUNET_assert (0 ==
+                           json_array_append_new (
+                             blind_sigs,
+                             GNUNET_JSON_from_rsa_signature (sigs[i])));
+            GNUNET_CRYPTO_rsa_signature_free (sigs[i]);
+          }
+          return TALER_MHD_reply_json_pack (
+            connection,
+            MHD_HTTP_OK,
+            "{s:o}",
+            "blind_sigs", blind_sigs);
+        }
+        break;
+      case GNUNET_DB_STATUS_SOFT_ERROR:
+        goto RETRY;
+        break;
+      case GNUNET_DB_STATUS_HARD_ERROR:
+        ec = TALER_EC_TIP_PICKUP_DB_ERROR_HARD;
+        response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+        break;
+      default:
+        GNUNET_break (0);
+        ec = TALER_EC_INTERNAL_LOGIC_ERROR;
+        response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+        break;
+      }
+      return TALER_MHD_reply_with_error (connection,
+                                         response_code,
+                                         ec,
+                                         "Could not process pickup");
+    }
+  }
 
+  qs = TMH_db->lookup_tip (TMH_db->cls,
+                           hc->instance->settings.id,
+                           &tip_id,
+                           &total_authorized,
+                           &total_picked_up,
+                           &expiration,
+                           &exchange_url,
+                           &pc->reserve_priv);
   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
   {
     unsigned int response_code;
     enum TALER_ErrorCode ec;
 
+    TMH_db->rollback (TMH_db->cls);
     switch (qs)
     {
     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
@@ -97,9 +780,7 @@ TMH_post_tips_ID_pickup (const struct TMH_RequestHandler *rh,
       response_code = MHD_HTTP_NOT_FOUND;
       break;
     case GNUNET_DB_STATUS_SOFT_ERROR:
-      ec = TALER_EC_TIP_PICKUP_DB_ERROR_SOFT;
-      response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
-      break;
+      goto RETRY;
     case GNUNET_DB_STATUS_HARD_ERROR:
       ec = TALER_EC_TIP_PICKUP_DB_ERROR_HARD;
       response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
@@ -113,24 +794,82 @@ TMH_post_tips_ID_pickup (const struct TMH_RequestHandler 
*rh,
     return TALER_MHD_reply_with_error (connection,
                                        response_code,
                                        ec,
-                                       "Could not determine exchange URL for 
the given tip id");
-  }
-
-  timestamp_expire = GNUNET_TIME_absolute_add (timestamp,
-                                               GNUNET_TIME_UNIT_DAYS);
-
-  ret = TALER_MHD_reply_json_pack (
-    connection,
-    MHD_HTTP_OK,
-    "{s:s, s:o, s:o, s:o, s:o, s:o}",
-    "exchange_url", exchange_url,
-    "amount", TALER_JSON_from_amount (&tip_amount),
-    "amount_left", TALER_JSON_from_amount (&tip_amount_left),
-    "stamp_created", GNUNET_JSON_from_time_abs (timestamp),
-    "stamp_expire", GNUNET_JSON_from_time_abs (timestamp_expire),
-    "extra", extra);
-
-  GNUNET_free (exchange_url);
-  json_decref (extra);
-  return ret;
+                                       "Could not process pickup");
+  }
+  if (0 == GNUNET_TIME_absolute_get_remaining (expiration).rel_value_us)
+  {
+    TMH_db->rollback (TMH_db->cls);
+    return TALER_MHD_reply_with_error (connection,
+                                       response_code,
+                                       ec,
+                                       "Could not process pickup");
+  }
+  if (0 >
+      TALER_amount_subtract (&total_left,
+                             &total_authorized,
+                             &total_picked_up))
+  {
+    GNUNET_break (0);
+    TMH_db->rollback (TMH_db->cls);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_INTERNAL_LOGIC_ERROR,
+                                       "tip already overdrawn");
+  }
+
+  if (0 >
+      TALER_amount_cmp (&total_left,
+                        &total_requested))
+  {
+    GNUNET_break (0);
+    TMH_db->rollback (TMH_db->cls);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       
TALER_EC_PICKUP_AMOUNT_EXCEEDS_TIP_REMAINING,
+                                       "requested amount exceeds amount left 
in tip");
+  }
+
+  GNUNET_assert (0 >
+                 TALER_amount_add (&total_picked_up,
+                                   &total_picked_up,
+                                   &total_requested));
+  qs = TMH_db->insert_pickup (TMH_db->cls,
+                              hc->instance->settings.id,
+                              &tip_id,
+                              &total_picked_up,
+                              &pickup_id,
+                              &total_requested);
+  if (qs < 0)
+  {
+    TMH_db->rollback (TMH_db->cls);
+    if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+      goto RETRY;
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_TIP_PICKUP_DB_STORE_HARD_ERROR,
+                                       "Could not store pickup ID in 
database");
+  }
+  qs = TMH_db->commit (TMH_db->cls);
+  if (qs < 0)
+  {
+    TMH_db->rollback (TMH_db->cls);
+    if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+      goto RETRY;
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_TIP_PICKUP_DB_STORE_HARD_ERROR,
+                                       "Could not commit transaction to 
database");
+  }
+  MHD_suspend_connection (connection);
+  pc->tt = GNUNET_SCHEDULER_add_delayed (EXCHANGE_TIMEOUT,
+                                         &do_timeout,
+                                         pc);
+  for (unsigned int i = 0; i<num_coins; i++)
+  {
+    try_withdraw (pc,
+                  exchange_url,
+                  planchets[i],
+                  i);
+  }
+  return MHD_YES;
 }
diff --git a/src/backend/taler-merchant-httpd_private-get-tips-ID.c 
b/src/backend/taler-merchant-httpd_private-get-tips-ID.c
index f186363..6276189 100644
--- a/src/backend/taler-merchant-httpd_private-get-tips-ID.c
+++ b/src/backend/taler-merchant-httpd_private-get-tips-ID.c
@@ -42,14 +42,17 @@ TMH_private_get_tips_ID (const struct TMH_RequestHandler 
*rh,
                          struct MHD_Connection *connection,
                          struct TMH_HandlerContext *hc)
 {
-  char *exchange_url;
   struct GNUNET_HashCode tip_id;
-  struct TALER_Amount tip_amount;
-  struct TALER_Amount tip_amount_left;
-  struct GNUNET_TIME_Absolute timestamp;
+  struct TALER_Amount total_authorized;
+  struct TALER_Amount total_picked_up;
+  char *reason;
+  struct GNUNET_TIME_Absolute expiration;
+  unsigned int pickups_length = 0;
+  struct TALER_MERCHANTDB_PickupDetails *pickups = NULL;
   struct GNUNET_TIME_Absolute timestamp_expire;
-  MHD_RESULT ret;
   enum GNUNET_DB_QueryStatus qs;
+  bool fpu;
+  json_t *pickups_json;
 
   GNUNET_assert (NULL != hc->infix);
   if (GNUNET_OK !=
@@ -63,17 +66,27 @@ TMH_private_get_tips_ID (const struct TMH_RequestHandler 
*rh,
                                        TALER_EC_PARAMETER_MALFORMED,
                                        "tip_id malformed");
   }
+  {
+    const char *pstr;
 
+    pstr = MHD_lookup_connection_value (connection,
+                                        MHD_GET_ARGUMENT_KIND,
+                                        "pickups");
+    fpu = (NULL != pstr) ? 0 == strcasecmp (pstr, "yes") :
+          false;
+  }
   db->preflight (db->cls);
-  // FIXME: logic here is completely bonkers!
-  qs = db->lookup_tip_by_id (db->cls,
-                             &tip_id,
-                             &exchange_url,
-                             &extra,
-                             &tip_amount,
-                             &tip_amount_left,
-                             &timestamp);
-
+  qs = db->lookup_tip_details (db->cls,
+                               hc->instance->settings.id,
+                               &tip_id,
+                               fpu,
+                               &total_authorized,
+                               &total_picked_up,
+                               &reason,
+                               &expiration,
+                               &reserve_pub,
+                               &pickups_length,
+                               &pickups);
   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
   {
     unsigned int response_code;
@@ -104,21 +117,42 @@ TMH_private_get_tips_ID (const struct TMH_RequestHandler 
*rh,
                                        ec,
                                        "Could not determine exchange URL for 
the given tip id");
   }
+  if (fpu)
+  {
+    pickups_json = json_array ();
+    GNUNET_assert (NULL != pickups_json);
+    for (unsigned int i = 0; i<pickups_length; i++)
+    {
+      json_array_append_new (
+        pickups_json,
+        json_pack ("{s:o,s:o,s:o,s:I}",
+                   "pickup_id",
+                   GNUNET_JSON_from_data_auto (&pickups[i].pickup_id),
+                   "requested_amount",
+                   TALER_JSON_from_amount (&pickups[i].requested_amount),
+                   "exchange_amount",
+                   TALER_JSON_from_amount (&pickups[i].exchange_amount),
+                   "num_planchets",
+                   (json_int_t) pickups[i].num_planchets));
+    }
+  }
+  GNUNET_array_grow (pickups,
+                     pickups_length,
+                     0);
+  {
+    MHD_RESULT ret;
 
-  timestamp_expire = GNUNET_TIME_absolute_add (timestamp,
-                                               GNUNET_TIME_UNIT_DAYS);
-
-  ret = TALER_MHD_reply_json_pack (
-    connection,
-    MHD_HTTP_OK,
-    "{s:s, s:o, s:o, s:o, s:o, s:o?}",
-    "reason", reason,
-    "total_authorized", TALER_JSON_from_amount (&tip_amount),
-    "total_picked_up", TALER_JSON_from_amount (&tip_amount_left),
-    "reserve_pub", GNUNET_JSON_from_data_auto (&reserve_pub),
-    "expiration", GNUNET_JSON_from_time_abs (timestamp_expire),
-    "pickups", pickups);
-
-  GNUNET_free (exchange_url);
-  return ret;
+    ret = TALER_MHD_reply_json_pack (
+      connection,
+      MHD_HTTP_OK,
+      "{s:s, s:o, s:o, s:o, s:o, s:o?}",
+      "reason", reason,
+      "total_authorized", TALER_JSON_from_amount (&total_authorized),
+      "total_picked_up", TALER_JSON_from_amount (&total_picked_up),
+      "reserve_pub", GNUNET_JSON_from_data_auto (&reserve_pub),
+      "expiration", GNUNET_JSON_from_time_abs (expiration),
+      "pickups", pickups_json);
+    GNUNET_free (reason);
+    return ret;
+  }
 }
diff --git a/src/include/taler_merchantdb_plugin.h 
b/src/include/taler_merchantdb_plugin.h
index 0960fb9..60bf777 100644
--- a/src/include/taler_merchantdb_plugin.h
+++ b/src/include/taler_merchantdb_plugin.h
@@ -634,6 +634,34 @@ typedef void
   const struct TALER_Amount *refund_fee);
 
 
+/**
+ * Details about a pickup operation executed by the merchant.
+ */
+struct TALER_MERCHANTDB_PickupDetails
+{
+  /**
+   * Identifier for the pickup operation.
+   */
+  struct GNUNET_HashCode pickup_id;
+
+  /**
+   * Total amount requested for this @e pickup_id.
+   */
+  struct TALER_Amount requested_amount;
+
+  /**
+   * Total amount we successfully obtained from the exchange.
+   */
+  struct TALER_Amount exchange_amount;
+
+  /**
+   * Number of planchets involved in the request.
+   */
+  unsigned int num_planchets;
+
+};
+
+
 /**
  * Handle to interact with the database.
  *
@@ -1662,6 +1690,127 @@ struct TALER_MERCHANTDB_Plugin
                    struct GNUNET_TIME_Absolute *expiration);
 
 
+  /**
+   * Lookup pickup details for pickup @a pickup_id.
+   *
+   * @param cls closure, typically a connection to the db
+   * @param instance_id which instance should we lookup tip details for
+   * @param tip_id which tip should we lookup details on
+   * @param pickup_id which pickup should we lookup details on
+   * @param[out] exchange_url which exchange is the tip withdrawn from
+   * @param[out] reserve_priv private key the tip is withdrawn from (set if 
still available!)
+   * @param sigs_length length of the @a sigs array
+   * @param[out] sigs set to the (blind) signatures we have for this @a 
pickup_id,
+   *              those that are unavailable are left at NULL
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lookup_pickup)(void *cls,
+                   const char *instance_id,
+                   const struct GNUNET_HashCode *tip_id,
+                   const struct GNUNET_HashCode *pickup_id,
+                   char **exchange_url,
+                   struct TALER_ReservePrivateKeyP *reserve_priv,
+                   unsigned int sigs_length,
+                   struct GNUNET_CRYPTO_RsaSignature *sigs[]);
+
+
+  /**
+ * Lookup tip details for tip @a tip_id.
+ *
+ * @param cls closure, typically a connection to the db
+ * @param instance_id which instance should we lookup tip details for
+ * @param tip_id which tip should we lookup details on
+ * @param[out] total_authorized amount how high is the tip (with fees)
+ * @param[out] total_picked_up how much of the tip was so far picked up (with 
fees)
+ * @param[out] expiration set to when the tip expires
+ * @param[out] exchange_url set to the exchange URL where the reserve is
+ * @param[out] reserve_priv set to private key of reserve to be debited
+ * @return transaction status,
+ *      #TALER_EC_TIP_AUTHORIZE_RESERVE_EXPIRED if the reserve is known but 
has expired
+ *      #TALER_EC_TIP_AUTHORIZE_RESERVE_UNKNOWN if the reserve is not known
+ *      #TALER_EC_TIP_AUTHORIZE_INSUFFICIENT_FUNDS if the reserve has 
insufficient funds left
+ *      #TALER_EC_TIP_AUTHORIZE_DB_HARD_ERROR on hard DB errors
+ *      #TALER_EC_TIP_AUTHORIZE_DB_SOFT_ERROR on soft DB errors (client should 
retry)
+ *      #TALER_EC_NONE upon success
+ */
+  enum TALER_ErrorCode
+  (*lookup_tip)(void *cls,
+                const char *instance_id,
+                const struct GNUNET_HashCode *tip_id,
+                struct TALER_Amount *total_authorized,
+                struct TALER_Amount *total_picked_up,
+                struct GNUNET_TIME_Absolute *expiration,
+                char **exchange_url,
+                struct TALER_ReservePrivateKeyP *reserve_priv);
+
+
+  /**
+   * Lookup tip details for tip @a tip_id.
+   *
+   * @param cls closure, typically a connection to the db
+   * @param instance_id which instance should we lookup tip details for
+   * @param tip_id which tip should we lookup details on
+   * @param[out] total_authorized amount how high is the tip (with fees)
+   * @param[out] total_picked_up how much of the tip was so far picked up 
(with fees)
+   * @param[out] expiration set to when the tip expires
+   * @param[out] exchange_url set to the exchange URL where the reserve is
+   * @param[out] reserve_priv set to private key of reserve to be debited
+   * @return transaction status,
+   *      #TALER_EC_TIP_AUTHORIZE_RESERVE_EXPIRED if the reserve is known but 
has expired
+   *      #TALER_EC_TIP_AUTHORIZE_RESERVE_UNKNOWN if the reserve is not known
+   *      #TALER_EC_TIP_AUTHORIZE_INSUFFICIENT_FUNDS if the reserve has 
insufficient funds left
+   *      #TALER_EC_TIP_AUTHORIZE_DB_HARD_ERROR on hard DB errors
+   *      #TALER_EC_TIP_AUTHORIZE_DB_SOFT_ERROR on soft DB errors (client 
should retry)
+   *      #TALER_EC_NONE upon success
+   */
+  enum TALER_ErrorCode
+  (*lookup_tip)(void *cls,
+                const char *instance_id,
+                const struct GNUNET_HashCode tip_id,
+                struct TALER_Amount *total_authorized,
+                struct TALER_Amount *total_picked_up,
+                struct GNUNET_TIME_Absolute *expiration,
+                char **exchange_url,
+                struct TALER_ReservePrivateKeyP *reserve_priv);
+
+
+  /**
+   * Lookup tip details for tip @a tip_id.
+   *
+   * @param cls closure, typically a connection to the db
+   * @param instance_id which instance should we lookup tip details for
+   * @param tip_id which tip should we lookup details on
+   * @param fpu should we fetch details about individual pickups
+   * @param[out] total_authorized amount how high is the tip (with fees)
+   * @param[out] total_picked_up how much of the tip was so far picked up 
(with fees)
+   * @param[out] justification why was the tip approved
+   * @param[out] expiration set to when the tip expires
+   * @param[out] reserve_pub set to which reserve is debited
+   * @param[out] pickups_length set to the length of @e pickups
+   * @param[out] pickups if @a fpu is true, set to details about the pickup 
operations
+   * @return transaction status,
+   *      #TALER_EC_TIP_AUTHORIZE_RESERVE_EXPIRED if the reserve is known but 
has expired
+   *      #TALER_EC_TIP_AUTHORIZE_RESERVE_UNKNOWN if the reserve is not known
+   *      #TALER_EC_TIP_AUTHORIZE_INSUFFICIENT_FUNDS if the reserve has 
insufficient funds left
+   *      #TALER_EC_TIP_AUTHORIZE_DB_HARD_ERROR on hard DB errors
+   *      #TALER_EC_TIP_AUTHORIZE_DB_SOFT_ERROR on soft DB errors (client 
should retry)
+   *      #TALER_EC_NONE upon success
+   */
+  enum TALER_ErrorCode
+  (*lookup_tip_details)(void *cls,
+                        const char *instance_id,
+                        const struct GNUNET_HashCode tip_id,
+                        bool fpu,
+                        struct TALER_Amount *total_authorized,
+                        struct TALER_Amount *total_picked_up,
+                        char **justification,
+                        struct GNUNET_TIME_Absolute *expiration,
+                        struct TALER_ReservePublicKeyP *reserve_pub,
+                        unsigned int *pickups_length,
+                        struct TALER_MERCHANTDB_PickupDetails **pickups);
+
+
   /* ****************** OLD API ******************** */
 
 

-- 
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]