summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/backend/taler-merchant-httpd.c1
-rw-r--r--src/backend/taler-merchant-httpd.h27
-rw-r--r--src/backend/taler-merchant-httpd_post-tips-ID-pickup.c56
-rw-r--r--src/backend/taler-merchant-httpd_private-get-tips-ID.c319
-rw-r--r--src/backend/taler-merchant-httpd_private-get-tips-ID.h9
-rw-r--r--src/include/taler_merchant_service.h1
6 files changed, 374 insertions, 39 deletions
diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c
index 8e1a0fc0..bebe9f32 100644
--- a/src/backend/taler-merchant-httpd.c
+++ b/src/backend/taler-merchant-httpd.c
@@ -282,6 +282,7 @@ static void
do_shutdown (void *cls)
{
(void) cls;
+ TMH_force_tip_resume ();
TMH_force_ac_resume ();
TMH_force_pc_resume ();
TMH_force_kyc_resume ();
diff --git a/src/backend/taler-merchant-httpd.h b/src/backend/taler-merchant-httpd.h
index e8911b32..391987ab 100644
--- a/src/backend/taler-merchant-httpd.h
+++ b/src/backend/taler-merchant-httpd.h
@@ -268,6 +268,33 @@ struct TMH_OrderRefundEventP
/**
+ * Event generated when a client picks up a tip.
+ */
+struct TMH_TipPickupEventP
+{
+ /**
+ * Type is #TALER_DBEVENT_MERCHANT_TIP_PICKUP.
+ */
+ struct GNUNET_DB_EventHeaderP header;
+
+ /**
+ * Always zero (for alignment).
+ */
+ uint32_t reserved GNUNET_PACKED;
+
+ /**
+ * Tip ID.
+ */
+ struct TALER_TipIdentifierP tip_id;
+
+ /**
+ * Hash of the instance ID.
+ */
+ struct GNUNET_HashCode h_instance;
+
+};
+
+/**
* Possible flags indicating the state of an order.
*/
enum TMH_OrderStateFlags
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 5ef3cb40..24bbba35 100644
--- a/src/backend/taler-merchant-httpd_post-tips-ID-pickup.c
+++ b/src/backend/taler-merchant-httpd_post-tips-ID-pickup.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2017-2021 Taler Systems SA
+ (C) 2017-2023 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
@@ -22,6 +22,7 @@
#include <microhttpd.h>
#include <jansson.h>
#include <taler/taler_json_lib.h>
+#include <taler/taler_dbevents.h>
#include <taler/taler_signatures.h>
#include "taler-merchant-httpd.h"
#include "taler-merchant-httpd_mhd.h"
@@ -112,6 +113,11 @@ struct PickupContext
struct MHD_Connection *connection;
/**
+ * Request context.
+ */
+ struct TMH_HandlerContext *hc;
+
+ /**
* Timeout task.
*/
struct GNUNET_SCHEDULER_Task *tt;
@@ -177,6 +183,18 @@ struct PickupContext
* True if @e total_requested has been initialized.
*/
bool tr_initialized;
+
+ /**
+ * Should we generate a DB notification at the end for the pickup? Used to
+ * wake up long pollers upon tip pickup. Not done transactionally as there
+ * are potentially several coins individually added to the DB as
+ * transactions, and doing a notification per coin would be excessive.
+ * (And missing an event in the very rare case where our process fails
+ * hard between a DB operation and generating an HTTP reply is not a problem
+ * in this case.) However, if we in the future do group all DB transactions
+ * into one larger transaction, the notification should become part of it.
+ */
+ bool do_notify;
};
@@ -239,6 +257,22 @@ pick_context_cleanup (void *cls)
{
struct PickupContext *pc = cls;
+ if (pc->do_notify)
+ {
+ struct TMH_TipPickupEventP tip_eh = {
+ .header.size = htons (sizeof (tip_eh)),
+ .header.type = htons (TALER_DBEVENT_MERCHANT_TIP_PICKUP),
+ .tip_id = pc->tip_id
+ };
+
+ GNUNET_CRYPTO_hash (pc->hc->instance->settings.id,
+ strlen (pc->hc->instance->settings.id),
+ &tip_eh.h_instance);
+ TMH_db->event_notify (TMH_db->cls,
+ &tip_eh.header,
+ NULL,
+ 0);
+ }
stop_operations (pc); /* should not be any... */
for (unsigned int i = 0; i<pc->planchets_length; i++)
TALER_planchet_detail_free (&pc->planchets[i]);
@@ -326,15 +360,15 @@ withdraw_cb (void *cls,
TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
return;
}
- if (NULL == pc->po_head)
- {
- stop_operations (pc); /* stops timeout job */
- GNUNET_CONTAINER_DLL_remove (pc_head,
- pc_tail,
- pc);
- MHD_resume_connection (pc->connection);
- TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
- }
+ pc->do_notify = true;
+ if (NULL != pc->po_head)
+ return; /* More pending */
+ stop_operations (pc); /* stops timeout job */
+ GNUNET_CONTAINER_DLL_remove (pc_head,
+ pc_tail,
+ pc);
+ MHD_resume_connection (pc->connection);
+ TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
}
@@ -636,7 +670,7 @@ TMH_post_tips_ID_pickup (const struct TMH_RequestHandler *rh,
pc = GNUNET_new (struct PickupContext);
hc->ctx = pc;
hc->cc = &pick_context_cleanup;
-
+ pc->hc = hc;
GNUNET_assert (NULL != hc->infix);
if (GNUNET_OK !=
GNUNET_CRYPTO_hash_from_string (hc->infix,
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 17002168..8be57417 100644
--- a/src/backend/taler-merchant-httpd_private-get-tips-ID.c
+++ b/src/backend/taler-merchant-httpd_private-get-tips-ID.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2017-2021 Taler Systems SA
+ (C) 2017-2023 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
@@ -21,6 +21,7 @@
#include "platform.h"
#include <microhttpd.h>
#include <jansson.h>
+#include <taler/taler_dbevents.h>
#include <taler/taler_json_lib.h>
#include <taler/taler_signatures.h>
#include "taler-merchant-httpd.h"
@@ -28,12 +29,164 @@
#include "taler-merchant-httpd_exchanges.h"
+/**
+ * Information we keep per /kyc request.
+ */
+struct TipContext
+{
+ /**
+ * Stored in a DLL.
+ */
+ struct TipContext *next;
+
+ /**
+ * Stored in a DLL.
+ */
+ struct TipContext *prev;
+
+ /**
+ * Connection we are handling.
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * Our handler context.
+ */
+ struct TMH_HandlerContext *hc;
+
+ /**
+ * Database event we are waiting on to be resuming.
+ */
+ struct GNUNET_DB_EventHandler *eh;
+
+ /**
+ * Response to return, NULL if we don't have one yet.
+ */
+ struct MHD_Response *response;
+
+ /**
+ * When does this request time out?
+ */
+ struct GNUNET_TIME_Absolute timeout;
+
+ /**
+ * ID of the tip being queried.
+ */
+ struct TALER_TipIdentifierP tip_id;
+
+ /**
+ * Minimum tip amount picked up we should return to the
+ * client.
+ */
+ struct TALER_Amount min_amount;
+
+ /**
+ * #GNUNET_NO if the @e connection was not suspended,
+ * #GNUNET_YES if the @e connection was suspended,
+ * #GNUNET_SYSERR if @e connection was resumed to as
+ * part of #MH_force_pc_resume during shutdown.
+ */
+ enum GNUNET_GenericReturnValue suspended;
+
+ /**
+ * Is the "pickups" argument set to "yes"?
+ */
+ bool fpu;
+
+};
+
+
+/**
+ * Head of DLL.
+ */
+static struct TipContext *tc_head;
+
+/**
+ * Tail of DLL.
+ */
+static struct TipContext *tc_tail;
+
+
+void
+TMH_force_tip_resume ()
+{
+ for (struct TipContext *tc = tc_head;
+ NULL != tc;
+ tc = tc->next)
+ {
+ if (GNUNET_YES == tc->suspended)
+ {
+ tc->suspended = GNUNET_SYSERR;
+ MHD_resume_connection (tc->connection);
+ }
+ }
+}
+
+
+/**
+ * Custom cleanup routine for a `struct TipContext`.
+ *
+ * @param cls the `struct TipContext` to clean up.
+ */
+static void
+tip_context_cleanup (void *cls)
+{
+ struct TipContext *tc = cls;
+
+ if (NULL != tc->response)
+ {
+ MHD_destroy_response (tc->response);
+ tc->response = NULL;
+ }
+ if (NULL != tc->eh)
+ {
+ TMH_db->event_listen_cancel (tc->eh);
+ tc->eh = NULL;
+ }
+ GNUNET_CONTAINER_DLL_remove (tc_head,
+ tc_tail,
+ tc);
+ GNUNET_free (tc);
+}
+
+
+/**
+ * We have received a trigger from the database
+ * that we should (possibly) resume the request.
+ *
+ * @param cls a `struct TipContext` to resume
+ * @param extra usually NULL
+ * @param extra_size number of bytes in @a extra
+ */
+static void
+resume_by_event (void *cls,
+ const void *extra,
+ size_t extra_size)
+{
+ struct TipContext *tc = cls;
+
+ (void) extra;
+ (void) extra_size;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Resuming request %p by trigger\n",
+ tc);
+ if (GNUNET_NO == tc->suspended)
+ return; /* duplicate event is possible */
+ tc->suspended = GNUNET_NO;
+ GNUNET_CONTAINER_DLL_remove (tc_head,
+ tc_tail,
+ tc);
+ MHD_resume_connection (tc->connection);
+ TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
+}
+
+
MHD_RESULT
TMH_private_get_tips_ID (const struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
struct TMH_HandlerContext *hc)
{
- struct TALER_TipIdentifierP tip_id;
+ struct TipContext *tc = hc->ctx;
struct TALER_Amount total_authorized;
struct TALER_Amount total_picked_up;
char *reason;
@@ -42,38 +195,132 @@ TMH_private_get_tips_ID (const struct TMH_RequestHandler *rh,
unsigned int pickups_length = 0;
struct TALER_MERCHANTDB_PickupDetails *pickups = NULL;
enum GNUNET_DB_QueryStatus qs;
- bool fpu;
json_t *pickups_json = NULL;
(void) rh;
- GNUNET_assert (NULL != hc->infix);
- // FIXME: support long-polling (client-side already implemented)
- if (GNUNET_OK !=
- GNUNET_CRYPTO_hash_from_string (hc->infix,
- &tip_id.hash))
- {
- /* tip_id has wrong encoding */
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- hc->infix);
- }
+ if (NULL == tc)
{
- const char *pstr;
-
- pstr = MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
- "pickups");
- fpu = (NULL != pstr)
- ? 0 == strcasecmp (pstr, "yes")
- : false;
+ tc = GNUNET_new (struct TipContext);
+ hc->ctx = tc;
+ hc->cc = &tip_context_cleanup;
+ GNUNET_CONTAINER_DLL_insert (tc_head,
+ tc_tail,
+ tc);
+ tc->connection = connection;
+ tc->hc = hc;
+ GNUNET_assert (NULL != hc->infix);
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_hash_from_string (hc->infix,
+ &tc->tip_id.hash))
+ {
+ /* tip_id has wrong encoding */
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ hc->infix);
+ }
+ {
+ const char *pstr;
+
+ pstr = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "pickups");
+ tc->fpu = (NULL != pstr)
+ ? 0 == strcasecmp (pstr, "yes")
+ : false;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TMH_currency,
+ &tc->min_amount));
+ {
+ const char *min_amount;
+
+ min_amount = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "min_amount");
+ if (NULL != min_amount)
+ {
+ if (GNUNET_OK !=
+ TALER_string_to_amount (min_amount,
+ &tc->min_amount))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "min_amount");
+ }
+ if (0 !=
+ strcasecmp (tc->min_amount.currency,
+ TMH_currency))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_CURRENCY_MISMATCH,
+ TMH_currency);
+ }
+ }
+ }
+ /* process 'timeout_ms' argument */
+ {
+ const char *long_poll_timeout_s;
+
+ long_poll_timeout_s = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "timeout_ms");
+ if (NULL != long_poll_timeout_s)
+ {
+ unsigned int timeout_ms;
+ char dummy;
+ struct GNUNET_TIME_Relative timeout;
+
+ if (1 != sscanf (long_poll_timeout_s,
+ "%u%c",
+ &timeout_ms,
+ &dummy))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "timeout_ms must be non-negative number");
+ }
+ timeout = GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_MILLISECONDS,
+ timeout_ms);
+ tc->timeout = GNUNET_TIME_relative_to_absolute (timeout);
+ if (! GNUNET_TIME_relative_is_zero (timeout))
+ {
+ struct TMH_TipPickupEventP tip_eh = {
+ .header.size = htons (sizeof (tip_eh)),
+ .header.type = htons (TALER_DBEVENT_MERCHANT_TIP_PICKUP),
+ .tip_id = tc->tip_id
+ };
+
+ GNUNET_CRYPTO_hash (hc->instance->settings.id,
+ strlen (hc->instance->settings.id),
+ &tip_eh.h_instance);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Subscribing to payment triggers for %p\n",
+ tc);
+ tc->eh = TMH_db->event_listen (TMH_db->cls,
+ &tip_eh.header,
+ timeout,
+ &resume_by_event,
+ tc);
+ }
+ }
+ }
}
+
+ GNUNET_assert (GNUNET_YES != tc->suspended);
TMH_db->preflight (TMH_db->cls);
qs = TMH_db->lookup_tip_details (TMH_db->cls,
hc->instance->settings.id,
- &tip_id,
- fpu,
+ &tc->tip_id,
+ tc->fpu,
&total_authorized,
&total_picked_up,
&reason,
@@ -111,7 +358,25 @@ TMH_private_get_tips_ID (const struct TMH_RequestHandler *rh,
ec,
NULL);
}
- if (fpu)
+ /* do not allow timeout above tip expiration */
+ tc->timeout = GNUNET_TIME_absolute_min (tc->timeout,
+ expiration.abs_time);
+ if ( (NULL != tc->eh) &&
+ (GNUNET_TIME_absolute_is_future (tc->timeout)) &&
+ (1 == TALER_amount_cmp (&tc->min_amount,
+ &total_picked_up)) )
+ {
+ MHD_suspend_connection (connection);
+ tc->suspended = GNUNET_YES;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Suspending TIP request handling as pickup is below threshold requested by client\n");
+ GNUNET_array_grow (pickups,
+ pickups_length,
+ 0);
+ GNUNET_free (reason);
+ return MHD_YES;
+ }
+ if (tc->fpu)
{
pickups_json = json_array ();
GNUNET_assert (NULL != pickups_json);
diff --git a/src/backend/taler-merchant-httpd_private-get-tips-ID.h b/src/backend/taler-merchant-httpd_private-get-tips-ID.h
index a99dc365..60e30560 100644
--- a/src/backend/taler-merchant-httpd_private-get-tips-ID.h
+++ b/src/backend/taler-merchant-httpd_private-get-tips-ID.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2017 Taler Systems SA
+ (C) 2017, 2023 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
@@ -25,6 +25,13 @@
/**
+ * Force wake-up of all suspended tipping long-pollers.
+ */
+void
+TMH_force_tip_resume (void);
+
+
+/**
* Manages a GET /tips/$ID call, returning the status of the tip.
*
* @param rh context of the handler
diff --git a/src/include/taler_merchant_service.h b/src/include/taler_merchant_service.h
index 863f1676..49bc83fd 100644
--- a/src/include/taler_merchant_service.h
+++ b/src/include/taler_merchant_service.h
@@ -3616,6 +3616,7 @@ typedef void
void *cls,
const struct TALER_MERCHANT_TipWalletGetResponse *wgr);
+
/**
* Issue a GET /tips/$TIP_ID (public variant) request to the backend. Returns
* information needed to pick up a tip.