diff options
-rw-r--r-- | src/backend/taler-merchant-httpd.c | 1 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd.h | 27 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_post-tips-ID-pickup.c | 56 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_private-get-tips-ID.c | 319 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_private-get-tips-ID.h | 9 | ||||
-rw-r--r-- | src/include/taler_merchant_service.h | 1 |
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. |