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


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