/*
This file is part of TALER
(C) 2017-2021 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
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_post-tips-ID-pickup.c
* @brief implementation of a POST /tips/ID/pickup handler
* @author Christian Grothoff
*/
#include "platform.h"
#include
#include
#include
#include
#include "taler-merchant-httpd.h"
#include "taler-merchant-httpd_mhd.h"
#include "taler-merchant-httpd_helper.h"
#include "taler-merchant-httpd_exchanges.h"
#include "taler-merchant-httpd_post-tips-ID-pickup.h"
/**
* How often do we retry on serialization errors?
*/
#define MAX_RETRIES 3
/**
* How long do we give the exchange operation to complete withdrawing
* all of the planchets?
*/
#define EXCHANGE_TIMEOUT GNUNET_TIME_relative_multiply ( \
GNUNET_TIME_UNIT_SECONDS, 45)
/**
* Active pickup operations.
*/
struct PickupContext;
/**
* Handle for an individual planchet we are processing for a tip.
*/
struct PlanchetOperation
{
/**
* Active pickup operation this planchet belongs with.
*/
struct PickupContext *pc;
/**
* Kept in a DLL.
*/
struct PlanchetOperation *prev;
/**
* Kept in a DLL.
*/
struct PlanchetOperation *next;
/**
* Find operation (while active), later NULL.
*/
struct TMH_EXCHANGES_FindOperation *fo;
/**
* Withdraw handle (NULL while @e fo is active).
*/
struct TALER_EXCHANGE_Withdraw2Handle *w2h;
/**
* Details about the planchet for withdrawing.
*/
struct TALER_PlanchetDetail pd;
/**
* Offset of this planchet in the original request.
*/
unsigned int offset;
};
/**
* Active pickup operations.
*/
struct PickupContext
{
/**
* Kept in a DLL.
*/
struct PickupContext *next;
/**
* Kept in a DLL.
*/
struct PickupContext *prev;
/**
* The connection.
*/
struct MHD_Connection *connection;
/**
* Timeout task.
*/
struct GNUNET_SCHEDULER_Task *tt;
/**
* Head of DLL of exchange operations on planchets.
*/
struct PlanchetOperation *po_head;
/**
* Tail of DLL of exchange operations on planchets.
*/
struct PlanchetOperation *po_tail;
/**
* HTTP response to return (set on errors).
*/
struct MHD_Response *response;
/**
* Find operation (while active), later NULL.
*/
struct TMH_EXCHANGES_FindOperation *fo;
/**
* Which reserve are we draining?
*/
struct TALER_ReservePrivateKeyP reserve_priv;
/**
* Which tip is being picked up?
*/
struct GNUNET_HashCode tip_id;
/**
* What is the ID of the pickup operation? (Basically a
* hash over the key inputs).
*/
struct GNUNET_HashCode pickup_id;
/**
* Array of our planchets.
*/
struct TALER_PlanchetDetail *planchets;
/**
* Length of the @e planchets array.
*/
unsigned int planchets_length;
/**
* HTTP status to use (set on errors).
*/
unsigned int http_status;
/**
* Total amount requested in the pick up operation. Computed by
* totaling up the amounts of all the @e planchets.
*/
struct TALER_Amount total_requested;
/**
* True if @e total_requested has been initialized.
*/
bool tr_initialized;
};
/**
* Head of DLL.
*/
static struct PickupContext *pc_head;
/**
* Tail of DLL.
*/
static struct PickupContext *pc_tail;
/**
* Stop all ongoing operations associated with @a pc.
*/
static void
stop_operations (struct PickupContext *pc)
{
struct PlanchetOperation *po;
if (NULL != pc->tt)
{
GNUNET_SCHEDULER_cancel (pc->tt);
pc->tt = NULL;
}
if (NULL != pc->fo)
{
TMH_EXCHANGES_find_exchange_cancel (pc->fo);
pc->fo = NULL;
}
while (NULL != (po = pc->po_head))
{
if (NULL != po->fo)
{
TMH_EXCHANGES_find_exchange_cancel (po->fo);
po->fo = NULL;
}
if (NULL != po->w2h)
{
TALER_EXCHANGE_withdraw2_cancel (po->w2h);
po->w2h = NULL;
}
GNUNET_CONTAINER_DLL_remove (pc->po_head,
pc->po_tail,
po);
GNUNET_free (po);
}
}
/**
* Function called to clean up.
*
* @param cls a `struct PickupContext *` to clean up
*/
static void
pick_context_cleanup (void *cls)
{
struct PickupContext *pc = cls;
stop_operations (pc); /* should not be any... */
for (unsigned int i = 0; iplanchets_length; i++)
GNUNET_free (pc->planchets[i].coin_ev);
GNUNET_array_grow (pc->planchets,
pc->planchets_length,
0);
GNUNET_free (pc);
}
void
TMH_force_tip_pickup_resume ()
{
struct PickupContext *nxt;
for (struct PickupContext *pc = pc_head;
NULL != pc;
pc = nxt)
{
nxt = pc->next;
stop_operations (pc);
GNUNET_CONTAINER_DLL_remove (pc_head,
pc_tail,
pc);
MHD_resume_connection (pc->connection);
}
}
/**
* Callbacks of this type are used to serve the result of submitting a
* withdraw request to a exchange without the (un)blinding factor.
* We persist the result in the database and, if we were the last
* planchet operation, resume HTTP processing.
*
* @param cls closure with a `struct PlanchetOperation *`
* @param hr HTTP response data
* @param blind_sig blind signature over the coin, NULL on error
*/
static void
withdraw_cb (void *cls,
const struct TALER_EXCHANGE_HttpResponse *hr,
const struct TALER_BlindedDenominationSignature *blind_sig)
{
struct PlanchetOperation *po = cls;
struct PickupContext *pc = po->pc;
enum GNUNET_DB_QueryStatus qs;
GNUNET_CONTAINER_DLL_remove (pc->po_head,
pc->po_tail,
po);
if (NULL == blind_sig)
{
GNUNET_free (po);
stop_operations (pc);
pc->http_status = MHD_HTTP_BAD_GATEWAY;
pc->response =
TALER_MHD_MAKE_JSON_PACK (
TALER_JSON_pack_ec (TALER_EC_MERCHANT_TIP_PICKUP_EXCHANGE_ERROR),
TMH_pack_exchange_reply (hr));
GNUNET_CONTAINER_DLL_remove (pc_head,
pc_tail,
pc);
MHD_resume_connection (pc->connection);
TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
return;
}
qs = TMH_db->insert_pickup_blind_signature (TMH_db->cls,
&pc->pickup_id,
po->offset,
blind_sig);
GNUNET_free (po);
if (qs < 0)
{
stop_operations (pc);
pc->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
pc->response = TALER_MHD_make_error (
TALER_EC_GENERIC_DB_STORE_FAILED,
"blind signature");
GNUNET_CONTAINER_DLL_remove (pc_head,
pc_tail,
pc);
MHD_resume_connection (pc->connection);
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 */
}
}
/**
* Function called with the result of a #TMH_EXCHANGES_find_exchange()
* operation as part of a withdraw objective. If the exchange is ready,
* withdraws the planchet from the exchange.
*
* @param cls closure, with our `struct PlanchetOperation *`
* @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
do_withdraw (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 PlanchetOperation *po = cls;
struct PickupContext *pc = po->pc;
po->fo = NULL;
if (NULL == hr)
{
stop_operations (pc);
GNUNET_CONTAINER_DLL_remove (pc->po_head,
pc->po_tail,
po);
GNUNET_free (po);
pc->http_status = MHD_HTTP_GATEWAY_TIMEOUT;
pc->response = TALER_MHD_MAKE_JSON_PACK (
TALER_JSON_pack_ec (
TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT));
MHD_resume_connection (pc->connection);
TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
return;
}
if (NULL == eh)
{
stop_operations (pc);
GNUNET_CONTAINER_DLL_remove (pc->po_head,
pc->po_tail,
po);
GNUNET_free (po);
pc->http_status = MHD_HTTP_BAD_GATEWAY;
pc->response =
TALER_MHD_MAKE_JSON_PACK (
TALER_JSON_pack_ec (
TALER_EC_MERCHANT_GENERIC_EXCHANGE_CONNECT_FAILURE),
TMH_pack_exchange_reply (hr));
MHD_resume_connection (pc->connection);
TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
return;
}
po->w2h = TALER_EXCHANGE_withdraw2 (eh,
&po->pd,
&pc->reserve_priv,
&withdraw_cb,
po);
}
/**
* Withdraw @a planchet from @a exchange_url for @a pc operation at planchet
* @a offset. Sets up the respective operation and adds it @a pc's operation
* list. Once the operation is complete, the resulting blind signature is
* committed to the merchant's database. If all planchet operations are
* completed, the HTTP processing is resumed.
*
* @param[in,out] pc a pending pickup operation that includes @a planchet
* @param exchange_url identifies an exchange to do the pickup from
* @param planchet details about the coin to pick up
* @param offset offset of @a planchet in the list, needed to process the reply
*/
static void
try_withdraw (struct PickupContext *pc,
const char *exchange_url,
const struct TALER_PlanchetDetail *planchet,
unsigned int offset)
{
struct PlanchetOperation *po;
po = GNUNET_new (struct PlanchetOperation);
po->pc = pc;
po->pd = *planchet;
po->offset = offset;
po->fo = TMH_EXCHANGES_find_exchange (exchange_url,
NULL,
GNUNET_NO,
&do_withdraw,
po);
GNUNET_assert (NULL != po->fo);
GNUNET_CONTAINER_DLL_insert (pc->po_head,
pc->po_tail,
po);
}
/**
* Handle timeout for pickup.
*
* @param cls a `struct PickupContext *`
*/
static void
do_timeout (void *cls)
{
struct PickupContext *pc = cls;
pc->tt = NULL;
stop_operations (pc);
pc->http_status = MHD_HTTP_GATEWAY_TIMEOUT;
pc->response = TALER_MHD_make_error (
TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT,
NULL);
GNUNET_CONTAINER_DLL_remove (pc_head,
pc_tail,
pc);
MHD_resume_connection (pc->connection);
TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
}
/**
* Function called with the result of a #TMH_EXCHANGES_find_exchange()
* operation as part of a withdraw objective. Here, we initialize
* the "total_requested" amount by adding up the cost of the planchets
* provided by the client.
*
* @param cls closure, with our `struct PickupContext *`
* @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
compute_total_requested (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 PickupContext *pc = cls;
const struct TALER_EXCHANGE_Keys *keys;
pc->fo = NULL;
stop_operations (pc); /* stops timeout job */
if (NULL == hr)
{
pc->http_status = MHD_HTTP_GATEWAY_TIMEOUT;
pc->response = TALER_MHD_MAKE_JSON_PACK (
TALER_JSON_pack_ec (
TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT));
MHD_resume_connection (pc->connection);
TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
return;
}
if (NULL == eh)
{
pc->http_status = MHD_HTTP_BAD_GATEWAY;
pc->response =
TALER_MHD_MAKE_JSON_PACK (
TALER_JSON_pack_ec (
TALER_EC_MERCHANT_GENERIC_EXCHANGE_CONNECT_FAILURE),
TMH_pack_exchange_reply (hr));
MHD_resume_connection (pc->connection);
TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
return;
}
if (NULL == (keys = TALER_EXCHANGE_get_keys (eh)))
{
pc->http_status = MHD_HTTP_BAD_GATEWAY;
pc->response =
TALER_MHD_MAKE_JSON_PACK (
TALER_JSON_pack_ec (
TALER_EC_MERCHANT_GENERIC_EXCHANGE_KEYS_FAILURE),
TMH_pack_exchange_reply (hr));
MHD_resume_connection (pc->connection);
TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
return;
}
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (TMH_currency,
&pc->total_requested));
for (unsigned int i = 0; iplanchets_length; i++)
{
struct TALER_PlanchetDetail *pd = &pc->planchets[i];
const struct TALER_EXCHANGE_DenomPublicKey *dpk;
dpk = TALER_EXCHANGE_get_denomination_key_by_hash (keys,
&pd->denom_pub_hash);
if (NULL == dpk)
{
pc->http_status = MHD_HTTP_CONFLICT;
pc->response =
TALER_MHD_MAKE_JSON_PACK (
TALER_JSON_pack_ec (
TALER_EC_MERCHANT_TIP_PICKUP_DENOMINATION_UNKNOWN),
TMH_pack_exchange_reply (hr));
MHD_resume_connection (pc->connection);
TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
return;
}
if ( (GNUNET_YES !=
TALER_amount_cmp_currency (&pc->total_requested,
&dpk->value)) ||
(0 >
TALER_amount_add (&pc->total_requested,
&pc->total_requested,
&dpk->value)) )
{
pc->http_status = MHD_HTTP_BAD_REQUEST;
pc->response =
TALER_MHD_make_error (TALER_EC_MERCHANT_TIP_PICKUP_SUMMATION_FAILED,
"Could not add up values to compute pickup total");
MHD_resume_connection (pc->connection);
TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
return;
}
}
pc->tr_initialized = true;
MHD_resume_connection (pc->connection);
TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
}
/**
* The tip lookup operation failed. Generate an error response based on the @a qs.
*
* @param connection connection to generate error for
* @param qs DB status to base error creation on
* @return MHD result code
*/
static MHD_RESULT
reply_lookup_tip_failed (struct MHD_Connection *connection,
enum GNUNET_DB_QueryStatus qs)
{
unsigned int response_code;
enum TALER_ErrorCode ec;
TMH_db->rollback (TMH_db->cls);
switch (qs)
{
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
ec = TALER_EC_MERCHANT_GENERIC_TIP_ID_UNKNOWN;
response_code = MHD_HTTP_NOT_FOUND;
break;
case GNUNET_DB_STATUS_SOFT_ERROR:
ec = TALER_EC_GENERIC_DB_SOFT_FAILURE;
response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
break;
case GNUNET_DB_STATUS_HARD_ERROR:
ec = TALER_EC_GENERIC_DB_COMMIT_FAILED;
response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
break;
default:
GNUNET_break (0);
ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
break;
}
return TALER_MHD_reply_with_error (connection,
response_code,
ec,
NULL);
}
MHD_RESULT
TMH_post_tips_ID_pickup (const struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
struct TMH_HandlerContext *hc)
{
struct PickupContext *pc = hc->ctx;
char *exchange_url;
struct TALER_Amount total_authorized;
struct TALER_Amount total_picked_up;
struct TALER_Amount total_remaining;
struct GNUNET_TIME_Absolute expiration;
enum GNUNET_DB_QueryStatus qs;
unsigned int num_retries;
if (NULL == pc)
{
json_t *planchets;
json_t *planchet;
size_t index;
pc = GNUNET_new (struct PickupContext);
hc->ctx = pc;
hc->cc = &pick_context_cleanup;
GNUNET_assert (NULL != hc->infix);
if (GNUNET_OK !=
GNUNET_CRYPTO_hash_from_string (hc->infix,
&pc->tip_id))
{
/* 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);
}
{
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_json ("planchets",
&planchets),
GNUNET_JSON_spec_end ()
};
{
enum GNUNET_GenericReturnValue res;
res = TALER_MHD_parse_json_data (connection,
hc->request_body,
spec);
if (GNUNET_OK != res)
return (GNUNET_NO == res)
? MHD_YES
: MHD_NO;
}
}
if (! json_is_array (planchets))
{
GNUNET_break_op (0);
json_decref (planchets);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"planchets");
}
GNUNET_array_grow (pc->planchets,
pc->planchets_length,
json_array_size (planchets));
json_array_foreach (planchets, index, planchet) {
struct TALER_PlanchetDetail *pd = &pc->planchets[index];
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
&pd->denom_pub_hash),
GNUNET_JSON_spec_varsize ("coin_ev",
(void **) &pd->coin_ev,
&pd->coin_ev_size),
GNUNET_JSON_spec_end ()
};
{
enum GNUNET_GenericReturnValue res;
res = TALER_MHD_parse_json_data (connection,
planchet,
spec);
if (GNUNET_OK != res)
{
json_decref (planchets);
return (GNUNET_NO == res)
? MHD_YES
: MHD_NO;
}
}
}
json_decref (planchets);
{
struct GNUNET_HashContext *hc;
hc = GNUNET_CRYPTO_hash_context_start ();
GNUNET_CRYPTO_hash_context_read (hc,
&pc->tip_id,
sizeof (pc->tip_id));
for (unsigned int i = 0; iplanchets_length; i++)
{
struct TALER_PlanchetDetail *pd = &pc->planchets[i];
GNUNET_CRYPTO_hash_context_read (hc,
&pd->denom_pub_hash,
sizeof (pd->denom_pub_hash));
GNUNET_CRYPTO_hash_context_read (hc,
pd->coin_ev,
pd->coin_ev_size);
}
GNUNET_CRYPTO_hash_context_finish (hc,
&pc->pickup_id);
}
}
if (NULL != pc->response)
{
MHD_RESULT ret;
ret = MHD_queue_response (connection,
pc->http_status,
pc->response);
pc->response = NULL;
return ret;
}
if (! pc->tr_initialized)
{
qs = TMH_db->lookup_tip (TMH_db->cls,
hc->instance->settings.id,
&pc->tip_id,
&total_authorized,
&total_picked_up,
&expiration,
&exchange_url,
&pc->reserve_priv);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
return reply_lookup_tip_failed (connection,
qs);
MHD_suspend_connection (connection);
pc->connection = connection;
pc->tt = GNUNET_SCHEDULER_add_delayed (EXCHANGE_TIMEOUT,
&do_timeout,
pc);
pc->fo = TMH_EXCHANGES_find_exchange (exchange_url,
NULL,
GNUNET_NO,
&compute_total_requested,
pc);
GNUNET_free (exchange_url);
return MHD_YES;
}
TMH_db->preflight (TMH_db->cls);
num_retries = 0;
RETRY:
num_retries++;
if (num_retries > MAX_RETRIES)
{
GNUNET_break (0);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_SOFT_FAILURE,
NULL);
}
if (GNUNET_OK !=
TMH_db->start (TMH_db->cls,
"pickup tip"))
{
GNUNET_break (0);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_START_FAILED,
NULL);
}
{
struct TALER_BlindedDenominationSignature sigs[
GNUNET_NZL (pc->planchets_length)];
memset (sigs,
0,
sizeof (sigs));
qs = TMH_db->lookup_pickup (TMH_db->cls,
hc->instance->settings.id,
&pc->tip_id,
&pc->pickup_id,
&exchange_url,
&pc->reserve_priv,
pc->planchets_length,
sigs);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Lookup pickup `%s' resulted in %d\n",
GNUNET_h2s (&pc->pickup_id),
qs);
if (qs > GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)
qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
{
bool rollback = false;
for (unsigned int i = 0; i< pc->planchets_length; i++)
{
if (TALER_DENOMINATION_INVALID != sigs[i].cipher)
continue;
if (! rollback)
{
TMH_db->rollback (TMH_db->cls);
MHD_suspend_connection (connection);
GNUNET_CONTAINER_DLL_insert (pc_head,
pc_tail,
pc);
pc->tt = GNUNET_SCHEDULER_add_delayed (EXCHANGE_TIMEOUT,
&do_timeout,
pc);
rollback = true;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Lookup pickup `%s' initiated withdraw #%u\n",
GNUNET_h2s (&pc->pickup_id),
i);
try_withdraw (pc,
exchange_url,
&pc->planchets[i],
i);
}
GNUNET_free (exchange_url);
if (rollback)
return MHD_YES;
/* we got _all_ signatures, can continue! */
}
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs)
{
unsigned int response_code;
enum TALER_ErrorCode ec;
TMH_db->rollback (TMH_db->cls);
switch (qs)
{
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
{
json_t *blind_sigs;
blind_sigs = json_array ();
GNUNET_assert (NULL != blind_sigs);
for (unsigned int i = 0; iplanchets_length; i++)
{
GNUNET_assert (0 ==
json_array_append_new (
blind_sigs,
GNUNET_JSON_PACK (
TALER_JSON_pack_blinded_denom_sig ("blind_sig",
&sigs[i]))));
TALER_blinded_denom_sig_free (&sigs[i]);
}
return TALER_MHD_REPLY_JSON_PACK (
connection,
MHD_HTTP_OK,
GNUNET_JSON_pack_array_steal ("blind_sigs",
blind_sigs));
}
break;
case GNUNET_DB_STATUS_SOFT_ERROR:
goto RETRY;
case GNUNET_DB_STATUS_HARD_ERROR:
ec = TALER_EC_GENERIC_DB_FETCH_FAILED;
response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
break;
default:
GNUNET_break (0);
ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
break;
}
return TALER_MHD_reply_with_error (connection,
response_code,
ec,
NULL);
}
}
qs = TMH_db->lookup_tip (TMH_db->cls,
hc->instance->settings.id,
&pc->tip_id,
&total_authorized,
&total_picked_up,
&expiration,
&exchange_url,
&pc->reserve_priv);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
TMH_db->rollback (TMH_db->cls);
goto RETRY;
}
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
{
TMH_db->rollback (TMH_db->cls);
return reply_lookup_tip_failed (connection,
qs);
}
if (GNUNET_TIME_absolute_is_past (expiration))
{
GNUNET_free (exchange_url);
TMH_db->rollback (TMH_db->cls);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_GONE,
TALER_EC_MERCHANT_TIP_PICKUP_HAS_EXPIRED,
hc->infix);
}
if (0 >
TALER_amount_subtract (&total_remaining,
&total_authorized,
&total_picked_up))
{
GNUNET_free (exchange_url);
GNUNET_break_op (0);
TMH_db->rollback (TMH_db->cls);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
"picked up amount exceeds authorized amount");
}
if (0 >
TALER_amount_cmp (&total_remaining,
&pc->total_requested))
{
/* total_remaining < pc->total_requested */
GNUNET_free (exchange_url);
GNUNET_break_op (0);
TMH_db->rollback (TMH_db->cls);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_MERCHANT_TIP_PICKUP_AMOUNT_EXCEEDS_TIP_REMAINING,
hc->infix);
}
GNUNET_assert (0 <
TALER_amount_add (&total_picked_up,
&total_picked_up,
&pc->total_requested));
qs = TMH_db->insert_pickup (TMH_db->cls,
hc->instance->settings.id,
&pc->tip_id,
&total_picked_up,
&pc->pickup_id,
&pc->total_requested);
if (qs < 0)
{
TMH_db->rollback (TMH_db->cls);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
goto RETRY;
GNUNET_free (exchange_url);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_STORE_FAILED,
"pickup");
}
qs = TMH_db->commit (TMH_db->cls);
if (qs < 0)
{
TMH_db->rollback (TMH_db->cls);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
goto RETRY;
GNUNET_free (exchange_url);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_COMMIT_FAILED,
NULL);
}
MHD_suspend_connection (connection);
GNUNET_CONTAINER_DLL_insert (pc_head,
pc_tail,
pc);
pc->tt = GNUNET_SCHEDULER_add_delayed (EXCHANGE_TIMEOUT,
&do_timeout,
pc);
for (unsigned int i = 0; iplanchets_length; i++)
{
try_withdraw (pc,
exchange_url,
&pc->planchets[i],
i);
}
GNUNET_free (exchange_url);
return MHD_YES;
}