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 (13417B)


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