From e12f64d45b2f58b716497aab6fd6273b8f92b740 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Wed, 9 Sep 2020 23:11:51 +0200 Subject: likely fix for #6581, still needs testcase --- src/backend/taler-merchant-httpd.c | 136 +++++++++++++++++++++ src/backend/taler-merchant-httpd.h | 29 ++++- src/backend/taler-merchant-httpd_get-orders-ID.c | 2 + .../taler-merchant-httpd_post-orders-ID-paid.c | 13 +- .../taler-merchant-httpd_post-orders-ID-pay.c | 22 +++- .../taler-merchant-httpd_private-get-orders-ID.c | 2 + 6 files changed, 196 insertions(+), 8 deletions(-) diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c index 22e11d74..a6a45928 100644 --- a/src/backend/taler-merchant-httpd.c +++ b/src/backend/taler-merchant-httpd.c @@ -290,6 +290,40 @@ compute_pay_key (const char *order_id, } +/** + * Compute @a key to use for @a session_id and @a fulfillment_url in our + * #payment_trigger_map. + * + * @param session_id the session for which @a fulfillment_url matters + * @param fulfillment_url fullfillment URL of an order + * @param key[out] set to the hash map key to use + */ +static void +compute_pay_key2 (const char *session_id, + const char *fulfillment_url, + struct GNUNET_HashCode *key) +{ + size_t slen = strlen (session_id) + 1; + size_t ulen = strlen (fulfillment_url) + 1; + char buf[slen + ulen]; + + memcpy (buf, + session_id, + slen); + memcpy (&buf[slen], + fulfillment_url, + ulen); + GNUNET_CRYPTO_hash (buf, + sizeof (buf), + key); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Pay key for %s/%s is %s\n", + session_id, + fulfillment_url, + GNUNET_h2s (key)); +} + + /** * Resume processing all suspended connections past timeout. * @@ -318,6 +352,11 @@ do_resume (void *cls) GNUNET_CONTAINER_multihashmap_remove (payment_trigger_map, &sc->key, sc)); + if (sc->has_key2) + GNUNET_assert (GNUNET_YES == + GNUNET_CONTAINER_multihashmap_remove (payment_trigger_map, + &sc->key2, + sc)); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Resuming long polled job due to timeout\n"); MHD_resume_connection (sc->con); @@ -333,6 +372,8 @@ do_resume (void *cls) * Suspend connection from @a sc until payment has been received. * * @param order_id the order that we are waiting on + * @param session_id session ID of the requester + * @param fulfillment_url fulfillment URL of the contract * @param mi the merchant instance we are waiting on * @param sc connection to suspend * @param min_refund refund amount we are waiting on to be exceeded before resuming, @@ -340,6 +381,8 @@ do_resume (void *cls) */ void TMH_long_poll_suspend (const char *order_id, + const char *session_id, + const char *fulfillment_url, const struct TMH_MerchantInstance *mi, struct TMH_SuspendedConnection *sc, const struct TALER_Amount *min_refund) @@ -355,6 +398,22 @@ TMH_long_poll_suspend (const char *order_id, &sc->key, sc, GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE)); + if ( (NULL != session_id) && + (NULL != fulfillment_url) ) + { + sc->has_key2 = true; + compute_pay_key (order_id, + &mi->merchant_pub, + &sc->key2); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Suspending operation on key2 %s\n", + GNUNET_h2s (&sc->key2)); + GNUNET_assert (GNUNET_OK == + GNUNET_CONTAINER_multihashmap_put (payment_trigger_map, + &sc->key2, + sc, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE)); + } if (NULL != min_refund) { sc->awaiting_refund = true; @@ -392,6 +451,9 @@ resume_operation (void *cls, const struct ResumeData *rd = cls; struct TMH_SuspendedConnection *sc = value; + GNUNET_assert (0 == + GNUNET_memcmp (key, + &sc->key)); /* If the conditions are satisfied partially, turn them off for future calls. */ if ( (sc->awaiting_refund_obtained) && @@ -427,6 +489,11 @@ resume_operation (void *cls, GNUNET_CONTAINER_multihashmap_remove (payment_trigger_map, key, sc)); + if (sc->has_key2) + GNUNET_assert (GNUNET_YES == + GNUNET_CONTAINER_multihashmap_remove (payment_trigger_map, + &sc->key2, + sc)); GNUNET_assert (sc == GNUNET_CONTAINER_heap_remove_node (sc->hn)); sc->hn = NULL; @@ -475,6 +542,75 @@ TMH_long_poll_resume (const char *order_id, } +/** + * Function called to resume suspended connections. + * + * @param cls NULL + * @param key key in the #payment_trigger_map + * @param value a `struct TMH_SuspendedConnection` to resume + * @return #GNUNET_OK (continue to iterate) + */ +static int +resume_operation2 (void *cls, + const struct GNUNET_HashCode *key, + void *value) +{ + struct TMH_SuspendedConnection *sc = value; + + GNUNET_assert (sc->has_key2); + GNUNET_assert (0 == GNUNET_memcmp (key, + &sc->key2)); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Resuming operation suspended pending payment on key %s\n", + GNUNET_h2s (key)); + GNUNET_assert (GNUNET_YES == + GNUNET_CONTAINER_multihashmap_remove (payment_trigger_map, + &sc->key, + sc)); + GNUNET_assert (GNUNET_YES == + GNUNET_CONTAINER_multihashmap_remove (payment_trigger_map, + &sc->key2, + sc)); + GNUNET_assert (sc == + GNUNET_CONTAINER_heap_remove_node (sc->hn)); + sc->hn = NULL; + MHD_resume_connection (sc->con); + TMH_trigger_daemon (); + return GNUNET_OK; +} + + +/** + * Find out if we have any clients long-polling for @a order_id to be + * confirmed at merchant @a mpub, and if so, tell them to resume. + * + * @param session_id the session for which @a fulfillment_url became paid + * @param fulfillment_url fullfillment URL of which an order was paid + */ +void +TMH_long_poll_resume2 (const char *session_id, + const char *fulfillment_url) +{ + struct GNUNET_HashCode key; + int ret; + + compute_pay_key2 (session_id, + fulfillment_url, + &key); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Resuming operations suspended pending payment on key %s\n", + GNUNET_h2s (&key)); + ret = GNUNET_CONTAINER_multihashmap_get_multiple (payment_trigger_map, + &key, + &resume_operation2, + NULL); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%u operations remain suspended pending payment (%d)\n", + GNUNET_CONTAINER_multihashmap_size (payment_trigger_map), + ret); +} + + /** * Shutdown task (magically invoked when the application is being * quit) diff --git a/src/backend/taler-merchant-httpd.h b/src/backend/taler-merchant-httpd.h index 0d78312e..c631729d 100644 --- a/src/backend/taler-merchant-httpd.h +++ b/src/backend/taler-merchant-httpd.h @@ -343,6 +343,13 @@ struct TMH_SuspendedConnection */ struct GNUNET_HashCode key; + /** + * Optional session/fulfillment URI-based key + * of this entry in the #payment_trigger_map. Used internally by + * TMH_long_poll_resume2(). + */ + struct GNUNET_HashCode key2; + /** * At what time does this request expire? If set in the future, we * may wait this long for a payment to arrive before responding. @@ -355,7 +362,7 @@ struct TMH_SuspendedConnection struct TALER_Amount refund_expected; /** - * #GNUNET_YES if we are waiting for a refund. + * true if we are waiting for a refund. */ bool awaiting_refund; @@ -364,6 +371,10 @@ struct TMH_SuspendedConnection */ bool awaiting_refund_obtained; + /** + * True if @a key2 is set. + */ + bool has_key2; }; @@ -411,6 +422,8 @@ TMH_trigger_daemon (void); * Suspend connection from @a sc until payment has been received. * * @param order_id the order that we are waiting on + * @param session_id session ID of the requester + * @param fulfillment_url fulfillment URL of the contract * @param mi the merchant instance we are waiting on * @param sc connection to suspend * @param min_refund refund amount we are waiting on to be exceeded before resuming, @@ -418,6 +431,8 @@ TMH_trigger_daemon (void); */ void TMH_long_poll_suspend (const char *order_id, + const char *session_id, + const char *fulfillment_url, const struct TMH_MerchantInstance *mi, struct TMH_SuspendedConnection *sc, const struct TALER_Amount *min_refund); @@ -439,6 +454,18 @@ TMH_long_poll_resume (const char *order_id, bool obtained); +/** + * Find out if we have any clients long-polling for @a order_id to be + * confirmed at merchant @a mpub, and if so, tell them to resume. + * + * @param session_id the session for which @a fulfillment_url became paid + * @param fulfillment_url fullfillment URL of which an order was paid + */ +void +TMH_long_poll_resume2 (const char *session_id, + const char *fulfillment_url); + + /** * Decrement reference counter of @a mi, and free if it hits zero. * diff --git a/src/backend/taler-merchant-httpd_get-orders-ID.c b/src/backend/taler-merchant-httpd_get-orders-ID.c index 82d74f48..e875a3be 100644 --- a/src/backend/taler-merchant-httpd_get-orders-ID.c +++ b/src/backend/taler-merchant-httpd_get-orders-ID.c @@ -192,6 +192,8 @@ suspend_god (struct GetOrderData *god) "Suspending GET /orders/%s\n", god->order_id); TMH_long_poll_suspend (god->order_id, + god->session_id, + god->fulfillment_url, god->hc->instance, &god->sc, god->sc.awaiting_refund diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-paid.c b/src/backend/taler-merchant-httpd_post-orders-ID-paid.c index bcb9aa07..bd2c555a 100644 --- a/src/backend/taler-merchant-httpd_post-orders-ID-paid.c +++ b/src/backend/taler-merchant-httpd_post-orders-ID-paid.c @@ -180,11 +180,16 @@ TMH_post_orders_ID_paid (const struct TMH_RequestHandler *rh, NULL); } } + if (NULL != session_id) { - // FIXME-#6581: extract fulfillment_url from contract_terms. - // IF present, *ALSO* resume long-polling clients for the - // same fulfillment URL + session_id! - // NOTE: also should do the same in the pay handler! + const char *fulfillment_url; + + fulfillment_url + = json_string_value (json_object_get (contract_terms, + "fulfillment_url")); + if (NULL != fulfillment_url) + TMH_long_poll_resume2 (session_id, + fulfillment_url); } json_decref (contract_terms); diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c index 9c75e487..fd6cc7ab 100644 --- a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c +++ b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c @@ -198,6 +198,11 @@ struct PayContext */ char *order_id; + /** + * Fulfillment URL from the contract, or NULL if we don't have one. + */ + char *fulfillment_url; + /** * Serial number of this order in the database (set once we did the lookup). */ @@ -521,6 +526,7 @@ pay_context_cleanup (void *cls) pc->response = NULL; } GNUNET_free (pc->order_id); + GNUNET_free (pc->fulfillment_url); GNUNET_free (pc->session_id); GNUNET_CONTAINER_DLL_remove (pc_head, pc_tail, @@ -1443,6 +1449,10 @@ begin_transaction (struct PayContext *pc) } /* Notify clients that have been waiting for the payment to succeed */ + if ( (NULL != pc->session_id) && + (NULL != pc->fulfillment_url) ) + TMH_long_poll_resume2 (pc->session_id, + pc->fulfillment_url); TMH_long_poll_resume (pc->order_id, hc->instance, NULL, @@ -1729,10 +1739,16 @@ parse_pay (struct MHD_Connection *connection, GNUNET_JSON_spec_end () }; enum GNUNET_GenericReturnValue res; + const char *fulfillment_url; res = TALER_MHD_parse_internal_json_data (connection, contract_terms, espec); + fulfillment_url + = json_string_value (json_object_get (contract_terms, + "fulfillment_url")); + if (NULL != fulfillment_url) + pc->fulfillment_url = GNUNET_strdup (fulfillment_url); json_decref (contract_terms); if (GNUNET_YES != res) { @@ -1884,9 +1900,9 @@ TMH_post_orders_ID_pay (const struct TMH_RequestHandler *rh, "Suspending pay handling while working with the exchange\n"); GNUNET_assert (NULL == pc->timeout_task); pc->timeout_task = - GNUNET_SCHEDULER_add_delayed (get_pay_timeout (pc->coins_cnt), - &handle_pay_timeout, - pc); + GNUNET_SCHEDULER_add_delayed (get_pay_timeout (pc->coins_cnt), + &handle_pay_timeout, + pc); begin_transaction (pc); return MHD_YES; } diff --git a/src/backend/taler-merchant-httpd_private-get-orders-ID.c b/src/backend/taler-merchant-httpd_private-get-orders-ID.c index 5c43416b..d3413614 100644 --- a/src/backend/taler-merchant-httpd_private-get-orders-ID.c +++ b/src/backend/taler-merchant-httpd_private-get-orders-ID.c @@ -1035,6 +1035,8 @@ TMH_private_get_orders_ID (const struct TMH_RequestHandler *rh, "Suspending GET /private/orders/%s\n", hc->infix); TMH_long_poll_suspend (hc->infix, + gorc->session_id, + gorc->fulfillment_url, hc->instance, &gorc->sc, NULL); -- cgit v1.2.3