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-deposits.c (15333B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2014-2024 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-deposits.c
     18  * @brief Handle wire deposit tracking-related 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 <pthread.h>
     26 #include "taler/taler_dbevents.h"
     27 #include "taler/taler_json_lib.h"
     28 #include "taler/taler_util.h"
     29 #include "taler/taler_mhd_lib.h"
     30 #include "taler/taler_signatures.h"
     31 #include "taler-exchange-httpd_get-keys.h"
     32 #include "taler-exchange-httpd_get-deposits.h"
     33 #include "taler-exchange-httpd_responses.h"
     34 
     35 
     36 /**
     37  * Closure for #handle_wtid_data.
     38  */
     39 struct DepositWtidContext
     40 {
     41 
     42   /**
     43    * Kept in a DLL.
     44    */
     45   struct DepositWtidContext *next;
     46 
     47   /**
     48    * Kept in a DLL.
     49    */
     50   struct DepositWtidContext *prev;
     51 
     52   /**
     53    * Context for the request we are processing.
     54    */
     55   struct TEH_RequestContext *rc;
     56 
     57   /**
     58    * Subscription for the database event we are waiting for.
     59    */
     60   struct GNUNET_DB_EventHandler *eh;
     61 
     62   /**
     63    * Hash over the proposal data of the contract for which this deposit is made.
     64    */
     65   struct TALER_PrivateContractHashP h_contract_terms;
     66 
     67   /**
     68    * Hash over the wiring information of the merchant.
     69    */
     70   struct TALER_MerchantWireHashP h_wire;
     71 
     72   /**
     73    * The Merchant's public key.  The deposit inquiry request is to be
     74    * signed by the corresponding private key (using EdDSA).
     75    */
     76   struct TALER_MerchantPublicKeyP merchant;
     77 
     78   /**
     79    * Public key for KYC operations on the target bank
     80    * account for the wire transfer. All zero if no
     81    * public key is accepted yet. In that case, the
     82    * client should use the @e merchant public key for
     83    * the KYC auth wire transfer.
     84    */
     85   union TALER_AccountPublicKeyP account_pub;
     86 
     87   /**
     88    * The coin's public key.  This is the value that must have been
     89    * signed (blindly) by the Exchange.
     90    */
     91   struct TALER_CoinSpendPublicKeyP coin_pub;
     92 
     93   /**
     94    * Set by #handle_wtid data to the wire transfer ID.
     95    */
     96   struct TALER_WireTransferIdentifierRawP wtid;
     97 
     98   /**
     99    * Signature by the merchant.
    100    */
    101   struct TALER_MerchantSignatureP merchant_sig;
    102 
    103   /**
    104    * Set by #handle_wtid data to the coin's contribution to the wire transfer.
    105    */
    106   struct TALER_Amount coin_contribution;
    107 
    108   /**
    109    * Set by #handle_wtid data to the fee charged to the coin.
    110    */
    111   struct TALER_Amount coin_fee;
    112 
    113   /**
    114    * Set by #handle_wtid data to the wire transfer execution time.
    115    */
    116   struct GNUNET_TIME_Timestamp execution_time;
    117 
    118   /**
    119    * Timeout of the request, for long-polling.
    120    */
    121   struct GNUNET_TIME_Absolute timeout;
    122 
    123   /**
    124    * Set by #handle_wtid to the coin contribution to the transaction
    125    * (that is, @e coin_contribution minus @e coin_fee).
    126    */
    127   struct TALER_Amount coin_delta;
    128 
    129   /**
    130    * KYC status information for the receiving account.
    131    */
    132   struct TALER_EXCHANGEDB_KycStatus kyc;
    133 
    134   /**
    135    * #GNUNET_YES if we were suspended, #GNUNET_SYSERR
    136    * if we were woken up due to shutdown.
    137    */
    138   enum GNUNET_GenericReturnValue suspended;
    139 
    140   /**
    141    * What do we long-poll for? Defaults to
    142    * #TALER_DGLPT_OK if not given.
    143    */
    144   enum TALER_DepositGetLongPollTarget lpt;
    145 };
    146 
    147 
    148 /**
    149  * Head of DLL of suspended requests.
    150  */
    151 static struct DepositWtidContext *dwc_head;
    152 
    153 /**
    154  * Tail of DLL of suspended requests.
    155  */
    156 static struct DepositWtidContext *dwc_tail;
    157 
    158 
    159 void
    160 TEH_deposits_get_cleanup ()
    161 {
    162   struct DepositWtidContext *n;
    163 
    164   for (struct DepositWtidContext *ctx = dwc_head;
    165        NULL != ctx;
    166        ctx = n)
    167   {
    168     n = ctx->next;
    169     GNUNET_assert (GNUNET_YES == ctx->suspended);
    170     ctx->suspended = GNUNET_SYSERR;
    171     MHD_resume_connection (ctx->rc->connection);
    172     GNUNET_CONTAINER_DLL_remove (dwc_head,
    173                                  dwc_tail,
    174                                  ctx);
    175   }
    176 }
    177 
    178 
    179 /**
    180  * A merchant asked for details about a deposit.  Provide
    181  * them. Generates the 200 reply.
    182  *
    183  * @param ctx details to respond with
    184  * @return MHD result code
    185  */
    186 static MHD_RESULT
    187 reply_deposit_details (
    188   const struct DepositWtidContext *ctx)
    189 {
    190   struct MHD_Connection *connection = ctx->rc->connection;
    191   struct TALER_ExchangePublicKeyP pub;
    192   struct TALER_ExchangeSignatureP sig;
    193   enum TALER_ErrorCode ec;
    194 
    195   if (TALER_EC_NONE !=
    196       (ec = TALER_exchange_online_confirm_wire_sign (
    197          &TEH_keys_exchange_sign_,
    198          &ctx->h_wire,
    199          &ctx->h_contract_terms,
    200          &ctx->wtid,
    201          &ctx->coin_pub,
    202          ctx->execution_time,
    203          &ctx->coin_delta,
    204          &pub,
    205          &sig)))
    206   {
    207     GNUNET_break (0);
    208     return TALER_MHD_reply_with_ec (connection,
    209                                     ec,
    210                                     NULL);
    211   }
    212   return TALER_MHD_REPLY_JSON_PACK (
    213     connection,
    214     MHD_HTTP_OK,
    215     GNUNET_JSON_pack_data_auto ("wtid",
    216                                 &ctx->wtid),
    217     GNUNET_JSON_pack_timestamp ("execution_time",
    218                                 ctx->execution_time),
    219     TALER_JSON_pack_amount ("coin_contribution",
    220                             &ctx->coin_delta),
    221     GNUNET_JSON_pack_data_auto ("exchange_sig",
    222                                 &sig),
    223     GNUNET_JSON_pack_data_auto ("exchange_pub",
    224                                 &pub));
    225 }
    226 
    227 
    228 /**
    229  * Function called on events received from Postgres.
    230  * Wakes up long pollers.
    231  *
    232  * @param cls the `struct DepositWtidContext *`
    233  * @param extra additional event data provided
    234  * @param extra_size number of bytes in @a extra
    235  */
    236 static void
    237 db_event_cb (void *cls,
    238              const void *extra,
    239              size_t extra_size)
    240 {
    241   struct DepositWtidContext *ctx = cls;
    242   struct GNUNET_AsyncScopeSave old_scope;
    243 
    244   (void) extra;
    245   (void) extra_size;
    246   if (GNUNET_YES != ctx->suspended)
    247     return; /* might get multiple wake-up events */
    248   GNUNET_CONTAINER_DLL_remove (dwc_head,
    249                                dwc_tail,
    250                                ctx);
    251   GNUNET_async_scope_enter (&ctx->rc->async_scope_id,
    252                             &old_scope);
    253   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    254               "Resuming request handling\n");
    255   TEH_check_invariants ();
    256   ctx->suspended = GNUNET_NO;
    257   MHD_resume_connection (ctx->rc->connection);
    258   TALER_MHD_daemon_trigger ();
    259   TEH_check_invariants ();
    260   GNUNET_async_scope_restore (&old_scope);
    261 }
    262 
    263 
    264 /**
    265  * Lookup and return the wire transfer identifier.
    266  *
    267  * @param ctx context of the signed request to execute
    268  * @return MHD result code
    269  */
    270 static MHD_RESULT
    271 handle_track_transaction_request (
    272   struct DepositWtidContext *ctx)
    273 {
    274   struct MHD_Connection *connection = ctx->rc->connection;
    275   enum GNUNET_DB_QueryStatus qs;
    276   bool pending;
    277   struct TALER_Amount fee;
    278 
    279   qs = TEH_plugin->lookup_transfer_by_deposit (
    280     TEH_plugin->cls,
    281     &ctx->h_contract_terms,
    282     &ctx->h_wire,
    283     &ctx->coin_pub,
    284     &ctx->merchant,
    285     &pending,
    286     &ctx->wtid,
    287     &ctx->execution_time,
    288     &ctx->coin_contribution,
    289     &fee,
    290     &ctx->kyc,
    291     &ctx->account_pub);
    292   if (0 > qs)
    293   {
    294     GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
    295     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    296     return TALER_MHD_reply_with_error (
    297       connection,
    298       MHD_HTTP_INTERNAL_SERVER_ERROR,
    299       TALER_EC_GENERIC_DB_FETCH_FAILED,
    300       "lookup_transfer_by_deposit");
    301   }
    302   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    303   {
    304     return TALER_MHD_reply_with_error (
    305       connection,
    306       MHD_HTTP_NOT_FOUND,
    307       TALER_EC_EXCHANGE_DEPOSITS_GET_NOT_FOUND,
    308       NULL);
    309   }
    310 
    311   if (0 >
    312       TALER_amount_subtract (&ctx->coin_delta,
    313                              &ctx->coin_contribution,
    314                              &fee))
    315   {
    316     GNUNET_break (0);
    317     return TALER_MHD_reply_with_error (
    318       connection,
    319       MHD_HTTP_INTERNAL_SERVER_ERROR,
    320       TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
    321       "wire fees exceed aggregate in database");
    322   }
    323   if (pending)
    324   {
    325     if (GNUNET_TIME_absolute_is_future (ctx->timeout))
    326     {
    327       bool do_suspend = false;
    328       switch (ctx->lpt)
    329       {
    330       case TALER_DGLPT_NONE:
    331         break;
    332       case TALER_DGLPT_KYC_REQUIRED_OR_OK:
    333         do_suspend = ctx->kyc.ok;
    334         break;
    335       case TALER_DGLPT_OK:
    336         do_suspend = true;
    337         break;
    338       }
    339       if (do_suspend)
    340       {
    341         GNUNET_assert (GNUNET_NO == ctx->suspended);
    342         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    343                     "Suspending request handling\n");
    344         GNUNET_CONTAINER_DLL_insert (dwc_head,
    345                                      dwc_tail,
    346                                      ctx);
    347         ctx->suspended = GNUNET_YES;
    348         MHD_suspend_connection (connection);
    349         return MHD_YES;
    350       }
    351     }
    352     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    353                 "KYC required with row %llu\n",
    354                 (unsigned long long) ctx->kyc.requirement_row);
    355     return TALER_MHD_REPLY_JSON_PACK (
    356       connection,
    357       MHD_HTTP_ACCEPTED,
    358       GNUNET_JSON_pack_allow_null (
    359         (0 == ctx->kyc.requirement_row)
    360         ? GNUNET_JSON_pack_string ("requirement_row",
    361                                    NULL)
    362         : GNUNET_JSON_pack_uint64 ("requirement_row",
    363                                    ctx->kyc.requirement_row)),
    364       GNUNET_JSON_pack_allow_null (
    365         (GNUNET_is_zero (&ctx->account_pub))
    366         ? GNUNET_JSON_pack_string ("account_pub",
    367                                    NULL)
    368         : GNUNET_JSON_pack_data_auto ("account_pub",
    369                                       &ctx->account_pub)),
    370       GNUNET_JSON_pack_bool ("kyc_ok",
    371                              ctx->kyc.ok),
    372       GNUNET_JSON_pack_timestamp ("execution_time",
    373                                   ctx->execution_time));
    374   }
    375   return reply_deposit_details (ctx);
    376 }
    377 
    378 
    379 /**
    380  * Function called to clean up a context.
    381  *
    382  * @param rc request context with data to clean up
    383  */
    384 static void
    385 dwc_cleaner (struct TEH_RequestContext *rc)
    386 {
    387   struct DepositWtidContext *ctx = rc->rh_ctx;
    388 
    389   GNUNET_assert (GNUNET_YES != ctx->suspended);
    390   if (NULL != ctx->eh)
    391   {
    392     TEH_plugin->event_listen_cancel (TEH_plugin->cls,
    393                                      ctx->eh);
    394     ctx->eh = NULL;
    395   }
    396   GNUNET_free (ctx);
    397 }
    398 
    399 
    400 MHD_RESULT
    401 TEH_handler_deposits_get (struct TEH_RequestContext *rc,
    402                           const char *const args[4])
    403 {
    404   struct DepositWtidContext *ctx = rc->rh_ctx;
    405 
    406   if (NULL == ctx)
    407   {
    408     ctx = GNUNET_new (struct DepositWtidContext);
    409     ctx->rc = rc;
    410     ctx->lpt = TALER_DGLPT_OK; /* default */
    411     rc->rh_ctx = ctx;
    412     rc->rh_cleaner = &dwc_cleaner;
    413 
    414     if (GNUNET_OK !=
    415         GNUNET_STRINGS_string_to_data (args[0],
    416                                        strlen (args[0]),
    417                                        &ctx->h_wire,
    418                                        sizeof (ctx->h_wire)))
    419     {
    420       GNUNET_break_op (0);
    421       return TALER_MHD_reply_with_error (
    422         rc->connection,
    423         MHD_HTTP_BAD_REQUEST,
    424         TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_H_WIRE,
    425         args[0]);
    426     }
    427     if (GNUNET_OK !=
    428         GNUNET_STRINGS_string_to_data (args[1],
    429                                        strlen (args[1]),
    430                                        &ctx->merchant,
    431                                        sizeof (ctx->merchant)))
    432     {
    433       GNUNET_break_op (0);
    434       return TALER_MHD_reply_with_error (
    435         rc->connection,
    436         MHD_HTTP_BAD_REQUEST,
    437         TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_MERCHANT_PUB,
    438         args[1]);
    439     }
    440     if (GNUNET_OK !=
    441         GNUNET_STRINGS_string_to_data (args[2],
    442                                        strlen (args[2]),
    443                                        &ctx->h_contract_terms,
    444                                        sizeof (ctx->h_contract_terms)))
    445     {
    446       GNUNET_break_op (0);
    447       return TALER_MHD_reply_with_error (
    448         rc->connection,
    449         MHD_HTTP_BAD_REQUEST,
    450         TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_H_CONTRACT_TERMS,
    451         args[2]);
    452     }
    453     if (GNUNET_OK !=
    454         GNUNET_STRINGS_string_to_data (args[3],
    455                                        strlen (args[3]),
    456                                        &ctx->coin_pub,
    457                                        sizeof (ctx->coin_pub)))
    458     {
    459       GNUNET_break_op (0);
    460       return TALER_MHD_reply_with_error (
    461         rc->connection,
    462         MHD_HTTP_BAD_REQUEST,
    463         TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_COIN_PUB,
    464         args[3]);
    465     }
    466     TALER_MHD_parse_request_arg_auto_t (rc->connection,
    467                                         "merchant_sig",
    468                                         &ctx->merchant_sig);
    469     TALER_MHD_parse_request_timeout (rc->connection,
    470                                      &ctx->timeout);
    471     {
    472       uint64_t num = 0;
    473       int val;
    474 
    475       TALER_MHD_parse_request_number (rc->connection,
    476                                       "lpt",
    477                                       &num);
    478       val = (int) num;
    479       if ( (val < 0) ||
    480            (val > TALER_DGLPT_MAX) )
    481       {
    482         /* Protocol violation, but we can be graceful and
    483            just ignore the long polling! */
    484         GNUNET_break_op (0);
    485         val = TALER_DGLPT_NONE;
    486       }
    487       ctx->lpt = (enum TALER_DepositGetLongPollTarget) val;
    488       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    489                   "Long polling for target %d with timeout %s\n",
    490                   val,
    491                   GNUNET_TIME_relative2s (
    492                     GNUNET_TIME_absolute_get_remaining (
    493                       ctx->timeout),
    494                     true));
    495     }
    496     TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
    497     {
    498       if (GNUNET_OK !=
    499           TALER_merchant_deposit_verify (&ctx->merchant,
    500                                          &ctx->coin_pub,
    501                                          &ctx->h_contract_terms,
    502                                          &ctx->h_wire,
    503                                          &ctx->merchant_sig))
    504       {
    505         GNUNET_break_op (0);
    506         return TALER_MHD_reply_with_error (
    507           rc->connection,
    508           MHD_HTTP_FORBIDDEN,
    509           TALER_EC_EXCHANGE_DEPOSITS_GET_MERCHANT_SIGNATURE_INVALID,
    510           NULL);
    511       }
    512     }
    513     if ( (GNUNET_TIME_absolute_is_future (ctx->timeout)) &&
    514          (TALER_DGLPT_NONE != ctx->lpt) )
    515     {
    516       struct TALER_CoinDepositEventP rep = {
    517         .header.size = htons (sizeof (rep)),
    518         .header.type = htons (TALER_DBEVENT_EXCHANGE_DEPOSIT_STATUS_CHANGED),
    519         .merchant_pub = ctx->merchant
    520       };
    521 
    522       ctx->eh = TEH_plugin->event_listen (
    523         TEH_plugin->cls,
    524         GNUNET_TIME_absolute_get_remaining (ctx->timeout),
    525         &rep.header,
    526         &db_event_cb,
    527         ctx);
    528       GNUNET_break (NULL != ctx->eh);
    529     }
    530   }
    531 
    532   return handle_track_transaction_request (ctx);
    533 }
    534 
    535 
    536 /* end of taler-exchange-httpd_deposits_get.c */