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 : }
|