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

Generated by: LCOV version 2.0-1