/* This file is part of TALER (C) 2020 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. TALER is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with TALER; see the file COPYING. If not, see */ /** * @file taler-merchant-httpd_reserves.c * @brief logic for initially tracking a reserve's status * @author Christian Grothoff */ #include "platform.h" #include #include "taler-merchant-httpd.h" #include "taler-merchant-httpd_exchanges.h" #include "taler-merchant-httpd_reserves.h" /** * Our representation of a reserve that we are (still) checking the status of. */ struct Reserve { /** * Kept in a DLL. */ struct Reserve *next; /** * Kept in a DLL. */ struct Reserve *prev; /** * Reserve's public key. */ struct TALER_ReservePublicKeyP reserve_pub; /** * Amount the merchant expects to see in the reserve initially. * We log a warning if there is a mismatch. */ struct TALER_Amount expected_amount; /** * URL of the exchange hosting this reserve. */ char *exchange_url; /** * Instance this reserve belongs with. */ char *instance_id; /** * Active find operation for this reserve. */ struct TMH_EXCHANGES_FindOperation *fo; /** * Task scheduled waiting for a timeout for this reserve. */ struct GNUNET_SCHEDULER_Task *tt; /** * Get operation with the exchange. */ struct TALER_EXCHANGE_ReservesGetHandle *gh; /** * How long do we wait before trying this reserve again? */ struct GNUNET_TIME_Relative delay; }; /** * Head of DLL of pending reserves. */ static struct Reserve *reserves_head; /** * Tail of DLL of pending reserves. */ static struct Reserve *reserves_tail; /** * Function called to probe a reserve now. * * @param cls a `struct Reserve` to query */ static void try_now (void *cls); /** * Free reserve data structure. * * @param r reserve to free */ static void free_reserve (struct Reserve *r) { GNUNET_CONTAINER_DLL_remove (reserves_head, reserves_tail, r); if (NULL != r->fo) { TMH_EXCHANGES_find_exchange_cancel (r->fo); r->fo = NULL; } if (NULL != r->gh) { TALER_EXCHANGE_reserves_get_cancel (r->gh); r->gh = NULL; } if (NULL != r->tt) { GNUNET_SCHEDULER_cancel (r->tt); r->tt = NULL; } GNUNET_free (r->exchange_url); GNUNET_free (r->instance_id); GNUNET_free (r); } /** * Schedule a job to probe a reserve later again. * * @param r reserve to try again later */ static void try_later (struct Reserve *r) { r->delay = GNUNET_TIME_STD_BACKOFF (r->delay); r->tt = GNUNET_SCHEDULER_add_delayed (r->delay, &try_now, r); } /** * Callbacks of this type are used to serve the result of submitting a * reserve status request to a exchange. * * @param cls closure with a `struct Reserve *` * @param hr HTTP response data * @param balance current balance in the reserve, NULL on error * @param history_length number of entries in the transaction history, 0 on error * @param history detailed transaction history, NULL on error */ static void reserve_cb (void *cls, const struct TALER_EXCHANGE_HttpResponse *hr, const struct TALER_Amount *balance, unsigned int history_length, const struct TALER_EXCHANGE_ReserveHistory *history) { struct Reserve *r = cls; enum GNUNET_DB_QueryStatus qs; r->gh = NULL; if ( (NULL == hr) || (MHD_HTTP_OK != hr->http_status) ) { try_later (r); return; } if (GNUNET_OK != TALER_amount_cmp_currency (&r->expected_amount, balance)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Reserve currency disagreement: exchange `%s' has %s, expected %s\n", r->exchange_url, balance->currency, r->expected_amount.currency); free_reserve (r); return; } if (0 != TALER_amount_cmp (&r->expected_amount, balance)) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Reserve initial balance disagreement: exchange `%s' received `%s'\n", r->exchange_url, TALER_amount2s (balance)); } qs = TMH_db->activate_reserve (TMH_db->cls, r->instance_id, &r->reserve_pub, balance); if (qs <= 0) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to commit reserve activation to database (%d)\n", (int) qs); } else { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Reserve activated with initial balance %s\n", TALER_amount2s (balance)); } free_reserve (r); } /** * Function called with the result of a #TMH_EXCHANGES_find_exchange() * operation. * * @param cls closure * @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 find_cb (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 Reserve *r = cls; r->fo = NULL; if (NULL == eh) { try_later (r); return; } r->gh = TALER_EXCHANGE_reserves_get (eh, &r->reserve_pub, &reserve_cb, r); if (NULL == r->gh) { try_later (r); return; } } /** * Function called to probe a reserve now. * * @param cls a `struct Reserve` to query */ static void try_now (void *cls) { struct Reserve *r = cls; r->tt = NULL; r->fo = TMH_EXCHANGES_find_exchange (r->exchange_url, NULL, GNUNET_NO, &find_cb, r); if (NULL == r->fo) { try_later (r); return; } } /** * Function called with information about a reserve that we need * to check the status from at the exchange to see if/when it has * been filled (and with what amount). * * @param cls closure, NULL * @param instance_id for which instance is this reserve * @param exchange_url base URL of the exchange at which the reserve lives * @param reserve_pub public key of the reserve * @param expected_amount how much do we expect to see in the reserve */ static void add_reserve (void *cls, const char *instance_id, const char *exchange_url, const struct TALER_ReservePublicKeyP *reserve_pub, const struct TALER_Amount *expected_amount) { struct Reserve *r; (void) cls; r = GNUNET_new (struct Reserve); r->exchange_url = GNUNET_strdup (exchange_url); r->instance_id = GNUNET_strdup (instance_id); r->reserve_pub = *reserve_pub; r->expected_amount = *expected_amount; GNUNET_CONTAINER_DLL_insert (reserves_head, reserves_tail, r); try_now (r); } /** * Load information about reserves and start querying reserve status. * Must be called after the database is available. */ void TMH_RESERVES_init (void) { TMH_db->lookup_pending_reserves (TMH_db->cls, &add_reserve, NULL); } /** * Add a reserve to the list of reserves to check. * * @param instance_id which instance is the reserve for * @param exchange_url URL of the exchange with the reserve * @param reserve_pub public key of the reserve to check * @param expected_amount amount the merchant expects to see initially in the reserve */ void TMH_RESERVES_check (const char *instance_id, const char *exchange_url, const struct TALER_ReservePublicKeyP *reserve_pub, const struct TALER_Amount *expected_amount) { add_reserve (NULL, instance_id, exchange_url, reserve_pub, expected_amount); } /** * Stop checking reserve status. */ void TMH_RESERVES_done (void) { while (NULL != reserves_head) free_reserve (reserves_head); } /* end of taler-merchant-httpd_reserves.c */