LCOV - code coverage report
Current view: top level - backend - taler-merchant-httpd_post-orders-ID-abort.c (source / functions) Hit Total Coverage
Test: GNU Taler merchant coverage report Lines: 190 295 64.4 %
Date: 2021-08-30 06:54:17 Functions: 12 14 85.7 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.14