LCOV - code coverage report
Current view: top level - backenddb - insert_transfer_details.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 81.9 % 83 68
Test Date: 2026-09-04 23:42:01 Functions: 100.0 % 1 1

            Line data    Source code
       1              : /*
       2              :    This file is part of TALER
       3              :    Copyright (C) 2022, 2026 Taler Systems SA
       4              : 
       5              :    TALER is free software; you can redistribute it and/or modify it under the
       6              :    terms of the GNU 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 General Public License for more details.
      12              : 
      13              :    You should have received a copy of the GNU General Public License along with
      14              :    TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
      15              :  */
      16              : /**
      17              :  * @file src/backenddb/insert_transfer_details.c
      18              :  * @brief Implementation of the insert_transfer_details function for Postgres
      19              :  * @author Christian Grothoff
      20              :  */
      21              : #include "platform.h"
      22              : #include <taler/taler_pq_lib.h>
      23              : #include <taler/taler_dbevents.h>
      24              : #include "merchant-database/start.h"
      25              : #include "merchant-database/insert_transfer_details.h"
      26              : #include "helper.h"
      27              : 
      28              : 
      29              : /**
      30              :  * How often do we re-try if we run into a DB serialization error?
      31              :  */
      32              : #define MAX_RETRIES 3
      33              : 
      34              : 
      35              : enum GNUNET_DB_QueryStatus
      36           13 : TALER_MERCHANTDB_insert_transfer_details (
      37              :   struct TALER_MERCHANTDB_PostgresContext *pg,
      38              :   const char *instance_id,
      39              :   const char *exchange_url,
      40              :   struct TALER_FullPayto payto_uri,
      41              :   const struct TALER_WireTransferIdentifierRawP *wtid,
      42              :   const struct TALER_EXCHANGE_TransferData *td,
      43              :   unsigned int *num_settled_orders)
      44           13 : {
      45           13 :   unsigned int len = td->details_length;
      46           13 :   struct TALER_Amount coin_values[GNUNET_NZL (len)];
      47           13 :   struct TALER_Amount deposit_fees[GNUNET_NZL (len)];
      48           13 :   const struct TALER_CoinSpendPublicKeyP *coin_pubs[GNUNET_NZL (len)];
      49           13 :   const struct TALER_PrivateContractHashP *contract_terms[GNUNET_NZL (len)];
      50              :   enum GNUNET_DB_QueryStatus qs;
      51           13 :   bool duplicate = false;
      52              : 
      53           13 :   if (NULL != num_settled_orders)
      54            2 :     *num_settled_orders = 0;
      55           13 :   GNUNET_assert (NULL != pg->current_merchant_id);
      56           13 :   GNUNET_assert (0 == strcmp (instance_id,
      57              :                               pg->current_merchant_id));
      58           13 :   if (0 == len)
      59              :   {
      60              :     /* Legal on the wire: the exchange may sign a transfer with an
      61              :        empty list of deposits. Nothing to associate with the transfer,
      62              :        but we still store the (signed) transfer details below. */
      63            1 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
      64              :                 "Exchange `%s' reported wire transfer without any deposits\n",
      65              :                 exchange_url);
      66              :   }
      67              : 
      68           27 :   for (unsigned int i = 0; i<len; i++)
      69              :   {
      70           14 :     const struct TALER_TrackTransferDetails *tdd = &td->details[i];
      71              : 
      72           14 :     coin_values[i] = tdd->coin_value;
      73           14 :     deposit_fees[i] = tdd->coin_fee;
      74           14 :     coin_pubs[i] = &tdd->coin_pub;
      75           14 :     contract_terms[i] = &tdd->h_contract_terms;
      76              :   }
      77              : 
      78              :   // FIXME: why do we retry here, but not most other SQL statements?
      79              :   // We need a slightly more consistent strategy...
      80           13 :   for (unsigned int retries = 0;
      81           13 :        retries < MAX_RETRIES;
      82            0 :        retries++)
      83              :   {
      84           13 :     if (GNUNET_OK !=
      85           13 :         TALER_MERCHANTDB_start (pg,
      86              :                                 "insert transfer details"))
      87              :     {
      88            0 :       GNUNET_break (0);
      89            0 :       return GNUNET_DB_STATUS_HARD_ERROR;
      90              :     }
      91              : 
      92              :     {
      93           13 :       struct GNUNET_PQ_QueryParam params[] = {
      94           13 :         GNUNET_PQ_query_param_uint64 (&pg->current_merchant_serial),
      95           13 :         GNUNET_PQ_query_param_string (exchange_url),
      96           13 :         GNUNET_PQ_query_param_string (payto_uri.full_payto),
      97           13 :         GNUNET_PQ_query_param_auto_from_type (wtid),
      98           13 :         GNUNET_PQ_query_param_timestamp (&td->execution_time),
      99           13 :         GNUNET_PQ_query_param_auto_from_type (&td->exchange_pub),
     100           13 :         GNUNET_PQ_query_param_auto_from_type (&td->exchange_sig),
     101           13 :         TALER_PQ_query_param_amount_with_currency (pg->conn,
     102              :                                                    &td->total_amount),
     103           13 :         TALER_PQ_query_param_amount_with_currency (pg->conn,
     104              :                                                    &td->wire_fee),
     105           13 :         TALER_PQ_query_param_array_amount_with_currency (
     106              :           len,
     107              :           coin_values,
     108              :           "merchant",
     109              :           pg->conn),
     110           13 :         TALER_PQ_query_param_array_amount_with_currency (
     111              :           len,
     112              :           deposit_fees,
     113              :           "merchant",
     114              :           pg->conn),
     115           13 :         GNUNET_PQ_query_param_array_ptrs_auto_from_type (
     116              :           len,
     117              :           coin_pubs,
     118              :           pg->conn),
     119           13 :         GNUNET_PQ_query_param_array_ptrs_auto_from_type (
     120              :           len,
     121              :           contract_terms,
     122              :           pg->conn),
     123              :         GNUNET_PQ_query_param_end
     124              :       };
     125              :       bool no_account;
     126              :       bool no_exchange;
     127              :       bool conflict;
     128           13 :       size_t num_order_ids = 0;
     129           13 :       char *order_ids = NULL;
     130           13 :       struct GNUNET_PQ_ResultSpec rs[] = {
     131           13 :         GNUNET_PQ_result_spec_bool ("out_no_account",
     132              :                                     &no_account),
     133           13 :         GNUNET_PQ_result_spec_bool ("out_no_exchange",
     134              :                                     &no_exchange),
     135           13 :         GNUNET_PQ_result_spec_bool ("out_duplicate",
     136              :                                     &duplicate),
     137           13 :         GNUNET_PQ_result_spec_bool ("out_conflict",
     138              :                                     &conflict),
     139           13 :         GNUNET_PQ_result_spec_array_string (pg->conn,
     140              :                                             "out_order_ids",
     141              :                                             &num_order_ids,
     142              :                                             &order_ids),
     143              :         GNUNET_PQ_result_spec_end
     144              :       };
     145              : 
     146           13 :       TMH_PQ_prepare_anon (pg,
     147              :                            "SELECT"
     148              :                            " out_no_account"
     149              :                            ",out_no_exchange"
     150              :                            ",out_duplicate"
     151              :                            ",out_conflict"
     152              :                            ",out_order_ids"
     153              :                            " FROM merchant_do_insert_transfer_details"
     154              :                            " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13);");
     155           13 :       qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
     156              :                                                      "",
     157              :                                                      params,
     158              :                                                      rs);
     159           13 :       GNUNET_PQ_cleanup_query_params_closures (params);
     160           13 :       if (0 >= qs)
     161              :       {
     162            0 :         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     163            0 :         TALER_MERCHANTDB_rollback (pg);
     164            0 :         if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     165            0 :           continue;
     166            0 :         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     167              :                     "'insert_transfer_details' failed with status %d\n",
     168              :                     qs);
     169            0 :         return qs;
     170              :       }
     171              :       {
     172              :         /* One aggregated transfer can settle many orders; notify
     173              :            long-pollers about every single one of them. */
     174           13 :         const char *pos = order_ids;
     175              : 
     176           13 :         if (NULL != num_settled_orders)
     177            2 :           *num_settled_orders = (unsigned int) num_order_ids;
     178           25 :         for (size_t k = 0; k<num_order_ids; k++)
     179              :         {
     180           12 :           const char *order_id = pos;
     181           12 :           struct TMH_OrderPayEventP pay_eh = {
     182           12 :             .header.size = htons (sizeof (pay_eh)),
     183           12 :             .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_STATUS_CHANGED),
     184              :             .merchant_pub = pg->current_merchant_pub
     185              :           };
     186              : 
     187           12 :           pos += strlen (order_id) + 1;
     188           12 :           GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     189              :                       "Notifying clients about status change of order %s\n",
     190              :                       order_id);
     191           12 :           GNUNET_CRYPTO_hash (order_id,
     192              :                               strlen (order_id),
     193              :                               &pay_eh.h_order_id);
     194           12 :           GNUNET_PQ_event_notify (pg->conn,
     195              :                                   &pay_eh.header,
     196              :                                   NULL,
     197              :                                   0);
     198              :         }
     199           13 :         GNUNET_free (order_ids);
     200              :       }
     201           13 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     202              :                   "Transfer details inserted: %s%s%s%s\n",
     203              :                   no_account ? "no account " : "",
     204              :                   no_exchange ? "no exchange ": "",
     205              :                   duplicate ? "duplicate ": "",
     206              :                   conflict ? "conflict" : "");
     207              :     }
     208              : 
     209           13 :     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     210              :                 "Committing transaction...\n");
     211           13 :     qs = TALER_MERCHANTDB_commit (pg);
     212           13 :     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     213           13 :       return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
     214            0 :     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     215            0 :     if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
     216            0 :       break;
     217              :   }
     218            0 :   if (duplicate)
     219            0 :     return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
     220            0 :   return qs;
     221              : }
        

Generated by: LCOV version 2.0-1