/* This file is part of TALER (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 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-rewards-ID-pickup.c * @brief implementation of a POST /rewards/ID/pickup handler * @author Christian Grothoff */ #include "platform.h" #include #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-rewards-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 reward. */ 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_KeysOperation *fo; /** * Withdraw handle (NULL while @e fo is active). */ struct TALER_EXCHANGE_BatchWithdraw2Handle *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; /** * Request context. */ struct TMH_HandlerContext *hc; /** * Base URL of the exchange we withdraw from. */ char *exchange_url; /** * 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_KeysOperation *fo; /** * Which reserve are we draining? */ struct TALER_ReservePrivateKeyP reserve_priv; /** * Which reward is being picked up? */ struct TALER_RewardIdentifierP reward_id; /** * What is the ID of the pickup operation? (Basically a * hash over the key inputs). */ struct TALER_PickupIdentifierP 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; /** * Should we generate a DB notification at the end for the pickup? Used to * wake up long pollers upon reward 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; }; /** * 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_keys4exchange_cancel (pc->fo); pc->fo = NULL; } while (NULL != (po = pc->po_head)) { if (NULL != po->fo) { TMH_EXCHANGES_keys4exchange_cancel (po->fo); po->fo = NULL; } if (NULL != po->w2h) { TALER_EXCHANGE_batch_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; if (pc->do_notify) { struct TMH_RewardPickupEventP reward_eh = { .header.size = htons (sizeof (reward_eh)), .header.type = htons (TALER_DBEVENT_MERCHANT_REWARD_PICKUP), .reward_id = pc->reward_id }; GNUNET_CRYPTO_hash (pc->hc->instance->settings.id, strlen (pc->hc->instance->settings.id), &reward_eh.h_instance); TMH_db->event_notify (TMH_db->cls, &reward_eh.header, NULL, 0); } stop_operations (pc); /* should not be any... */ for (unsigned int i = 0; iplanchets_length; i++) TALER_planchet_detail_free (&pc->planchets[i]); GNUNET_array_grow (pc->planchets, pc->planchets_length, 0); GNUNET_free (pc->exchange_url); GNUNET_free (pc); } void TMH_force_reward_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 w2r response data */ static void withdraw_cb (void *cls, const struct TALER_EXCHANGE_BatchWithdraw2Response *w2r) { struct PlanchetOperation *po = cls; const struct TALER_EXCHANGE_HttpResponse *hr = &w2r->hr; struct PickupContext *pc = po->pc; enum GNUNET_DB_QueryStatus qs; GNUNET_CONTAINER_DLL_remove (pc->po_head, pc->po_tail, po); TMH_db->preflight (TMH_db->cls); if (MHD_HTTP_OK != hr->http_status) { 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_REWARD_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; } GNUNET_assert (1 == w2r->details.ok.blind_sigs_length); qs = TMH_db->insert_pickup_blind_signature (TMH_db->cls, &pc->pickup_id, po->offset, &w2r->details.ok.blind_sigs[0]); 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; } 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 */ } /** * Function called with the result of a #TMH_EXCHANGES_keys4exchange() * 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 keys keys for the exchange * @param exchange representation of the exchange */ static void do_withdraw (void *cls, struct TALER_EXCHANGE_Keys *keys, struct TMH_Exchange *exchange) { struct PlanchetOperation *po = cls; struct PickupContext *pc = po->pc; (void) exchange; po->fo = NULL; TMH_db->preflight (TMH_db->cls); if (NULL == keys) { 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; } /* FIXME: actually use *batch* withdraw instead of doing this coin-by-coin non-sense */ po->w2h = TALER_EXCHANGE_batch_withdraw2 (TMH_curl_ctx, pc->exchange_url, keys, &pc->reserve_priv, 1, &po->pd, &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 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 struct TALER_PlanchetDetail *planchet, unsigned int offset) { struct PlanchetOperation *po; TMH_db->preflight (TMH_db->cls); po = GNUNET_new (struct PlanchetOperation); po->pc = pc; po->pd = *planchet; po->offset = offset; po->fo = TMH_EXCHANGES_keys4exchange (pc->exchange_url, false, &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_keys4exchange() * 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 keys the keys of the exchange * @param exchange representation of the exchange */ static void compute_total_requested (void *cls, struct TALER_EXCHANGE_Keys *keys, struct TMH_Exchange *exchange) { struct PickupContext *pc = cls; bool have_request = false; (void) exchange; pc->fo = NULL; stop_operations (pc); /* stops timeout job */ if (NULL == keys) { pc->http_status = MHD_HTTP_GATEWAY_TIMEOUT; pc->response = TALER_MHD_MAKE_JSON_PACK ( TALER_JSON_pack_ec ( TALER_EC_MERCHANT_GENERIC_EXCHANGE_KEYS_FAILURE)); MHD_resume_connection (pc->connection); TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */ return; } 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_REWARD_PICKUP_DENOMINATION_UNKNOWN)); MHD_resume_connection (pc->connection); TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */ return; } if (have_request) { if (GNUNET_YES != TALER_amount_cmp_currency (&pc->total_requested, &dpk->value)) { pc->http_status = MHD_HTTP_BAD_REQUEST; pc->response = TALER_MHD_make_error (TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, "Must not mix currencies when picking up rewards"); MHD_resume_connection (pc->connection); TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */ return; } if (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_REWARD_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; } } else { pc->total_requested = dpk->value; have_request = true; } } if (! have_request) { pc->http_status = MHD_HTTP_BAD_REQUEST; pc->response = TALER_MHD_make_error (TALER_EC_MERCHANT_REWARD_PICKUP_SUMMATION_FAILED, "Empty request array not allowed"); 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 reward 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_reward_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_REWARD_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_rewards_ID_pickup (const struct TMH_RequestHandler *rh, struct MHD_Connection *connection, struct TMH_HandlerContext *hc) { struct PickupContext *pc = hc->ctx; char *next_url; struct TALER_Amount total_authorized; struct TALER_Amount total_picked_up; struct TALER_Amount total_remaining; struct GNUNET_TIME_Timestamp expiration; enum GNUNET_DB_QueryStatus qs; unsigned int num_retries; if (NULL == pc) { const json_t *planchets; json_t *planchet; size_t index; 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, &pc->reward_id.hash)) { /* reward_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_array_const ("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; } } 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), TALER_JSON_spec_blinded_planchet ("coin_ev", &pd->blinded_planchet), GNUNET_JSON_spec_end () }; { enum GNUNET_GenericReturnValue res; res = TALER_MHD_parse_json_data (connection, planchet, spec); if (GNUNET_OK != res) { return (GNUNET_NO == res) ? MHD_YES : MHD_NO; } } } { struct GNUNET_HashContext *hc; hc = GNUNET_CRYPTO_hash_context_start (); GNUNET_CRYPTO_hash_context_read (hc, &pc->reward_id, sizeof (pc->reward_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)); TALER_blinded_planchet_hash_ (&pd->blinded_planchet, hc); } GNUNET_CRYPTO_hash_context_finish (hc, &pc->pickup_id.hash); } } 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) { char *next_url; qs = TMH_db->lookup_reward (TMH_db->cls, hc->instance->settings.id, &pc->reward_id, &total_authorized, &total_picked_up, &expiration, &pc->exchange_url, &next_url, &pc->reserve_priv); if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) return reply_lookup_reward_failed (connection, qs); GNUNET_free (next_url); MHD_suspend_connection (connection); pc->connection = connection; pc->tt = GNUNET_SCHEDULER_add_delayed (EXCHANGE_TIMEOUT, &do_timeout, pc); pc->fo = TMH_EXCHANGES_keys4exchange (pc->exchange_url, false, &compute_total_requested, pc); 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 reward")) { 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)); GNUNET_free (pc->exchange_url); qs = TMH_db->lookup_pickup (TMH_db->cls, hc->instance->settings.id, &pc->reward_id, &pc->pickup_id, &pc->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.hash), 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 (NULL != sigs[i].blinded_sig) 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.hash), i); try_withdraw (pc, &pc->planchets[i], i); } 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); } } GNUNET_free (pc->exchange_url); qs = TMH_db->lookup_reward (TMH_db->cls, hc->instance->settings.id, &pc->reward_id, &total_authorized, &total_picked_up, &expiration, &pc->exchange_url, &next_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_reward_failed (connection, qs); } GNUNET_free (next_url); if (GNUNET_TIME_absolute_is_past (expiration.abs_time)) { TMH_db->rollback (TMH_db->cls); return TALER_MHD_reply_with_error (connection, MHD_HTTP_GONE, TALER_EC_MERCHANT_REWARD_PICKUP_HAS_EXPIRED, hc->infix); } if (GNUNET_OK != TALER_amount_cmp_currency (&total_authorized, &total_picked_up)) { /* This could theoretically happen if the exchange changed its currency between us approving the reward and the client then picks it up with the new exchange currency. And of course the backend would have had to get the new /keys of the exchange already as well. Very theoretical case. */ GNUNET_break_op (0); TMH_db->rollback (TMH_db->cls); return TALER_MHD_reply_with_error (connection, MHD_HTTP_CONFLICT, TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, "picked up amount does not use same currency as authorized amount"); } if (0 > TALER_amount_subtract (&total_remaining, &total_authorized, &total_picked_up)) { 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_break_op (0); TMH_db->rollback (TMH_db->cls); return TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, TALER_EC_MERCHANT_REWARD_PICKUP_AMOUNT_EXCEEDS_REWARD_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->reward_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; 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; 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, &pc->planchets[i], i); } return MHD_YES; }