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_transfers_get.c (20874B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2014-2018, 2021 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_transfers_get.c
     18  * @brief Handle wire transfer(s) GET 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_signatures.h"
     27 #include "taler-exchange-httpd_keys.h"
     28 #include "taler-exchange-httpd_transfers_get.h"
     29 #include "taler-exchange-httpd_responses.h"
     30 #include "taler/taler_json_lib.h"
     31 #include "taler/taler_mhd_lib.h"
     32 
     33 
     34 /**
     35  * Information about one of the transactions that was
     36  * aggregated, to be returned in the /transfers response.
     37  */
     38 struct AggregatedDepositDetail
     39 {
     40 
     41   /**
     42    * We keep deposit details in a DLL.
     43    */
     44   struct AggregatedDepositDetail *next;
     45 
     46   /**
     47    * We keep deposit details in a DLL.
     48    */
     49   struct AggregatedDepositDetail *prev;
     50 
     51   /**
     52    * Hash of the contract terms.
     53    */
     54   struct TALER_PrivateContractHashP h_contract_terms;
     55 
     56   /**
     57    * Coin's public key of the deposited coin.
     58    */
     59   struct TALER_CoinSpendPublicKeyP coin_pub;
     60 
     61   /**
     62    * Total value of the coin in the deposit (after
     63    * refunds).
     64    */
     65   struct TALER_Amount deposit_value;
     66 
     67   /**
     68    * Fees charged by the exchange for the deposit of this coin (possibly after reduction due to refunds).
     69    */
     70   struct TALER_Amount deposit_fee;
     71 
     72   /**
     73    * Total amount refunded for this coin.
     74    */
     75   struct TALER_Amount refund_total;
     76 };
     77 
     78 
     79 /**
     80  * A merchant asked for transaction details about a wire transfer.
     81  * Provide them. Generates the 200 reply.
     82  *
     83  * @param connection connection to the client
     84  * @param total total amount that was transferred
     85  * @param merchant_pub public key of the merchant
     86  * @param payto_uri destination account
     87  * @param wire_fee wire fee that was charged
     88  * @param exec_time execution time of the wire transfer
     89  * @param wdd_head linked list with details about the combined deposits
     90  * @return MHD result code
     91  */
     92 static MHD_RESULT
     93 reply_transfer_details (struct MHD_Connection *connection,
     94                         const struct TALER_Amount *total,
     95                         const struct TALER_MerchantPublicKeyP *merchant_pub,
     96                         const struct TALER_FullPayto payto_uri,
     97                         const struct TALER_Amount *wire_fee,
     98                         struct GNUNET_TIME_Timestamp exec_time,
     99                         const struct AggregatedDepositDetail *wdd_head)
    100 {
    101   json_t *deposits;
    102   struct GNUNET_HashContext *hash_context;
    103   struct GNUNET_HashCode h_details;
    104   struct TALER_ExchangePublicKeyP pub;
    105   struct TALER_ExchangeSignatureP sig;
    106   struct TALER_FullPaytoHashP h_payto;
    107 
    108   deposits = json_array ();
    109   GNUNET_assert (NULL != deposits);
    110   hash_context = GNUNET_CRYPTO_hash_context_start ();
    111   for (const struct AggregatedDepositDetail *wdd_pos = wdd_head;
    112        NULL != wdd_pos;
    113        wdd_pos = wdd_pos->next)
    114   {
    115     TALER_exchange_online_wire_deposit_append (hash_context,
    116                                                &wdd_pos->h_contract_terms,
    117                                                exec_time,
    118                                                &wdd_pos->coin_pub,
    119                                                &wdd_pos->deposit_value,
    120                                                &wdd_pos->deposit_fee);
    121     if (0 !=
    122         json_array_append_new (
    123           deposits,
    124           GNUNET_JSON_PACK (
    125             GNUNET_JSON_pack_data_auto ("h_contract_terms",
    126                                         &wdd_pos->h_contract_terms),
    127             GNUNET_JSON_pack_data_auto ("coin_pub",
    128                                         &wdd_pos->coin_pub),
    129 
    130             GNUNET_JSON_pack_allow_null (
    131               TALER_JSON_pack_amount ("refund_total",
    132                                       TALER_amount_is_zero (
    133                                         &wdd_pos->refund_total)
    134                                       ? NULL
    135                                       : &wdd_pos->refund_total)),
    136             TALER_JSON_pack_amount ("deposit_value",
    137                                     &wdd_pos->deposit_value),
    138             TALER_JSON_pack_amount ("deposit_fee",
    139                                     &wdd_pos->deposit_fee))))
    140     {
    141       json_decref (deposits);
    142       GNUNET_CRYPTO_hash_context_abort (hash_context);
    143       return TALER_MHD_reply_with_error (connection,
    144                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    145                                          TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
    146                                          "json_array_append_new() failed");
    147     }
    148   }
    149   GNUNET_CRYPTO_hash_context_finish (hash_context,
    150                                      &h_details);
    151   {
    152     enum TALER_ErrorCode ec;
    153 
    154     if (TALER_EC_NONE !=
    155         (ec = TALER_exchange_online_wire_deposit_sign (
    156            &TEH_keys_exchange_sign_,
    157            total,
    158            wire_fee,
    159            merchant_pub,
    160            payto_uri,
    161            &h_details,
    162            &pub,
    163            &sig)))
    164     {
    165       json_decref (deposits);
    166       return TALER_MHD_reply_with_ec (connection,
    167                                       ec,
    168                                       NULL);
    169     }
    170   }
    171 
    172   TALER_full_payto_hash (payto_uri,
    173                          &h_payto);
    174   return TALER_MHD_REPLY_JSON_PACK (
    175     connection,
    176     MHD_HTTP_OK,
    177     TALER_JSON_pack_amount ("total",
    178                             total),
    179     TALER_JSON_pack_amount ("wire_fee",
    180                             wire_fee),
    181     GNUNET_JSON_pack_data_auto ("merchant_pub",
    182                                 merchant_pub),
    183     GNUNET_JSON_pack_data_auto ("h_payto",
    184                                 &h_payto),
    185     GNUNET_JSON_pack_timestamp ("execution_time",
    186                                 exec_time),
    187     GNUNET_JSON_pack_array_steal ("deposits",
    188                                   deposits),
    189     GNUNET_JSON_pack_data_auto ("exchange_sig",
    190                                 &sig),
    191     GNUNET_JSON_pack_data_auto ("exchange_pub",
    192                                 &pub));
    193 }
    194 
    195 
    196 /**
    197  * Closure for #handle_transaction_data.
    198  */
    199 struct WtidTransactionContext
    200 {
    201 
    202   /**
    203    * Identifier of the wire transfer to track.
    204    */
    205   struct TALER_WireTransferIdentifierRawP wtid;
    206 
    207   /**
    208    * Total amount of the wire transfer, as calculated by
    209    * summing up the individual amounts. To be rounded down
    210    * to calculate the real transfer amount at the end.
    211    * Only valid if @e is_valid is #GNUNET_YES.
    212    */
    213   struct TALER_Amount total;
    214 
    215   /**
    216    * Public key of the merchant, only valid if @e is_valid
    217    * is #GNUNET_YES.
    218    */
    219   struct TALER_MerchantPublicKeyP merchant_pub;
    220 
    221   /**
    222    * Wire fees applicable at @e exec_time.
    223    */
    224   struct TALER_WireFeeSet fees;
    225 
    226   /**
    227    * Execution time of the wire transfer
    228    */
    229   struct GNUNET_TIME_Timestamp exec_time;
    230 
    231   /**
    232    * Head of DLL with deposit details for transfers GET response.
    233    */
    234   struct AggregatedDepositDetail *wdd_head;
    235 
    236   /**
    237    * Tail of DLL with deposit details for transfers GET response.
    238    */
    239   struct AggregatedDepositDetail *wdd_tail;
    240 
    241   /**
    242    * Where were the funds wired?
    243    */
    244   struct TALER_FullPayto payto_uri;
    245 
    246   /**
    247    * JSON array with details about the individual deposits.
    248    */
    249   json_t *deposits;
    250 
    251   /**
    252    * Initially #GNUNET_NO, if we found no deposits so far.  Set to
    253    * #GNUNET_YES if we got transaction data, and the database replies
    254    * remained consistent with respect to @e merchant_pub and @e h_wire
    255    * (as they should).  Set to #GNUNET_SYSERR if we encountered an
    256    * internal error.
    257    */
    258   enum GNUNET_GenericReturnValue is_valid;
    259 
    260 };
    261 
    262 
    263 /**
    264  * Callback that totals up the applicable refunds.
    265  *
    266  * @param cls a `struct TALER_Amount` where we keep the total
    267  * @param amount_with_fee amount being refunded
    268  */
    269 static enum GNUNET_GenericReturnValue
    270 add_refunds (void *cls,
    271              const struct TALER_Amount *amount_with_fee)
    272 
    273 {
    274   struct TALER_Amount *total = cls;
    275 
    276   if (0 >
    277       TALER_amount_add (total,
    278                         total,
    279                         amount_with_fee))
    280   {
    281     GNUNET_break (0);
    282     return GNUNET_SYSERR;
    283   }
    284   return GNUNET_OK;
    285 }
    286 
    287 
    288 /**
    289  * Function called with the results of the lookup of the individual deposits
    290  * that were aggregated for the given wire transfer.
    291  *
    292  * @param cls our context for transmission
    293  * @param rowid which row in the DB is the information from (for diagnostics), ignored
    294  * @param merchant_pub public key of the merchant (should be same for all callbacks with the same @e cls)
    295  * @param account_payto_uri where the funds were sent
    296  * @param h_payto hash over @a account_payto_uri as it is in the DB
    297  * @param exec_time execution time of the wire transfer (should be same for all callbacks with the same @e cls)
    298  * @param h_contract_terms which proposal was this payment about
    299  * @param denom_pub denomination public key of the @a coin_pub (ignored)
    300  * @param coin_pub which public key was this payment about
    301  * @param deposit_value amount contributed by this coin in total (including fee)
    302  * @param deposit_fee deposit fee charged by exchange for this coin
    303  */
    304 static void
    305 handle_deposit_data (
    306   void *cls,
    307   uint64_t rowid,
    308   const struct TALER_MerchantPublicKeyP *merchant_pub,
    309   const struct TALER_FullPayto account_payto_uri,
    310   const struct TALER_FullPaytoHashP *h_payto,
    311   struct GNUNET_TIME_Timestamp exec_time,
    312   const struct TALER_PrivateContractHashP *h_contract_terms,
    313   const struct TALER_DenominationPublicKey *denom_pub,
    314   const struct TALER_CoinSpendPublicKeyP *coin_pub,
    315   const struct TALER_Amount *deposit_value,
    316   const struct TALER_Amount *deposit_fee)
    317 {
    318   struct WtidTransactionContext *ctx = cls;
    319   struct TALER_Amount total_refunds;
    320   struct TALER_Amount dval;
    321   struct TALER_Amount dfee;
    322   enum GNUNET_DB_QueryStatus qs;
    323 
    324   (void) rowid;
    325   (void) denom_pub;
    326   (void) h_payto;
    327   if (GNUNET_SYSERR == ctx->is_valid)
    328     return;
    329   GNUNET_assert (GNUNET_OK ==
    330                  TALER_amount_set_zero (deposit_value->currency,
    331                                         &total_refunds));
    332   qs = TEH_plugin->select_refunds_by_coin (TEH_plugin->cls,
    333                                            coin_pub,
    334                                            merchant_pub,
    335                                            h_contract_terms,
    336                                            &add_refunds,
    337                                            &total_refunds);
    338   if (qs < 0)
    339   {
    340     GNUNET_break (0);
    341     ctx->is_valid = GNUNET_SYSERR;
    342     return;
    343   }
    344   if (1 ==
    345       TALER_amount_cmp (&total_refunds,
    346                         deposit_value))
    347   {
    348     /* Refunds exceeded total deposit? not OK! */
    349     GNUNET_break (0);
    350     ctx->is_valid = GNUNET_SYSERR;
    351     return;
    352   }
    353   if (0 ==
    354       TALER_amount_cmp (&total_refunds,
    355                         deposit_value))
    356   {
    357     /* total_refunds == deposit_value;
    358        in this case, the total contributed to the
    359        wire transfer is zero (as are fees) */
    360     GNUNET_assert (GNUNET_OK ==
    361                    TALER_amount_set_zero (deposit_value->currency,
    362                                           &dval));
    363     GNUNET_assert (GNUNET_OK ==
    364                    TALER_amount_set_zero (deposit_value->currency,
    365                                           &dfee));
    366 
    367   }
    368   else
    369   {
    370     /* Compute deposit value by subtracting refunds */
    371     GNUNET_assert (0 <
    372                    TALER_amount_subtract (&dval,
    373                                           deposit_value,
    374                                           &total_refunds));
    375     if (-1 ==
    376         TALER_amount_cmp (&dval,
    377                           deposit_fee))
    378     {
    379       /* dval < deposit_fee, so after refunds less than
    380          the deposit fee remains; reduce deposit fee to
    381          the remaining value of the coin */
    382       dfee = dval;
    383     }
    384     else
    385     {
    386       /* Partial refund, deposit fee remains */
    387       dfee = *deposit_fee;
    388     }
    389   }
    390 
    391   if (GNUNET_NO == ctx->is_valid)
    392   {
    393     /* First one we encounter, setup general information in 'ctx' */
    394     ctx->merchant_pub = *merchant_pub;
    395     ctx->payto_uri.full_payto = GNUNET_strdup (account_payto_uri.full_payto);
    396     ctx->exec_time = exec_time;
    397     ctx->is_valid = GNUNET_YES;
    398     if (0 >
    399         TALER_amount_subtract (&ctx->total,
    400                                &dval,
    401                                &dfee))
    402     {
    403       GNUNET_break (0);
    404       ctx->is_valid = GNUNET_SYSERR;
    405       return;
    406     }
    407   }
    408   else
    409   {
    410     struct TALER_Amount delta;
    411 
    412     /* Subsequent data, check general information matches that in 'ctx';
    413        (it should, otherwise the deposits should not have been aggregated) */
    414     if ( (0 != GNUNET_memcmp (&ctx->merchant_pub,
    415                               merchant_pub)) ||
    416          (0 != TALER_full_payto_cmp (account_payto_uri,
    417                                      ctx->payto_uri)) )
    418     {
    419       GNUNET_break (0);
    420       ctx->is_valid = GNUNET_SYSERR;
    421       return;
    422     }
    423     if (0 >
    424         TALER_amount_subtract (&delta,
    425                                &dval,
    426                                &dfee))
    427     {
    428       GNUNET_break (0);
    429       ctx->is_valid = GNUNET_SYSERR;
    430       return;
    431     }
    432     if (0 >
    433         TALER_amount_add (&ctx->total,
    434                           &ctx->total,
    435                           &delta))
    436     {
    437       GNUNET_break (0);
    438       ctx->is_valid = GNUNET_SYSERR;
    439       return;
    440     }
    441   }
    442 
    443   {
    444     struct AggregatedDepositDetail *wdd;
    445 
    446     wdd = GNUNET_new (struct AggregatedDepositDetail);
    447     wdd->deposit_value = dval;
    448     wdd->deposit_fee = dfee;
    449     wdd->refund_total = total_refunds;
    450     wdd->h_contract_terms = *h_contract_terms;
    451     wdd->coin_pub = *coin_pub;
    452     GNUNET_CONTAINER_DLL_insert (ctx->wdd_head,
    453                                  ctx->wdd_tail,
    454                                  wdd);
    455   }
    456 }
    457 
    458 
    459 /**
    460  * Free data structure reachable from @a ctx, but not @a ctx itself.
    461  *
    462  * @param ctx context to free
    463  */
    464 static void
    465 free_ctx (struct WtidTransactionContext *ctx)
    466 {
    467   struct AggregatedDepositDetail *wdd;
    468 
    469   while (NULL != (wdd = ctx->wdd_head))
    470   {
    471     GNUNET_CONTAINER_DLL_remove (ctx->wdd_head,
    472                                  ctx->wdd_tail,
    473                                  wdd);
    474     GNUNET_free (wdd);
    475   }
    476   GNUNET_free (ctx->payto_uri.full_payto);
    477 }
    478 
    479 
    480 /**
    481  * Execute a "/transfers" GET operation.  Returns the deposit details of the
    482  * deposits that were aggregated to create the given wire transfer.
    483  *
    484  * If it returns a non-error code, the transaction logic MUST
    485  * NOT queue a MHD response.  IF it returns an hard error, the
    486  * transaction logic MUST queue a MHD response and set @a mhd_ret.  IF
    487  * it returns the soft error code, the function MAY be called again to
    488  * retry and MUST not queue a MHD response.
    489  *
    490  * @param cls closure
    491  * @param connection MHD request which triggered the transaction
    492  * @param[out] mhd_ret set to MHD response status for @a connection,
    493  *             if transaction failed (!)
    494  * @return transaction status
    495  */
    496 static enum GNUNET_DB_QueryStatus
    497 get_transfer_deposits (void *cls,
    498                        struct MHD_Connection *connection,
    499                        MHD_RESULT *mhd_ret)
    500 {
    501   struct WtidTransactionContext *ctx = cls;
    502   enum GNUNET_DB_QueryStatus qs;
    503   struct GNUNET_TIME_Timestamp wire_fee_start_date;
    504   struct GNUNET_TIME_Timestamp wire_fee_end_date;
    505   struct TALER_MasterSignatureP wire_fee_master_sig;
    506 
    507   /* resetting to NULL/0 in case transaction was repeated after
    508      serialization failure */
    509   free_ctx (ctx);
    510   qs = TEH_plugin->lookup_wire_transfer (TEH_plugin->cls,
    511                                          &ctx->wtid,
    512                                          &handle_deposit_data,
    513                                          ctx);
    514   if (0 > qs)
    515   {
    516     if (GNUNET_DB_STATUS_HARD_ERROR == qs)
    517     {
    518       GNUNET_break (0);
    519       *mhd_ret = TALER_MHD_reply_with_error (connection,
    520                                              MHD_HTTP_INTERNAL_SERVER_ERROR,
    521                                              TALER_EC_GENERIC_DB_FETCH_FAILED,
    522                                              "wire transfer");
    523     }
    524     return qs;
    525   }
    526   if (GNUNET_SYSERR == ctx->is_valid)
    527   {
    528     GNUNET_break (0);
    529     *mhd_ret = TALER_MHD_reply_with_error (connection,
    530                                            MHD_HTTP_INTERNAL_SERVER_ERROR,
    531                                            TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
    532                                            "wire history malformed");
    533     return GNUNET_DB_STATUS_HARD_ERROR;
    534   }
    535   if (GNUNET_NO == ctx->is_valid)
    536   {
    537     *mhd_ret = TALER_MHD_reply_with_error (connection,
    538                                            MHD_HTTP_NOT_FOUND,
    539                                            TALER_EC_EXCHANGE_TRANSFERS_GET_WTID_NOT_FOUND,
    540                                            NULL);
    541     return GNUNET_DB_STATUS_HARD_ERROR;
    542   }
    543   {
    544     char *wire_method;
    545     uint64_t rowid;
    546 
    547     wire_method = TALER_payto_get_method (ctx->payto_uri.full_payto);
    548     if (NULL == wire_method)
    549     {
    550       GNUNET_break (0);
    551       *mhd_ret = TALER_MHD_reply_with_error (connection,
    552                                              MHD_HTTP_INTERNAL_SERVER_ERROR,
    553                                              TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
    554                                              "payto:// without wire method encountered");
    555       return GNUNET_DB_STATUS_HARD_ERROR;
    556     }
    557     qs = TEH_plugin->get_wire_fee (TEH_plugin->cls,
    558                                    wire_method,
    559                                    ctx->exec_time,
    560                                    &rowid,
    561                                    &wire_fee_start_date,
    562                                    &wire_fee_end_date,
    563                                    &ctx->fees,
    564                                    &wire_fee_master_sig);
    565     GNUNET_free (wire_method);
    566   }
    567   if (0 >= qs)
    568   {
    569     if ( (GNUNET_DB_STATUS_HARD_ERROR == qs) ||
    570          (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) )
    571     {
    572       GNUNET_break (0);
    573       *mhd_ret = TALER_MHD_reply_with_error (connection,
    574                                              MHD_HTTP_INTERNAL_SERVER_ERROR,
    575                                              TALER_EC_EXCHANGE_TRANSFERS_GET_WIRE_FEE_NOT_FOUND,
    576                                              NULL);
    577     }
    578     return qs;
    579   }
    580   if (0 >
    581       TALER_amount_subtract (&ctx->total,
    582                              &ctx->total,
    583                              &ctx->fees.wire))
    584   {
    585     GNUNET_break (0);
    586     *mhd_ret = TALER_MHD_reply_with_error (connection,
    587                                            MHD_HTTP_INTERNAL_SERVER_ERROR,
    588                                            TALER_EC_EXCHANGE_TRANSFERS_GET_WIRE_FEE_INCONSISTENT,
    589                                            NULL);
    590     return GNUNET_DB_STATUS_HARD_ERROR;
    591   }
    592   return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
    593 }
    594 
    595 
    596 MHD_RESULT
    597 TEH_handler_transfers_get (struct TEH_RequestContext *rc,
    598                            const char *const args[1])
    599 {
    600   struct WtidTransactionContext ctx;
    601   MHD_RESULT mhd_ret;
    602 
    603   memset (&ctx,
    604           0,
    605           sizeof (ctx));
    606   if (GNUNET_OK !=
    607       GNUNET_STRINGS_string_to_data (args[0],
    608                                      strlen (args[0]),
    609                                      &ctx.wtid,
    610                                      sizeof (ctx.wtid)))
    611   {
    612     GNUNET_break_op (0);
    613     return TALER_MHD_reply_with_error (rc->connection,
    614                                        MHD_HTTP_BAD_REQUEST,
    615                                        TALER_EC_EXCHANGE_TRANSFERS_GET_WTID_MALFORMED,
    616                                        args[0]);
    617   }
    618   if (GNUNET_OK !=
    619       TEH_DB_run_transaction (rc->connection,
    620                               "run transfers GET",
    621                               TEH_MT_REQUEST_OTHER,
    622                               &mhd_ret,
    623                               &get_transfer_deposits,
    624                               &ctx))
    625   {
    626     free_ctx (&ctx);
    627     return mhd_ret;
    628   }
    629   mhd_ret = reply_transfer_details (rc->connection,
    630                                     &ctx.total,
    631                                     &ctx.merchant_pub,
    632                                     ctx.payto_uri,
    633                                     &ctx.fees.wire,
    634                                     ctx.exec_time,
    635                                     ctx.wdd_head);
    636   free_ctx (&ctx);
    637   return mhd_ret;
    638 }
    639 
    640 
    641 /* end of taler-exchange-httpd_transfers_get.c */