LCOV - code coverage report
Current view: top level - exchange - taler-exchange-httpd_deposits_get.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 74 141 52.5 %
Date: 2025-07-03 11:36:01 Functions: 5 6 83.3 %

          Line data    Source code
       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_deposits_get.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_keys.h"
      32             : #include "taler-exchange-httpd_deposits_get.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          21 : TEH_deposits_get_cleanup ()
     161             : {
     162             :   struct DepositWtidContext *n;
     163             : 
     164          21 :   for (struct DepositWtidContext *ctx = dwc_head;
     165          21 :        NULL != ctx;
     166           0 :        ctx = n)
     167             :   {
     168           0 :     n = ctx->next;
     169           0 :     GNUNET_assert (GNUNET_YES == ctx->suspended);
     170           0 :     ctx->suspended = GNUNET_SYSERR;
     171           0 :     MHD_resume_connection (ctx->rc->connection);
     172           0 :     GNUNET_CONTAINER_DLL_remove (dwc_head,
     173             :                                  dwc_tail,
     174             :                                  ctx);
     175             :   }
     176          21 : }
     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           2 : reply_deposit_details (
     188             :   const struct DepositWtidContext *ctx)
     189             : {
     190           2 :   struct MHD_Connection *connection = ctx->rc->connection;
     191             :   struct TALER_ExchangePublicKeyP pub;
     192             :   struct TALER_ExchangeSignatureP sig;
     193             :   enum TALER_ErrorCode ec;
     194             : 
     195           2 :   if (TALER_EC_NONE !=
     196           2 :       (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           0 :     GNUNET_break (0);
     208           0 :     return TALER_MHD_reply_with_ec (connection,
     209             :                                     ec,
     210             :                                     NULL);
     211             :   }
     212           2 :   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           0 : db_event_cb (void *cls,
     238             :              const void *extra,
     239             :              size_t extra_size)
     240             : {
     241           0 :   struct DepositWtidContext *ctx = cls;
     242             :   struct GNUNET_AsyncScopeSave old_scope;
     243             : 
     244             :   (void) extra;
     245             :   (void) extra_size;
     246           0 :   if (GNUNET_YES != ctx->suspended)
     247           0 :     return; /* might get multiple wake-up events */
     248           0 :   GNUNET_CONTAINER_DLL_remove (dwc_head,
     249             :                                dwc_tail,
     250             :                                ctx);
     251           0 :   GNUNET_async_scope_enter (&ctx->rc->async_scope_id,
     252             :                             &old_scope);
     253           0 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     254             :               "Resuming request handling\n");
     255           0 :   TEH_check_invariants ();
     256           0 :   ctx->suspended = GNUNET_NO;
     257           0 :   MHD_resume_connection (ctx->rc->connection);
     258           0 :   TALER_MHD_daemon_trigger ();
     259           0 :   TEH_check_invariants ();
     260           0 :   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           8 : handle_track_transaction_request (
     272             :   struct DepositWtidContext *ctx)
     273             : {
     274           8 :   struct MHD_Connection *connection = ctx->rc->connection;
     275             :   enum GNUNET_DB_QueryStatus qs;
     276             :   bool pending;
     277             :   struct TALER_Amount fee;
     278             : 
     279           8 :   qs = TEH_plugin->lookup_transfer_by_deposit (
     280           8 :     TEH_plugin->cls,
     281           8 :     &ctx->h_contract_terms,
     282           8 :     &ctx->h_wire,
     283           8 :     &ctx->coin_pub,
     284           8 :     &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           8 :   if (0 > qs)
     293             :   {
     294           0 :     GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
     295           0 :     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     296           0 :     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           8 :   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     303             :   {
     304           2 :     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           6 :   if (0 >
     312           6 :       TALER_amount_subtract (&ctx->coin_delta,
     313           6 :                              &ctx->coin_contribution,
     314             :                              &fee))
     315             :   {
     316           0 :     GNUNET_break (0);
     317           0 :     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           6 :   if (pending)
     324             :   {
     325           4 :     if (GNUNET_TIME_absolute_is_future (ctx->timeout))
     326             :     {
     327           0 :       bool do_suspend = false;
     328           0 :       switch (ctx->lpt)
     329             :       {
     330           0 :       case TALER_DGLPT_NONE:
     331           0 :         break;
     332           0 :       case TALER_DGLPT_KYC_REQUIRED_OR_OK:
     333           0 :         do_suspend = ctx->kyc.ok;
     334           0 :         break;
     335           0 :       case TALER_DGLPT_OK:
     336           0 :         do_suspend = true;
     337           0 :         break;
     338             :       }
     339           0 :       if (do_suspend)
     340             :       {
     341           0 :         GNUNET_assert (GNUNET_NO == ctx->suspended);
     342           0 :         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     343             :                     "Suspending request handling\n");
     344           0 :         GNUNET_CONTAINER_DLL_insert (dwc_head,
     345             :                                      dwc_tail,
     346             :                                      ctx);
     347           0 :         ctx->suspended = GNUNET_YES;
     348           0 :         MHD_suspend_connection (connection);
     349           0 :         return MHD_YES;
     350             :       }
     351             :     }
     352           4 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     353             :                 "KYC required with row %llu\n",
     354             :                 (unsigned long long) ctx->kyc.requirement_row);
     355           4 :     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           2 :   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           8 : dwc_cleaner (struct TEH_RequestContext *rc)
     386             : {
     387           8 :   struct DepositWtidContext *ctx = rc->rh_ctx;
     388             : 
     389           8 :   GNUNET_assert (GNUNET_YES != ctx->suspended);
     390           8 :   if (NULL != ctx->eh)
     391             :   {
     392           0 :     TEH_plugin->event_listen_cancel (TEH_plugin->cls,
     393             :                                      ctx->eh);
     394           0 :     ctx->eh = NULL;
     395             :   }
     396           8 :   GNUNET_free (ctx);
     397           8 : }
     398             : 
     399             : 
     400             : MHD_RESULT
     401           8 : TEH_handler_deposits_get (struct TEH_RequestContext *rc,
     402             :                           const char *const args[4])
     403             : {
     404           8 :   struct DepositWtidContext *ctx = rc->rh_ctx;
     405             : 
     406           8 :   if (NULL == ctx)
     407             :   {
     408           8 :     ctx = GNUNET_new (struct DepositWtidContext);
     409           8 :     ctx->rc = rc;
     410           8 :     ctx->lpt = TALER_DGLPT_OK; /* default */
     411           8 :     rc->rh_ctx = ctx;
     412           8 :     rc->rh_cleaner = &dwc_cleaner;
     413             : 
     414           8 :     if (GNUNET_OK !=
     415           8 :         GNUNET_STRINGS_string_to_data (args[0],
     416             :                                        strlen (args[0]),
     417           8 :                                        &ctx->h_wire,
     418             :                                        sizeof (ctx->h_wire)))
     419             :     {
     420           0 :       GNUNET_break_op (0);
     421           0 :       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           8 :     if (GNUNET_OK !=
     428           8 :         GNUNET_STRINGS_string_to_data (args[1],
     429           8 :                                        strlen (args[1]),
     430           8 :                                        &ctx->merchant,
     431             :                                        sizeof (ctx->merchant)))
     432             :     {
     433           0 :       GNUNET_break_op (0);
     434           0 :       return TALER_MHD_reply_with_error (
     435             :         rc->connection,
     436             :         MHD_HTTP_BAD_REQUEST,
     437             :         TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_MERCHANT_PUB,
     438           0 :         args[1]);
     439             :     }
     440           8 :     if (GNUNET_OK !=
     441           8 :         GNUNET_STRINGS_string_to_data (args[2],
     442           8 :                                        strlen (args[2]),
     443           8 :                                        &ctx->h_contract_terms,
     444             :                                        sizeof (ctx->h_contract_terms)))
     445             :     {
     446           0 :       GNUNET_break_op (0);
     447           0 :       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           0 :         args[2]);
     452             :     }
     453           8 :     if (GNUNET_OK !=
     454           8 :         GNUNET_STRINGS_string_to_data (args[3],
     455           8 :                                        strlen (args[3]),
     456           8 :                                        &ctx->coin_pub,
     457             :                                        sizeof (ctx->coin_pub)))
     458             :     {
     459           0 :       GNUNET_break_op (0);
     460           0 :       return TALER_MHD_reply_with_error (
     461             :         rc->connection,
     462             :         MHD_HTTP_BAD_REQUEST,
     463             :         TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_COIN_PUB,
     464           0 :         args[3]);
     465             :     }
     466           8 :     TALER_MHD_parse_request_arg_auto_t (rc->connection,
     467             :                                         "merchant_sig",
     468             :                                         &ctx->merchant_sig);
     469           8 :     TALER_MHD_parse_request_timeout (rc->connection,
     470             :                                      &ctx->timeout);
     471             :     {
     472           8 :       uint64_t num = 0;
     473             :       int val;
     474             : 
     475           8 :       TALER_MHD_parse_request_number (rc->connection,
     476             :                                       "lpt",
     477             :                                       &num);
     478           8 :       val = (int) num;
     479           8 :       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           0 :         GNUNET_break_op (0);
     485           0 :         val = TALER_DGLPT_NONE;
     486             :       }
     487           8 :       ctx->lpt = (enum TALER_DepositGetLongPollTarget) val;
     488           8 :       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           8 :     TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
     497             :     {
     498           8 :       if (GNUNET_OK !=
     499           8 :           TALER_merchant_deposit_verify (&ctx->merchant,
     500           8 :                                          &ctx->coin_pub,
     501           8 :                                          &ctx->h_contract_terms,
     502           8 :                                          &ctx->h_wire,
     503           8 :                                          &ctx->merchant_sig))
     504             :       {
     505           0 :         GNUNET_break_op (0);
     506           0 :         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           8 :     if ( (GNUNET_TIME_absolute_is_future (ctx->timeout)) &&
     514           0 :          (TALER_DGLPT_NONE != ctx->lpt) )
     515             :     {
     516           0 :       struct TALER_CoinDepositEventP rep = {
     517           0 :         .header.size = htons (sizeof (rep)),
     518           0 :         .header.type = htons (TALER_DBEVENT_EXCHANGE_DEPOSIT_STATUS_CHANGED),
     519             :         .merchant_pub = ctx->merchant
     520             :       };
     521             : 
     522           0 :       ctx->eh = TEH_plugin->event_listen (
     523           0 :         TEH_plugin->cls,
     524             :         GNUNET_TIME_absolute_get_remaining (ctx->timeout),
     525             :         &rep.header,
     526             :         &db_event_cb,
     527             :         ctx);
     528           0 :       GNUNET_break (NULL != ctx->eh);
     529             :     }
     530             :   }
     531             : 
     532           8 :   return handle_track_transaction_request (ctx);
     533             : }
     534             : 
     535             : 
     536             : /* end of taler-exchange-httpd_deposits_get.c */

Generated by: LCOV version 1.16