/*
This file is part of TALER
(C) 2020, 2021 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"
/**
* How long do we keep the long-poller open?
* Not very long here, as if the money has not
* yet arrived, there is a fair chance that it'll
* take much longer, and in that case we rather
* enter into the delay created by try_later().
*/
#define LONGPOLL_DELAY GNUNET_TIME_UNIT_MINUTES
/**
* 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)
{
/* minimum delay is the #LONGPOLL_DELAY */
r->delay = GNUNET_TIME_relative_max (LONGPOLL_DELAY,
r->delay);
/* STD_BACKOFF has a maximum of 15 minutes */
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,
LONGPOLL_DELAY,
&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);
}
void
TMH_RESERVES_init (void)
{
TMH_db->lookup_pending_reserves (TMH_db->cls,
&add_reserve,
NULL);
}
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);
}
void
TMH_RESERVES_done (void)
{
while (NULL != reserves_head)
free_reserve (reserves_head);
}
/* end of taler-merchant-httpd_reserves.c */