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

            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           15 : TMH_force_ac_resume ()
     270              : {
     271           15 :   for (struct AbortContext *ac = ac_head;
     272           15 :        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           15 : }
     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 2.0-1