LCOV - code coverage report
Current view: top level - backend - taler-merchant-httpd_post-orders-ID-abort.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 191 298 64.1 %
Date: 2025-06-23 16:22:09 Functions: 12 14 85.7 %

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   (C) 2014-2021 Taler Systems SA
       4             : 
       5             :   TALER is free software; you can redistribute it and/or modify
       6             :   it under the terms of the GNU Affero General Public License as
       7             :   published by the Free Software Foundation; either version 3,
       8             :   or (at your option) any later version.
       9             : 
      10             :   TALER is distributed in the hope that it will be useful, but
      11             :   WITHOUT ANY WARRANTY; without even the implied warranty of
      12             :   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13             :   GNU General Public License for more details.
      14             : 
      15             :   You should have received a copy of the GNU General Public
      16             :   License along with TALER; see the file COPYING.  If not,
      17             :   see <http://www.gnu.org/licenses/>
      18             : */
      19             : /**
      20             :  * @file taler-merchant-httpd_post-orders-ID-abort.c
      21             :  * @brief handling of POST /orders/$ID/abort requests
      22             :  * @author Marcello Stanisci
      23             :  * @author Christian Grothoff
      24             :  * @author Florian Dold
      25             :  */
      26             : #include "platform.h"
      27             : #include <taler/taler_json_lib.h>
      28             : #include <taler/taler_exchange_service.h>
      29             : #include "taler-merchant-httpd_exchanges.h"
      30             : #include "taler-merchant-httpd_helper.h"
      31             : #include "taler-merchant-httpd_post-orders-ID-abort.h"
      32             : 
      33             : 
      34             : /**
      35             :  * How long to wait before giving up processing with the exchange?
      36             :  */
      37             : #define ABORT_GENERIC_TIMEOUT (GNUNET_TIME_relative_multiply ( \
      38             :                                  GNUNET_TIME_UNIT_SECONDS, \
      39             :                                  30))
      40             : 
      41             : /**
      42             :  * How often do we retry the (complex!) database transaction?
      43             :  */
      44             : #define MAX_RETRIES 5
      45             : 
      46             : /**
      47             :  * Information we keep for an individual call to the /abort handler.
      48             :  */
      49             : struct AbortContext;
      50             : 
      51             : /**
      52             :  * Information kept during a /abort request for each coin.
      53             :  */
      54             : struct RefundDetails
      55             : {
      56             : 
      57             :   /**
      58             :    * Public key of the coin.
      59             :    */
      60             :   struct TALER_CoinSpendPublicKeyP coin_pub;
      61             : 
      62             :   /**
      63             :    * Signature from the exchange confirming the refund.
      64             :    * Set if we were successful (status 200).
      65             :    */
      66             :   struct TALER_ExchangeSignatureP exchange_sig;
      67             : 
      68             :   /**
      69             :    * Public key used for @e exchange_sig.
      70             :    * Set if we were successful (status 200).
      71             :    */
      72             :   struct TALER_ExchangePublicKeyP exchange_pub;
      73             : 
      74             :   /**
      75             :    * Reference to the main AbortContext
      76             :    */
      77             :   struct AbortContext *ac;
      78             : 
      79             :   /**
      80             :    * Handle to the refund operation we are performing for
      81             :    * this coin, NULL after the operation is done.
      82             :    */
      83             :   struct TALER_EXCHANGE_RefundHandle *rh;
      84             : 
      85             :   /**
      86             :    * URL of the exchange that issued this coin.
      87             :    */
      88             :   char *exchange_url;
      89             : 
      90             :   /**
      91             :    * Body of the response from the exchange.  Note that the body returned MUST
      92             :    * be freed (if non-NULL).
      93             :    */
      94             :   json_t *exchange_reply;
      95             : 
      96             :   /**
      97             :    * Amount this coin contributes to the total purchase price.
      98             :    * This amount includes the deposit fee.
      99             :    */
     100             :   struct TALER_Amount amount_with_fee;
     101             : 
     102             :   /**
     103             :    * Offset of this coin into the `rd` array of all coins in the
     104             :    * @e ac.
     105             :    */
     106             :   unsigned int index;
     107             : 
     108             :   /**
     109             :    * HTTP status returned by the exchange (if any).
     110             :    */
     111             :   unsigned int http_status;
     112             : 
     113             :   /**
     114             :    * Did we try to process this refund yet?
     115             :    */
     116             :   bool processed;
     117             : 
     118             :   /**
     119             :    * Did we find the deposit in our own database?
     120             :    */
     121             :   bool found_deposit;
     122             : };
     123             : 
     124             : 
     125             : /**
     126             :  * Information we keep for an individual call to the /abort handler.
     127             :  */
     128             : struct AbortContext
     129             : {
     130             : 
     131             :   /**
     132             :    * Hashed contract terms (according to client).
     133             :    */
     134             :   struct TALER_PrivateContractHashP h_contract_terms;
     135             : 
     136             :   /**
     137             :    * Context for our operation.
     138             :    */
     139             :   struct TMH_HandlerContext *hc;
     140             : 
     141             :   /**
     142             :    * Stored in a DLL.
     143             :    */
     144             :   struct AbortContext *next;
     145             : 
     146             :   /**
     147             :    * Stored in a DLL.
     148             :    */
     149             :   struct AbortContext *prev;
     150             : 
     151             :   /**
     152             :    * Array with @e coins_cnt coins we are despositing.
     153             :    */
     154             :   struct RefundDetails *rd;
     155             : 
     156             :   /**
     157             :    * MHD connection to return to
     158             :    */
     159             :   struct MHD_Connection *connection;
     160             : 
     161             :   /**
     162             :    * Task called when the (suspended) processing for
     163             :    * the /abort request times out.
     164             :    * Happens when we don't get a response from the exchange.
     165             :    */
     166             :   struct GNUNET_SCHEDULER_Task *timeout_task;
     167             : 
     168             :   /**
     169             :    * Response to return, NULL if we don't have one yet.
     170             :    */
     171             :   struct MHD_Response *response;
     172             : 
     173             :   /**
     174             :    * Handle to the exchange that we are doing the abortment with.
     175             :    * (initially NULL while @e fo is trying to find a exchange).
     176             :    */
     177             :   struct TALER_EXCHANGE_Handle *mh;
     178             : 
     179             :   /**
     180             :    * Handle for operation to lookup /keys (and auditors) from
     181             :    * the exchange used for this transaction; NULL if no operation is
     182             :    * pending.
     183             :    */
     184             :   struct TMH_EXCHANGES_KeysOperation *fo;
     185             : 
     186             :   /**
     187             :    * URL of the exchange used for the last @e fo.
     188             :    */
     189             :   const char *current_exchange;
     190             : 
     191             :   /**
     192             :    * Number of coins this abort is for.  Length of the @e rd array.
     193             :    */
     194             :   size_t coins_cnt;
     195             : 
     196             :   /**
     197             :    * How often have we retried the 'main' transaction?
     198             :    */
     199             :   unsigned int retry_counter;
     200             : 
     201             :   /**
     202             :    * Number of transactions still pending.  Initially set to
     203             :    * @e coins_cnt, decremented on each transaction that
     204             :    * successfully finished.
     205             :    */
     206             :   size_t pending;
     207             : 
     208             :   /**
     209             :    * Number of transactions still pending for the currently selected
     210             :    * exchange.  Initially set to the number of coins started at the
     211             :    * exchange, decremented on each transaction that successfully
     212             :    * finished.  Once it hits zero, we pick the next exchange.
     213             :    */
     214             :   size_t pending_at_ce;
     215             : 
     216             :   /**
     217             :    * HTTP status code to use for the reply, i.e 200 for "OK".
     218             :    * Special value UINT_MAX is used to indicate hard errors
     219             :    * (no reply, return #MHD_NO).
     220             :    */
     221             :   unsigned int response_code;
     222             : 
     223             :   /**
     224             :    * #GNUNET_NO if the @e connection was not suspended,
     225             :    * #GNUNET_YES if the @e connection was suspended,
     226             :    * #GNUNET_SYSERR if @e connection was resumed to as
     227             :    * part of #MH_force_ac_resume during shutdown.
     228             :    */
     229             :   int suspended;
     230             : 
     231             : };
     232             : 
     233             : 
     234             : /**
     235             :  * Head of active abort context DLL.
     236             :  */
     237             : static struct AbortContext *ac_head;
     238             : 
     239             : /**
     240             :  * Tail of active abort context DLL.
     241             :  */
     242             : static struct AbortContext *ac_tail;
     243             : 
     244             : 
     245             : /**
     246             :  * Abort all pending /deposit operations.
     247             :  *
     248             :  * @param ac abort context to abort
     249             :  */
     250             : static void
     251           4 : abort_refunds (struct AbortContext *ac)
     252             : {
     253           4 :   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     254             :               "Aborting pending /deposit operations\n");
     255           8 :   for (size_t i = 0; i<ac->coins_cnt; i++)
     256             :   {
     257           4 :     struct RefundDetails *rdi = &ac->rd[i];
     258             : 
     259           4 :     if (NULL != rdi->rh)
     260             :     {
     261           0 :       TALER_EXCHANGE_refund_cancel (rdi->rh);
     262           0 :       rdi->rh = NULL;
     263             :     }
     264             :   }
     265           4 : }
     266             : 
     267             : 
     268             : void
     269          14 : TMH_force_ac_resume ()
     270             : {
     271          14 :   for (struct AbortContext *ac = ac_head;
     272          14 :        NULL != ac;
     273           0 :        ac = ac->next)
     274             :   {
     275           0 :     abort_refunds (ac);
     276           0 :     if (NULL != ac->timeout_task)
     277             :     {
     278           0 :       GNUNET_SCHEDULER_cancel (ac->timeout_task);
     279           0 :       ac->timeout_task = NULL;
     280             :     }
     281           0 :     if (GNUNET_YES == ac->suspended)
     282             :     {
     283           0 :       ac->suspended = GNUNET_SYSERR;
     284           0 :       MHD_resume_connection (ac->connection);
     285             :     }
     286             :   }
     287          14 : }
     288             : 
     289             : 
     290             : /**
     291             :  * Resume the given abort context and send the given response.
     292             :  * Stores the response in the @a ac and signals MHD to resume
     293             :  * the connection.  Also ensures MHD runs immediately.
     294             :  *
     295             :  * @param ac abortment context
     296             :  * @param response_code response code to use
     297             :  * @param response response data to send back
     298             :  */
     299             : static void
     300           2 : resume_abort_with_response (struct AbortContext *ac,
     301             :                             unsigned int response_code,
     302             :                             struct MHD_Response *response)
     303             : {
     304           2 :   abort_refunds (ac);
     305           2 :   ac->response_code = response_code;
     306           2 :   ac->response = response;
     307           2 :   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     308             :               "Resuming /abort handling as exchange interaction is done (%u)\n",
     309             :               response_code);
     310           2 :   if (NULL != ac->timeout_task)
     311             :   {
     312           2 :     GNUNET_SCHEDULER_cancel (ac->timeout_task);
     313           2 :     ac->timeout_task = NULL;
     314             :   }
     315           2 :   GNUNET_assert (GNUNET_YES == ac->suspended);
     316           2 :   ac->suspended = GNUNET_NO;
     317           2 :   MHD_resume_connection (ac->connection);
     318           2 :   TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
     319           2 : }
     320             : 
     321             : 
     322             : /**
     323             :  * Resume abortment processing with an error.
     324             :  *
     325             :  * @param ac operation to resume
     326             :  * @param http_status http status code to return
     327             :  * @param ec taler error code to return
     328             :  * @param msg human readable error message
     329             :  */
     330             : static void
     331           0 : resume_abort_with_error (struct AbortContext *ac,
     332             :                          unsigned int http_status,
     333             :                          enum TALER_ErrorCode ec,
     334             :                          const char *msg)
     335             : {
     336           0 :   resume_abort_with_response (ac,
     337             :                               http_status,
     338             :                               TALER_MHD_make_error (ec,
     339             :                                                     msg));
     340           0 : }
     341             : 
     342             : 
     343             : /**
     344             :  * Generate a response that indicates abortment success.
     345             :  *
     346             :  * @param ac abortment context
     347             :  */
     348             : static void
     349           2 : generate_success_response (struct AbortContext *ac)
     350             : {
     351             :   json_t *refunds;
     352           2 :   unsigned int hc = MHD_HTTP_OK;
     353             : 
     354           2 :   refunds = json_array ();
     355           2 :   if (NULL == refunds)
     356             :   {
     357           0 :     GNUNET_break (0);
     358           0 :     resume_abort_with_error (ac,
     359             :                              MHD_HTTP_INTERNAL_SERVER_ERROR,
     360             :                              TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
     361             :                              "could not create JSON array");
     362           0 :     return;
     363             :   }
     364           4 :   for (size_t i = 0; i<ac->coins_cnt; i++)
     365             :   {
     366           2 :     struct RefundDetails *rdi = &ac->rd[i];
     367             :     json_t *detail;
     368             : 
     369           2 :     if (rdi->found_deposit)
     370             :     {
     371           2 :       if ( ( (MHD_HTTP_BAD_REQUEST <= rdi->http_status) &&
     372           0 :              (MHD_HTTP_NOT_FOUND != rdi->http_status) &&
     373           0 :              (MHD_HTTP_GONE != rdi->http_status) ) ||
     374           2 :            (0 == rdi->http_status) ||
     375           2 :            (NULL == rdi->exchange_reply) )
     376             :       {
     377           0 :         hc = MHD_HTTP_BAD_GATEWAY;
     378             :       }
     379             :     }
     380           2 :     if (! rdi->found_deposit)
     381             :     {
     382           0 :       detail = GNUNET_JSON_PACK (
     383             :         GNUNET_JSON_pack_string ("type",
     384             :                                  "undeposited"));
     385             :     }
     386             :     else
     387             :     {
     388           2 :       if (MHD_HTTP_OK != rdi->http_status)
     389             :       {
     390           0 :         detail = GNUNET_JSON_PACK (
     391             :           GNUNET_JSON_pack_string ("type",
     392             :                                    "failure"),
     393             :           GNUNET_JSON_pack_uint64 ("exchange_status",
     394             :                                    rdi->http_status),
     395             :           GNUNET_JSON_pack_uint64 ("exchange_code",
     396             :                                    (NULL != rdi->exchange_reply)
     397             :                                    ? TALER_JSON_get_error_code (
     398             :                                      rdi->exchange_reply)
     399             :                                    : TALER_EC_GENERIC_INVALID_RESPONSE),
     400             :           GNUNET_JSON_pack_allow_null (
     401             :             GNUNET_JSON_pack_object_incref ("exchange_reply",
     402             :                                             rdi->exchange_reply)));
     403             :       }
     404             :       else
     405             :       {
     406           2 :         detail = GNUNET_JSON_PACK (
     407             :           GNUNET_JSON_pack_string ("type",
     408             :                                    "success"),
     409             :           GNUNET_JSON_pack_uint64 ("exchange_status",
     410             :                                    rdi->http_status),
     411             :           GNUNET_JSON_pack_data_auto ("exchange_sig",
     412             :                                       &rdi->exchange_sig),
     413             :           GNUNET_JSON_pack_data_auto ("exchange_pub",
     414             :                                       &rdi->exchange_pub));
     415             :       }
     416             :     }
     417           2 :     GNUNET_assert (0 ==
     418             :                    json_array_append_new (refunds,
     419             :                                           detail));
     420             :   }
     421             : 
     422             :   /* Resume and send back the response.  */
     423           2 :   resume_abort_with_response (
     424             :     ac,
     425             :     hc,
     426           2 :     TALER_MHD_MAKE_JSON_PACK (
     427             :       GNUNET_JSON_pack_array_steal ("refunds",
     428             :                                     refunds)));
     429             : }
     430             : 
     431             : 
     432             : /**
     433             :  * Custom cleanup routine for a `struct AbortContext`.
     434             :  *
     435             :  * @param cls the `struct AbortContext` to clean up.
     436             :  */
     437             : static void
     438           2 : abort_context_cleanup (void *cls)
     439             : {
     440           2 :   struct AbortContext *ac = cls;
     441             : 
     442           2 :   if (NULL != ac->timeout_task)
     443             :   {
     444           0 :     GNUNET_SCHEDULER_cancel (ac->timeout_task);
     445           0 :     ac->timeout_task = NULL;
     446             :   }
     447           2 :   abort_refunds (ac);
     448           4 :   for (size_t i = 0; i<ac->coins_cnt; i++)
     449             :   {
     450           2 :     struct RefundDetails *rdi = &ac->rd[i];
     451             : 
     452           2 :     if (NULL != rdi->exchange_reply)
     453             :     {
     454           2 :       json_decref (rdi->exchange_reply);
     455           2 :       rdi->exchange_reply = NULL;
     456             :     }
     457           2 :     GNUNET_free (rdi->exchange_url);
     458             :   }
     459           2 :   GNUNET_free (ac->rd);
     460           2 :   if (NULL != ac->fo)
     461             :   {
     462           0 :     TMH_EXCHANGES_keys4exchange_cancel (ac->fo);
     463           0 :     ac->fo = NULL;
     464             :   }
     465           2 :   if (NULL != ac->response)
     466             :   {
     467           0 :     MHD_destroy_response (ac->response);
     468           0 :     ac->response = NULL;
     469             :   }
     470           2 :   GNUNET_CONTAINER_DLL_remove (ac_head,
     471             :                                ac_tail,
     472             :                                ac);
     473           2 :   GNUNET_free (ac);
     474           2 : }
     475             : 
     476             : 
     477             : /**
     478             :  * Find the exchange we need to talk to for the next
     479             :  * pending deposit permission.
     480             :  *
     481             :  * @param ac abortment context we are processing
     482             :  */
     483             : static void
     484             : find_next_exchange (struct AbortContext *ac);
     485             : 
     486             : 
     487             : /**
     488             :  * Function called with the result from the exchange (to be
     489             :  * passed back to the wallet).
     490             :  *
     491             :  * @param cls closure
     492             :  * @param rr response data
     493             :  */
     494             : static void
     495           2 : refund_cb (void *cls,
     496             :            const struct TALER_EXCHANGE_RefundResponse *rr)
     497             : {
     498           2 :   struct RefundDetails *rd = cls;
     499           2 :   const struct TALER_EXCHANGE_HttpResponse *hr = &rr->hr;
     500           2 :   struct AbortContext *ac = rd->ac;
     501             : 
     502           2 :   rd->rh = NULL;
     503           2 :   rd->http_status = hr->http_status;
     504           2 :   rd->exchange_reply = json_incref ((json_t*) hr->reply);
     505           2 :   if (MHD_HTTP_OK == hr->http_status)
     506             :   {
     507           2 :     rd->exchange_pub = rr->details.ok.exchange_pub;
     508           2 :     rd->exchange_sig = rr->details.ok.exchange_sig;
     509             :   }
     510           2 :   ac->pending_at_ce--;
     511           2 :   if (0 == ac->pending_at_ce)
     512           2 :     find_next_exchange (ac);
     513           2 : }
     514             : 
     515             : 
     516             : /**
     517             :  * Function called with the result of our exchange lookup.
     518             :  *
     519             :  * @param cls the `struct AbortContext`
     520             :  * @param keys keys of the exchange
     521             :  * @param exchange representation of the exchange
     522             :  */
     523             : static void
     524           2 : process_abort_with_exchange (void *cls,
     525             :                              struct TALER_EXCHANGE_Keys *keys,
     526             :                              struct TMH_Exchange *exchange)
     527             : {
     528           2 :   struct AbortContext *ac = cls;
     529             : 
     530             :   (void) exchange;
     531           2 :   ac->fo = NULL;
     532           2 :   GNUNET_assert (GNUNET_YES == ac->suspended);
     533           2 :   if (NULL == keys)
     534             :   {
     535           0 :     resume_abort_with_response (
     536             :       ac,
     537             :       MHD_HTTP_GATEWAY_TIMEOUT,
     538             :       TALER_MHD_make_error (
     539             :         TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT,
     540             :         NULL));
     541           0 :     return;
     542             :   }
     543             :   /* Initiate refund operation for all coins of
     544             :      the current exchange (!) */
     545           2 :   GNUNET_assert (0 == ac->pending_at_ce);
     546           4 :   for (size_t i = 0; i<ac->coins_cnt; i++)
     547             :   {
     548           2 :     struct RefundDetails *rdi = &ac->rd[i];
     549             : 
     550           2 :     if (rdi->processed)
     551           0 :       continue;
     552           2 :     GNUNET_assert (NULL == rdi->rh);
     553           2 :     if (0 != strcmp (rdi->exchange_url,
     554             :                      ac->current_exchange))
     555           0 :       continue;
     556           2 :     rdi->processed = true;
     557           2 :     ac->pending--;
     558           2 :     if (! rdi->found_deposit)
     559             :     {
     560             :       /* Coin wasn't even deposited yet, we do not need to refund it. */
     561           0 :       continue;
     562             :     }
     563           4 :     rdi->rh = TALER_EXCHANGE_refund (
     564             :       TMH_curl_ctx,
     565             :       ac->current_exchange,
     566             :       keys,
     567           2 :       &rdi->amount_with_fee,
     568           2 :       &ac->h_contract_terms,
     569           2 :       &rdi->coin_pub,
     570             :       0,                                /* rtransaction_id */
     571           2 :       &ac->hc->instance->merchant_priv,
     572             :       &refund_cb,
     573             :       rdi);
     574           2 :     if (NULL == rdi->rh)
     575             :     {
     576           0 :       GNUNET_break_op (0);
     577           0 :       resume_abort_with_error (ac,
     578             :                                MHD_HTTP_INTERNAL_SERVER_ERROR,
     579             :                                TALER_EC_MERCHANT_POST_ORDERS_ID_ABORT_EXCHANGE_REFUND_FAILED,
     580             :                                "Failed to start refund with exchange");
     581           0 :       return;
     582             :     }
     583           2 :     ac->pending_at_ce++;
     584             :   }
     585             :   /* Still continue if no coins for this exchange were deposited. */
     586           2 :   if (0 == ac->pending_at_ce)
     587           0 :     find_next_exchange (ac);
     588             : }
     589             : 
     590             : 
     591             : /**
     592             :  * Begin of the DB transaction.  If required (from
     593             :  * soft/serialization errors), the transaction can be
     594             :  * restarted here.
     595             :  *
     596             :  * @param ac abortment context to transact
     597             :  */
     598             : static void
     599             : begin_transaction (struct AbortContext *ac);
     600             : 
     601             : 
     602             : /**
     603             :  * Find the exchange we need to talk to for the next
     604             :  * pending deposit permission.
     605             :  *
     606             :  * @param ac abortment context we are processing
     607             :  */
     608             : static void
     609           4 : find_next_exchange (struct AbortContext *ac)
     610             : {
     611           6 :   for (size_t i = 0; i<ac->coins_cnt; i++)
     612             :   {
     613           4 :     struct RefundDetails *rdi = &ac->rd[i];
     614             : 
     615           4 :     if (! rdi->processed)
     616             :     {
     617           2 :       ac->current_exchange = rdi->exchange_url;
     618           2 :       ac->fo = TMH_EXCHANGES_keys4exchange (ac->current_exchange,
     619             :                                             false,
     620             :                                             &process_abort_with_exchange,
     621             :                                             ac);
     622           2 :       if (NULL == ac->fo)
     623             :       {
     624             :         /* strange, should have happened on pay! */
     625           0 :         GNUNET_break (0);
     626           0 :         resume_abort_with_error (ac,
     627             :                                  MHD_HTTP_INTERNAL_SERVER_ERROR,
     628             :                                  TALER_EC_MERCHANT_GENERIC_EXCHANGE_UNTRUSTED,
     629             :                                  ac->current_exchange);
     630           0 :         return;
     631             :       }
     632           2 :       return;
     633             :     }
     634             :   }
     635           2 :   ac->current_exchange = NULL;
     636           2 :   GNUNET_assert (0 == ac->pending);
     637             :   /* We are done with all the HTTP requests, go back and try
     638             :      the 'big' database transaction! (It should work now!) */
     639           2 :   begin_transaction (ac);
     640             : }
     641             : 
     642             : 
     643             : /**
     644             :  * Function called with information about a coin that was deposited.
     645             :  *
     646             :  * @param cls closure
     647             :  * @param exchange_url exchange where @a coin_pub was deposited
     648             :  * @param coin_pub public key of the coin
     649             :  * @param amount_with_fee amount the exchange will deposit for this coin
     650             :  * @param deposit_fee fee the exchange will charge for this coin
     651             :  * @param refund_fee fee the exchange will charge for refunding this coin
     652             :  */
     653             : static void
     654           4 : refund_coins (void *cls,
     655             :               const char *exchange_url,
     656             :               const struct TALER_CoinSpendPublicKeyP *coin_pub,
     657             :               const struct TALER_Amount *amount_with_fee,
     658             :               const struct TALER_Amount *deposit_fee,
     659             :               const struct TALER_Amount *refund_fee)
     660             : {
     661           4 :   struct AbortContext *ac = cls;
     662             :   struct GNUNET_TIME_Timestamp now;
     663             : 
     664             :   (void) deposit_fee;
     665             :   (void) refund_fee;
     666           4 :   now = GNUNET_TIME_timestamp_get ();
     667           8 :   for (size_t i = 0; i<ac->coins_cnt; i++)
     668             :   {
     669           4 :     struct RefundDetails *rdi = &ac->rd[i];
     670             :     enum GNUNET_DB_QueryStatus qs;
     671             : 
     672           4 :     if ( (0 !=
     673           4 :           GNUNET_memcmp (coin_pub,
     674           4 :                          &rdi->coin_pub)) ||
     675             :          (0 !=
     676           4 :           strcmp (exchange_url,
     677           4 :                   rdi->exchange_url)) )
     678           0 :       continue; /* not in request */
     679           4 :     rdi->found_deposit = true;
     680           4 :     rdi->amount_with_fee = *amount_with_fee;
     681             :     /* Store refund in DB */
     682           4 :     qs = TMH_db->refund_coin (TMH_db->cls,
     683           4 :                               ac->hc->instance->settings.id,
     684           4 :                               &ac->h_contract_terms,
     685             :                               now,
     686             :                               coin_pub,
     687             :                               /* justification */
     688             :                               "incomplete abortment aborted");
     689           4 :     if (0 > qs)
     690             :     {
     691           0 :       TMH_db->rollback (TMH_db->cls);
     692           0 :       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     693             :       {
     694           0 :         begin_transaction (ac);
     695           0 :         return;
     696             :       }
     697             :       /* Always report on hard error as well to enable diagnostics */
     698           0 :       GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
     699           0 :       resume_abort_with_error (ac,
     700             :                                MHD_HTTP_INTERNAL_SERVER_ERROR,
     701             :                                TALER_EC_GENERIC_DB_STORE_FAILED,
     702             :                                "refund_coin");
     703           0 :       return;
     704             :     }
     705             :   } /* for all coins */
     706             : }
     707             : 
     708             : 
     709             : /**
     710             :  * Begin of the DB transaction.  If required (from soft/serialization errors),
     711             :  * the transaction can be restarted here.
     712             :  *
     713             :  * @param ac abortment context to transact
     714             :  */
     715             : static void
     716           4 : begin_transaction (struct AbortContext *ac)
     717             : {
     718             :   enum GNUNET_DB_QueryStatus qs;
     719             : 
     720             :   /* Avoid re-trying transactions on soft errors forever! */
     721           4 :   if (ac->retry_counter++ > MAX_RETRIES)
     722             :   {
     723           0 :     GNUNET_break (0);
     724           0 :     resume_abort_with_error (ac,
     725             :                              MHD_HTTP_INTERNAL_SERVER_ERROR,
     726             :                              TALER_EC_GENERIC_DB_SOFT_FAILURE,
     727             :                              NULL);
     728           0 :     return;
     729             :   }
     730           4 :   GNUNET_assert (GNUNET_YES == ac->suspended);
     731             : 
     732             :   /* First, try to see if we have all we need already done */
     733           4 :   TMH_db->preflight (TMH_db->cls);
     734           4 :   if (GNUNET_OK !=
     735           4 :       TMH_db->start (TMH_db->cls,
     736             :                      "run abort"))
     737             :   {
     738           0 :     GNUNET_break (0);
     739           0 :     resume_abort_with_error (ac,
     740             :                              MHD_HTTP_INTERNAL_SERVER_ERROR,
     741             :                              TALER_EC_GENERIC_DB_START_FAILED,
     742             :                              NULL);
     743           0 :     return;
     744             :   }
     745             : 
     746             :   /* check payment was indeed incomplete
     747             :      (now that we are in the transaction scope!) */
     748             :   {
     749             :     struct TALER_PrivateContractHashP h_contract_terms;
     750             :     bool paid;
     751             : 
     752           4 :     qs = TMH_db->lookup_order_status (TMH_db->cls,
     753           4 :                                       ac->hc->instance->settings.id,
     754           4 :                                       ac->hc->infix,
     755             :                                       &h_contract_terms,
     756             :                                       &paid);
     757           4 :     switch (qs)
     758             :     {
     759           0 :     case GNUNET_DB_STATUS_SOFT_ERROR:
     760             :     case GNUNET_DB_STATUS_HARD_ERROR:
     761             :       /* Always report on hard error to enable diagnostics */
     762           0 :       GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
     763           0 :       TMH_db->rollback (TMH_db->cls);
     764           0 :       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     765             :       {
     766           0 :         begin_transaction (ac);
     767           0 :         return;
     768             :       }
     769             :       /* Always report on hard error as well to enable diagnostics */
     770           0 :       GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
     771           0 :       resume_abort_with_error (ac,
     772             :                                MHD_HTTP_INTERNAL_SERVER_ERROR,
     773             :                                TALER_EC_GENERIC_DB_FETCH_FAILED,
     774             :                                "order status");
     775           0 :       return;
     776           0 :     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
     777           0 :       TMH_db->rollback (TMH_db->cls);
     778           0 :       resume_abort_with_error (ac,
     779             :                                MHD_HTTP_NOT_FOUND,
     780             :                                TALER_EC_MERCHANT_POST_ORDERS_ID_ABORT_CONTRACT_NOT_FOUND,
     781             :                                "Could not find contract");
     782           0 :       return;
     783           4 :     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
     784           4 :       if (paid)
     785             :       {
     786             :         /* Payment is complete, refuse to abort. */
     787           0 :         TMH_db->rollback (TMH_db->cls);
     788           0 :         resume_abort_with_error (ac,
     789             :                                  MHD_HTTP_PRECONDITION_FAILED,
     790             :                                  TALER_EC_MERCHANT_POST_ORDERS_ID_ABORT_REFUND_REFUSED_PAYMENT_COMPLETE,
     791             :                                  "Payment was complete, refusing to abort");
     792           0 :         return;
     793             :       }
     794             :     }
     795           4 :     if (0 !=
     796           4 :         GNUNET_memcmp (&ac->h_contract_terms,
     797             :                        &h_contract_terms))
     798             :     {
     799           0 :       GNUNET_break_op (0);
     800           0 :       TMH_db->rollback (TMH_db->cls);
     801           0 :       resume_abort_with_error (ac,
     802             :                                MHD_HTTP_FORBIDDEN,
     803             :                                TALER_EC_MERCHANT_POST_ORDERS_ID_ABORT_CONTRACT_HASH_MISSMATCH,
     804             :                                "Provided hash does not match order on file");
     805           0 :       return;
     806             :     }
     807             :   }
     808             : 
     809             :   /* Mark all deposits we have in our database for the order as refunded. */
     810           4 :   qs = TMH_db->lookup_deposits (TMH_db->cls,
     811           4 :                                 ac->hc->instance->settings.id,
     812           4 :                                 &ac->h_contract_terms,
     813             :                                 &refund_coins,
     814             :                                 ac);
     815           4 :   if (0 > qs)
     816             :   {
     817           0 :     TMH_db->rollback (TMH_db->cls);
     818           0 :     if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     819             :     {
     820           0 :       begin_transaction (ac);
     821           0 :       return;
     822             :     }
     823             :     /* Always report on hard error as well to enable diagnostics */
     824           0 :     GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
     825           0 :     resume_abort_with_error (ac,
     826             :                              MHD_HTTP_INTERNAL_SERVER_ERROR,
     827             :                              TALER_EC_GENERIC_DB_FETCH_FAILED,
     828             :                              "deposits");
     829           0 :     return;
     830             :   }
     831             : 
     832           4 :   qs = TMH_db->commit (TMH_db->cls);
     833           4 :   if (0 > qs)
     834             :   {
     835           0 :     TMH_db->rollback (TMH_db->cls);
     836           0 :     if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     837             :     {
     838           0 :       begin_transaction (ac);
     839           0 :       return;
     840             :     }
     841           0 :     resume_abort_with_error (ac,
     842             :                              MHD_HTTP_INTERNAL_SERVER_ERROR,
     843             :                              TALER_EC_GENERIC_DB_COMMIT_FAILED,
     844             :                              NULL);
     845           0 :     return;
     846             :   }
     847             : 
     848             :   /* At this point, the refund got correctly committed
     849             :      into the database. Tell exchange about abort/refund. */
     850           4 :   if (ac->pending > 0)
     851             :   {
     852           2 :     find_next_exchange (ac);
     853           2 :     return;
     854             :   }
     855           2 :   generate_success_response (ac);
     856             : }
     857             : 
     858             : 
     859             : /**
     860             :  * Try to parse the abort request into the given abort context.
     861             :  * Schedules an error response in the connection on failure.
     862             :  *
     863             :  * @param connection HTTP connection we are receiving abortment on
     864             :  * @param hc context we use to handle the abortment
     865             :  * @param ac state of the /abort call
     866             :  * @return #GNUNET_OK on success,
     867             :  *         #GNUNET_NO on failure (response was queued with MHD)
     868             :  *         #GNUNET_SYSERR on hard error (MHD connection must be dropped)
     869             :  */
     870             : static enum GNUNET_GenericReturnValue
     871           2 : parse_abort (struct MHD_Connection *connection,
     872             :              struct TMH_HandlerContext *hc,
     873             :              struct AbortContext *ac)
     874             : {
     875             :   const json_t *coins;
     876             :   struct GNUNET_JSON_Specification spec[] = {
     877           2 :     GNUNET_JSON_spec_array_const ("coins",
     878             :                                   &coins),
     879           2 :     GNUNET_JSON_spec_fixed_auto ("h_contract",
     880             :                                  &ac->h_contract_terms),
     881             : 
     882           2 :     GNUNET_JSON_spec_end ()
     883             :   };
     884             :   enum GNUNET_GenericReturnValue res;
     885             : 
     886           2 :   res = TALER_MHD_parse_json_data (connection,
     887           2 :                                    hc->request_body,
     888             :                                    spec);
     889           2 :   if (GNUNET_YES != res)
     890             :   {
     891           0 :     GNUNET_break_op (0);
     892           0 :     return res;
     893             :   }
     894           2 :   ac->coins_cnt = json_array_size (coins);
     895           2 :   if (0 == ac->coins_cnt)
     896             :   {
     897           0 :     GNUNET_break_op (0);
     898             :     return (MHD_YES ==
     899           0 :             TALER_MHD_reply_with_error (connection,
     900             :                                         MHD_HTTP_BAD_REQUEST,
     901             :                                         TALER_EC_MERCHANT_POST_ORDERS_ID_ABORT_COINS_ARRAY_EMPTY,
     902             :                                         "coins"))
     903             :       ? GNUNET_NO
     904           0 :       : GNUNET_SYSERR;
     905             :   }
     906             :   /* note: 1 coin = 1 deposit confirmation expected */
     907           2 :   ac->pending = ac->coins_cnt;
     908           2 :   ac->rd = GNUNET_new_array (ac->coins_cnt,
     909             :                              struct RefundDetails);
     910             :   /* This loop populates the array 'rd' in 'ac' */
     911             :   {
     912             :     unsigned int coins_index;
     913             :     json_t *coin;
     914           4 :     json_array_foreach (coins, coins_index, coin)
     915             :     {
     916           2 :       struct RefundDetails *rd = &ac->rd[coins_index];
     917             :       const char *exchange_url;
     918             :       struct GNUNET_JSON_Specification ispec[] = {
     919           2 :         TALER_JSON_spec_web_url ("exchange_url",
     920             :                                  &exchange_url),
     921           2 :         GNUNET_JSON_spec_fixed_auto ("coin_pub",
     922             :                                      &rd->coin_pub),
     923           2 :         GNUNET_JSON_spec_end ()
     924             :       };
     925             : 
     926           2 :       res = TALER_MHD_parse_json_data (connection,
     927             :                                        coin,
     928             :                                        ispec);
     929           2 :       if (GNUNET_YES != res)
     930             :       {
     931           0 :         GNUNET_break_op (0);
     932           0 :         return res;
     933             :       }
     934           2 :       rd->exchange_url = GNUNET_strdup (exchange_url);
     935           2 :       rd->index = coins_index;
     936           2 :       rd->ac = ac;
     937             :     }
     938             :   }
     939           2 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     940             :               "Handling /abort for order `%s' with contract hash `%s'\n",
     941             :               ac->hc->infix,
     942             :               GNUNET_h2s (&ac->h_contract_terms.hash));
     943           2 :   return GNUNET_OK;
     944             : }
     945             : 
     946             : 
     947             : /**
     948             :  * Handle a timeout for the processing of the abort request.
     949             :  *
     950             :  * @param cls our `struct AbortContext`
     951             :  */
     952             : static void
     953           0 : handle_abort_timeout (void *cls)
     954             : {
     955           0 :   struct AbortContext *ac = cls;
     956             : 
     957           0 :   ac->timeout_task = NULL;
     958           0 :   GNUNET_assert (GNUNET_YES == ac->suspended);
     959           0 :   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     960             :               "Resuming abort with error after timeout\n");
     961           0 :   if (NULL != ac->fo)
     962             :   {
     963           0 :     TMH_EXCHANGES_keys4exchange_cancel (ac->fo);
     964           0 :     ac->fo = NULL;
     965             :   }
     966           0 :   resume_abort_with_error (ac,
     967             :                            MHD_HTTP_GATEWAY_TIMEOUT,
     968             :                            TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT,
     969             :                            NULL);
     970           0 : }
     971             : 
     972             : 
     973             : MHD_RESULT
     974           4 : TMH_post_orders_ID_abort (const struct TMH_RequestHandler *rh,
     975             :                           struct MHD_Connection *connection,
     976             :                           struct TMH_HandlerContext *hc)
     977             : {
     978           4 :   struct AbortContext *ac = hc->ctx;
     979             : 
     980           4 :   if (NULL == ac)
     981             :   {
     982           2 :     ac = GNUNET_new (struct AbortContext);
     983           2 :     GNUNET_CONTAINER_DLL_insert (ac_head,
     984             :                                  ac_tail,
     985             :                                  ac);
     986           2 :     ac->connection = connection;
     987           2 :     ac->hc = hc;
     988           2 :     hc->ctx = ac;
     989           2 :     hc->cc = &abort_context_cleanup;
     990             :   }
     991           4 :   if (GNUNET_SYSERR == ac->suspended)
     992           0 :     return MHD_NO; /* during shutdown, we don't generate any more replies */
     993           4 :   if (0 != ac->response_code)
     994             :   {
     995             :     MHD_RESULT res;
     996             : 
     997             :     /* We are *done* processing the request,
     998             :        just queue the response (!) */
     999           2 :     if (UINT_MAX == ac->response_code)
    1000             :     {
    1001           0 :       GNUNET_break (0);
    1002           0 :       return MHD_NO; /* hard error */
    1003             :     }
    1004           2 :     res = MHD_queue_response (connection,
    1005             :                               ac->response_code,
    1006             :                               ac->response);
    1007           2 :     MHD_destroy_response (ac->response);
    1008           2 :     ac->response = NULL;
    1009           2 :     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    1010             :                 "Queueing response (%u) for /abort (%s).\n",
    1011             :                 (unsigned int) ac->response_code,
    1012             :                 res ? "OK" : "FAILED");
    1013           2 :     return res;
    1014             :   }
    1015             :   {
    1016             :     enum GNUNET_GenericReturnValue ret;
    1017             : 
    1018           2 :     ret = parse_abort (connection,
    1019             :                        hc,
    1020             :                        ac);
    1021           2 :     if (GNUNET_OK != ret)
    1022             :       return (GNUNET_NO == ret)
    1023             :              ? MHD_YES
    1024           0 :              : MHD_NO;
    1025             :   }
    1026             : 
    1027             :   /* Abort not finished, suspend while we interact with the exchange */
    1028           2 :   GNUNET_assert (GNUNET_NO == ac->suspended);
    1029           2 :   MHD_suspend_connection (connection);
    1030           2 :   ac->suspended = GNUNET_YES;
    1031           2 :   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    1032             :               "Suspending abort handling while working with the exchange\n");
    1033           2 :   ac->timeout_task = GNUNET_SCHEDULER_add_delayed (ABORT_GENERIC_TIMEOUT,
    1034             :                                                    &handle_abort_timeout,
    1035             :                                                    ac);
    1036           2 :   begin_transaction (ac);
    1037           2 :   return MHD_YES;
    1038             : }
    1039             : 
    1040             : 
    1041             : /* end of taler-merchant-httpd_post-orders-ID-abort.c */

Generated by: LCOV version 1.16