exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

taler-exchange-httpd_get-purses-PURSE_PUB-merge.c (13598B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2022 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Affero General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file taler-exchange-httpd_get-purses-PURSE_PUB-merge.c
     18  * @brief Handle GET /purses/$PID/$TARGET requests
     19  * @author Christian Grothoff
     20  */
     21 #include <gnunet/gnunet_util_lib.h>
     22 #include <jansson.h>
     23 #include <microhttpd.h>
     24 #include "taler/taler_mhd_lib.h"
     25 #include "taler/taler_dbevents.h"
     26 #include "taler-exchange-httpd_get-keys.h"
     27 #include "taler-exchange-httpd_get-purses-PURSE_PUB-merge.h"
     28 #include "taler-exchange-httpd_mhd.h"
     29 #include "taler-exchange-httpd_responses.h"
     30 #include "exchange-database/select_purse.h"
     31 #include "exchange-database/event_listen.h"
     32 #include "exchange-database/event_listen_cancel.h"
     33 
     34 
     35 /**
     36  * Information about an ongoing /purses GET operation.
     37  */
     38 struct GetContext
     39 {
     40   /**
     41    * Kept in a DLL.
     42    */
     43   struct GetContext *next;
     44 
     45   /**
     46    * Kept in a DLL.
     47    */
     48   struct GetContext *prev;
     49 
     50   /**
     51    * Connection we are handling.
     52    */
     53   struct MHD_Connection *connection;
     54 
     55   /**
     56    * Subscription for the database event we are
     57    * waiting for.
     58    */
     59   struct GNUNET_DB_EventHandler *eh;
     60 
     61   /**
     62    * Subscription for refund event we are
     63    * waiting for.
     64    */
     65   struct GNUNET_DB_EventHandler *ehr;
     66 
     67   /**
     68    * Public key of our purse.
     69    */
     70   struct TALER_PurseContractPublicKeyP purse_pub;
     71 
     72   /**
     73    * When does this purse expire?
     74    */
     75   struct GNUNET_TIME_Timestamp purse_expiration;
     76 
     77   /**
     78    * When was this purse merged?
     79    */
     80   struct GNUNET_TIME_Timestamp merge_timestamp;
     81 
     82   /**
     83    * How much is the purse (supposed) to be worth?
     84    */
     85   struct TALER_Amount amount;
     86 
     87   /**
     88    * How much was deposited into the purse so far?
     89    */
     90   struct TALER_Amount deposited;
     91 
     92   /**
     93    * Hash over the contract of the purse.
     94    */
     95   struct TALER_PrivateContractHashP h_contract;
     96 
     97   /**
     98    * When will this request time out?
     99    */
    100   struct GNUNET_TIME_Absolute timeout;
    101 
    102   /**
    103    * true to wait for merge, false to wait for deposit.
    104    */
    105   bool wait_for_merge;
    106 
    107   /**
    108    * True if we are still suspended.
    109    */
    110   bool suspended;
    111 };
    112 
    113 
    114 /**
    115  * Head of DLL of suspended GET requests.
    116  */
    117 static struct GetContext *gc_head;
    118 
    119 /**
    120  * Tail of DLL of suspended GET requests.
    121  */
    122 static struct GetContext *gc_tail;
    123 
    124 
    125 void
    126 TEH_purses_get_cleanup ()
    127 {
    128   struct GetContext *gc;
    129 
    130   while (NULL != (gc = gc_head))
    131   {
    132     GNUNET_CONTAINER_DLL_remove (gc_head,
    133                                  gc_tail,
    134                                  gc);
    135     if (gc->suspended)
    136     {
    137       gc->suspended = false;
    138       MHD_resume_connection (gc->connection);
    139     }
    140   }
    141 }
    142 
    143 
    144 /**
    145  * Function called once a connection is done to
    146  * clean up the `struct GetContext` state.
    147  *
    148  * @param rc context to clean up for
    149  */
    150 static void
    151 gc_cleanup (struct TEH_RequestContext *rc)
    152 {
    153   struct GetContext *gc = rc->rh_ctx;
    154 
    155   GNUNET_assert (! gc->suspended);
    156   if (NULL != gc->eh)
    157   {
    158     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    159                 "Cancelling DB event listening\n");
    160     TALER_TALER_EXCHANGEDB_event_listen_cancel (TEH_pg,
    161                                                 gc->eh);
    162     gc->eh = NULL;
    163   }
    164   if (NULL != gc->ehr)
    165   {
    166     TALER_TALER_EXCHANGEDB_event_listen_cancel (TEH_pg,
    167                                                 gc->ehr);
    168     gc->ehr = NULL;
    169   }
    170   GNUNET_free (gc);
    171 }
    172 
    173 
    174 /**
    175  * Function called on events received from Postgres.
    176  * Wakes up long pollers.
    177  *
    178  * @param cls the `struct TEH_RequestContext *`
    179  * @param extra additional event data provided
    180  * @param extra_size number of bytes in @a extra
    181  */
    182 static void
    183 db_event_cb (void *cls,
    184              const void *extra,
    185              size_t extra_size)
    186 {
    187   struct TEH_RequestContext *rc = cls;
    188   struct GetContext *gc = rc->rh_ctx;
    189   struct GNUNET_AsyncScopeSave old_scope;
    190 
    191   (void) extra;
    192   (void) extra_size;
    193   if (NULL == gc)
    194     return; /* event triggered while main transaction
    195                was still running */
    196   if (! gc->suspended)
    197     return; /* might get multiple wake-up events */
    198   gc->suspended = false;
    199   GNUNET_async_scope_enter (&rc->async_scope_id,
    200                             &old_scope);
    201   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    202               "Waking up on %p - %p - %s\n",
    203               rc,
    204               gc,
    205               gc->suspended ? "suspended" : "active");
    206   TEH_check_invariants ();
    207   GNUNET_CONTAINER_DLL_remove (gc_head,
    208                                gc_tail,
    209                                gc);
    210   MHD_resume_connection (gc->connection);
    211   TALER_MHD_daemon_trigger ();
    212   TEH_check_invariants ();
    213   GNUNET_async_scope_restore (&old_scope);
    214 }
    215 
    216 
    217 MHD_RESULT
    218 TEH_handler_purses_get (struct TEH_RequestContext *rc,
    219                         const char *const args[2])
    220 {
    221   struct GetContext *gc = rc->rh_ctx;
    222   bool purse_deleted;
    223   bool purse_refunded;
    224   MHD_RESULT res;
    225 
    226   if (NULL == gc)
    227   {
    228     gc = GNUNET_new (struct GetContext);
    229     rc->rh_ctx = gc;
    230     rc->rh_cleaner = &gc_cleanup;
    231     gc->connection = rc->connection;
    232     if (GNUNET_OK !=
    233         GNUNET_STRINGS_string_to_data (args[0],
    234                                        strlen (args[0]),
    235                                        &gc->purse_pub,
    236                                        sizeof (gc->purse_pub)))
    237     {
    238       GNUNET_break_op (0);
    239       return TALER_MHD_reply_with_error (rc->connection,
    240                                          MHD_HTTP_BAD_REQUEST,
    241                                          TALER_EC_EXCHANGE_GENERIC_PURSE_PUB_MALFORMED,
    242                                          args[0]);
    243     }
    244     if (0 == strcmp (args[1],
    245                      "merge"))
    246       gc->wait_for_merge = true;
    247     else if (0 == strcmp (args[1],
    248                           "deposit"))
    249       gc->wait_for_merge = false;
    250     else
    251     {
    252       GNUNET_break_op (0);
    253       return TALER_MHD_reply_with_error (rc->connection,
    254                                          MHD_HTTP_BAD_REQUEST,
    255                                          TALER_EC_EXCHANGE_PURSES_INVALID_WAIT_TARGET,
    256                                          args[1]);
    257     }
    258 
    259     TALER_MHD_parse_request_timeout (rc->connection,
    260                                      &gc->timeout);
    261     if ( (GNUNET_TIME_absolute_is_future (gc->timeout)) &&
    262          (NULL == gc->eh) )
    263     {
    264       struct TALER_EXCHANGEDB_PurseEventP rep = {
    265         .header.size = htons (sizeof (rep)),
    266         .header.type = htons (
    267           gc->wait_for_merge
    268           ? TALER_DBEVENT_EXCHANGE_PURSE_MERGED
    269           : TALER_DBEVENT_EXCHANGE_PURSE_DEPOSITED),
    270         .purse_pub = gc->purse_pub
    271       };
    272 
    273       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    274                   "Starting DB event listening on purse %s (%s)\n",
    275                   TALER_B2S (&gc->purse_pub),
    276                   gc->wait_for_merge
    277                   ? "waiting for merge"
    278                   : "waiting for deposit");
    279       gc->eh = TALER_EXCHANGEDB_event_listen (
    280         TEH_pg,
    281         GNUNET_TIME_absolute_get_remaining (gc->timeout),
    282         &rep.header,
    283         &db_event_cb,
    284         rc);
    285       if (NULL == gc->eh)
    286       {
    287         GNUNET_break (0);
    288         gc->timeout = GNUNET_TIME_UNIT_ZERO_ABS;
    289       }
    290       else
    291       {
    292         struct GNUNET_DB_EventHeaderP repr = {
    293           .size = htons (sizeof (repr)),
    294           .type = htons (TALER_DBEVENT_EXCHANGE_PURSE_REFUNDED),
    295         };
    296 
    297         gc->ehr = TALER_EXCHANGEDB_event_listen (
    298           TEH_pg,
    299           GNUNET_TIME_absolute_get_remaining (gc->timeout),
    300           &repr,
    301           &db_event_cb,
    302           rc);
    303       }
    304     }
    305   } /* end first-time initialization */
    306 
    307   {
    308     enum GNUNET_DB_QueryStatus qs;
    309     struct GNUNET_TIME_Timestamp create_timestamp;
    310 
    311     qs = TALER_EXCHANGEDB_select_purse (TEH_pg,
    312                                         &gc->purse_pub,
    313                                         &create_timestamp,
    314                                         &gc->purse_expiration,
    315                                         &gc->amount,
    316                                         &gc->deposited,
    317                                         &gc->h_contract,
    318                                         &gc->merge_timestamp,
    319                                         &purse_deleted,
    320                                         &purse_refunded);
    321     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    322                 "select_purse %s returned %d (%s)\n",
    323                 args[0],
    324                 (int) qs,
    325                 GNUNET_TIME_timestamp2s (gc->merge_timestamp));
    326     switch (qs)
    327     {
    328     case GNUNET_DB_STATUS_HARD_ERROR:
    329       GNUNET_break (0);
    330       return TALER_MHD_reply_with_error (rc->connection,
    331                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    332                                          TALER_EC_GENERIC_DB_FETCH_FAILED,
    333                                          "select_purse");
    334     case GNUNET_DB_STATUS_SOFT_ERROR:
    335       GNUNET_break (0);
    336       return TALER_MHD_reply_with_error (rc->connection,
    337                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    338                                          TALER_EC_GENERIC_DB_FETCH_FAILED,
    339                                          "select_purse");
    340     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    341       return TALER_MHD_reply_with_error (rc->connection,
    342                                          MHD_HTTP_NOT_FOUND,
    343                                          TALER_EC_EXCHANGE_GENERIC_PURSE_UNKNOWN,
    344                                          NULL);
    345     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    346       break; /* handled below */
    347     }
    348   }
    349   if (purse_refunded ||
    350       purse_deleted)
    351   {
    352     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    353                 "Purse refunded or deleted\n");
    354     return TALER_MHD_reply_with_error (rc->connection,
    355                                        MHD_HTTP_GONE,
    356                                        purse_deleted
    357                                        ? TALER_EC_EXCHANGE_GENERIC_PURSE_DELETED
    358                                        : TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED
    359                                        ,
    360                                        GNUNET_TIME_timestamp2s (
    361                                          gc->purse_expiration));
    362   }
    363 
    364   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    365               "Deposited amount is %s (%d/%d/%d)\n",
    366               TALER_amount2s (&gc->deposited),
    367               GNUNET_TIME_absolute_is_future (gc->timeout),
    368               GNUNET_TIME_absolute_is_never (gc->merge_timestamp.abs_time),
    369               (0 <
    370                TALER_amount_cmp (&gc->amount,
    371                                  &gc->deposited)));
    372   if (GNUNET_TIME_absolute_is_future (gc->timeout) &&
    373       ( ((gc->wait_for_merge) &&
    374          GNUNET_TIME_absolute_is_never (gc->merge_timestamp.abs_time)) ||
    375         ((! gc->wait_for_merge) &&
    376          (0 <
    377           TALER_amount_cmp (&gc->amount,
    378                             &gc->deposited))) ) )
    379   {
    380     gc->suspended = true;
    381     GNUNET_CONTAINER_DLL_insert (gc_head,
    382                                  gc_tail,
    383                                  gc);
    384     MHD_suspend_connection (gc->connection);
    385     return MHD_YES;
    386   }
    387 
    388   {
    389     struct GNUNET_TIME_Timestamp dt = GNUNET_TIME_timestamp_get ();
    390     struct TALER_ExchangePublicKeyP exchange_pub;
    391     struct TALER_ExchangeSignatureP exchange_sig;
    392     enum TALER_ErrorCode ec;
    393 
    394     if (GNUNET_TIME_timestamp_cmp (dt,
    395                                    >,
    396                                    gc->purse_expiration))
    397       dt = gc->purse_expiration;
    398     if (0 <
    399         TALER_amount_cmp (&gc->amount,
    400                           &gc->deposited))
    401     {
    402       /* amount > deposited: not yet fully paid */
    403       dt = GNUNET_TIME_UNIT_ZERO_TS;
    404     }
    405     if (TALER_EC_NONE !=
    406         (ec = TALER_exchange_online_purse_status_sign (
    407            &TEH_keys_exchange_sign_,
    408            gc->merge_timestamp,
    409            dt,
    410            &gc->deposited,
    411            &exchange_pub,
    412            &exchange_sig)))
    413     {
    414       res = TALER_MHD_reply_with_ec (rc->connection,
    415                                      ec,
    416                                      NULL);
    417     }
    418     else
    419     {
    420       /* Make sure merge_timestamp is omitted if not yet merged */
    421       if (GNUNET_TIME_absolute_is_never (gc->merge_timestamp.abs_time))
    422         gc->merge_timestamp = GNUNET_TIME_UNIT_ZERO_TS;
    423       res = TALER_MHD_REPLY_JSON_PACK (
    424         rc->connection,
    425         MHD_HTTP_OK,
    426         TALER_JSON_pack_amount ("balance",
    427                                 &gc->deposited),
    428         GNUNET_JSON_pack_data_auto ("exchange_sig",
    429                                     &exchange_sig),
    430         GNUNET_JSON_pack_data_auto ("exchange_pub",
    431                                     &exchange_pub),
    432         GNUNET_JSON_pack_timestamp ("purse_expiration",
    433                                     gc->purse_expiration),
    434         GNUNET_JSON_pack_allow_null (
    435           GNUNET_JSON_pack_timestamp ("merge_timestamp",
    436                                       gc->merge_timestamp)),
    437         GNUNET_JSON_pack_allow_null (
    438           GNUNET_JSON_pack_timestamp ("deposit_timestamp",
    439                                       dt))
    440         );
    441     }
    442   }
    443   return res;
    444 }
    445 
    446 
    447 /* end of taler-exchange-httpd_purses_get.c */