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 exchangedb/do_import_credits.c
18 : * @brief Implementation of the do_import_credits function for Postgres
19 : * @author Christian Grothoff
20 : * @author Joseph Xu
21 : */
22 : #include "taler/taler_pq_lib.h"
23 : #include "exchange-database/do_import_credits.h"
24 : #include "helper.h"
25 :
26 :
27 : /**
28 : * Compute the notification channel for a reserve that was credited.
29 : *
30 : * @param reserve_pub reserve to notify on
31 : * @return string to pass to postgres for the notification
32 : */
33 : static char *
34 60 : compute_notify_on_reserve (const struct TALER_ReservePublicKeyP *reserve_pub)
35 : {
36 60 : struct TALER_EXCHANGEDB_ReserveEventP rep = {
37 60 : .header.size = htons (sizeof (rep)),
38 60 : .header.type = htons (TALER_DBEVENT_EXCHANGE_RESERVE_INCOMING),
39 : .reserve_pub = *reserve_pub
40 : };
41 :
42 60 : return GNUNET_PQ_get_event_notify_channel (&rep.header);
43 : }
44 :
45 :
46 : /**
47 : * Compute the notification channel for an account that authenticated itself.
48 : *
49 : * @param h_payto normalized hash of the account that was authenticated
50 : * @return string to pass to postgres for the notification
51 : */
52 : static char *
53 20 : compute_notify_on_kycauth (const struct TALER_NormalizedPaytoHashP *h_payto)
54 : {
55 20 : struct TALER_EXCHANGEDB_KycCompletedEventP rep = {
56 20 : .header.size = htons (sizeof (rep)),
57 20 : .header.type = htons (TALER_DBEVENT_EXCHANGE_KYC_COMPLETED),
58 : .h_payto = *h_payto
59 : };
60 :
61 20 : return GNUNET_PQ_get_event_notify_channel (&rep.header);
62 : }
63 :
64 :
65 : /**
66 : * Closure for #helper_cb().
67 : */
68 : struct Context
69 : {
70 : /**
71 : * Array with entries set to 'true' for transfers we had already imported.
72 : */
73 : bool *transaction_duplicates;
74 :
75 : /**
76 : * Length of the @e transaction_duplicates array.
77 : */
78 : unsigned int length;
79 :
80 : /**
81 : * Set to #GNUNET_SYSERR on failures.
82 : */
83 : enum GNUNET_GenericReturnValue status;
84 : };
85 :
86 :
87 : /**
88 : * Function called with the rows the stored procedure returned, one per
89 : * reserve transfer, in input order.
90 : *
91 : * @param cls closure of type `struct Context *`
92 : * @param result the postgres result
93 : * @param num_results the number of results in @a result
94 : */
95 : static void
96 115 : helper_cb (void *cls,
97 : PGresult *result,
98 : unsigned int num_results)
99 : {
100 115 : struct Context *ctx = cls;
101 :
102 115 : if (num_results > ctx->length)
103 : {
104 : /* The procedure returns one row per reserve transfer we passed in; a
105 : different count means the two have drifted apart, and writing past the
106 : caller's array is not the way to find that out. */
107 0 : GNUNET_break (0);
108 0 : ctx->status = GNUNET_SYSERR;
109 0 : return;
110 : }
111 175 : for (unsigned int i = 0; i<num_results; i++)
112 : {
113 60 : struct GNUNET_PQ_ResultSpec rs[] = {
114 60 : GNUNET_PQ_result_spec_bool ("out_duplicate",
115 60 : &ctx->transaction_duplicates[i]),
116 : GNUNET_PQ_result_spec_end
117 : };
118 :
119 60 : if (GNUNET_OK !=
120 60 : GNUNET_PQ_extract_result (result,
121 : rs,
122 : i))
123 : {
124 0 : GNUNET_break (0);
125 0 : ctx->status = GNUNET_SYSERR;
126 0 : return;
127 : }
128 : }
129 : }
130 :
131 :
132 : enum GNUNET_DB_QueryStatus
133 115 : TALER_EXCHANGEDB_do_import_credits (
134 : struct TALER_EXCHANGEDB_PostgresContext *pg,
135 : const struct TALER_EXCHANGEDB_CreditBatch *batch,
136 : enum GNUNET_DB_QueryStatus *reserve_results)
137 115 : {
138 115 : unsigned int rlen = batch->reserves_length;
139 115 : unsigned int klen = batch->kycauths_length;
140 115 : unsigned int wlen = batch->wads_length;
141 115 : unsigned int dups = 0;
142 :
143 : /* reserve transfers */
144 115 : struct TALER_ReservePublicKeyP reserve_pubs[GNUNET_NZL (rlen)];
145 115 : uint64_t wire_references[GNUNET_NZL (rlen)];
146 115 : struct TALER_Amount balances[GNUNET_NZL (rlen)];
147 115 : struct GNUNET_TIME_Timestamp execution_times[GNUNET_NZL (rlen)];
148 115 : struct TALER_FullPaytoHashP h_full_paytos[GNUNET_NZL (rlen)];
149 115 : struct TALER_NormalizedPaytoHashP h_normalized_paytos[GNUNET_NZL (rlen)];
150 115 : const char *payto_uris[GNUNET_NZL (rlen)];
151 115 : char *notify_s[GNUNET_NZL (rlen)];
152 115 : bool transaction_duplicates[GNUNET_NZL (rlen)];
153 :
154 : /* KYC authentication transfers */
155 115 : union TALER_AccountPublicKeyP ka_account_pubs[GNUNET_NZL (klen)];
156 115 : uint64_t ka_wire_references[GNUNET_NZL (klen)];
157 115 : struct TALER_Amount ka_balances[GNUNET_NZL (klen)];
158 115 : struct GNUNET_TIME_Timestamp ka_execution_times[GNUNET_NZL (klen)];
159 115 : struct TALER_FullPaytoHashP ka_h_full_paytos[GNUNET_NZL (klen)];
160 115 : struct TALER_NormalizedPaytoHashP ka_h_normalized_paytos[GNUNET_NZL (klen)];
161 115 : const char *ka_payto_uris[GNUNET_NZL (klen)];
162 115 : char *ka_notify_s[GNUNET_NZL (klen)];
163 :
164 : /* WAD transfers */
165 115 : struct TALER_WadIdentifierP wad_ids[GNUNET_NZL (wlen)];
166 115 : const char *wad_origin_exchange_urls[GNUNET_NZL (wlen)];
167 115 : struct TALER_Amount wad_balances[GNUNET_NZL (wlen)];
168 115 : struct GNUNET_TIME_Timestamp wad_execution_times[GNUNET_NZL (wlen)];
169 :
170 : struct GNUNET_TIME_Timestamp reserve_expiration
171 115 : = GNUNET_TIME_relative_to_timestamp (pg->idle_reserve_expiration_time);
172 : struct GNUNET_TIME_Timestamp gc
173 115 : = GNUNET_TIME_relative_to_timestamp (pg->legal_reserve_expiration_time);
174 : struct GNUNET_TIME_Absolute lease_until
175 115 : = GNUNET_TIME_relative_to_absolute (batch->lease);
176 : enum GNUNET_DB_QueryStatus qs;
177 :
178 175 : for (unsigned int i = 0; i<rlen; i++)
179 : {
180 60 : const struct TALER_EXCHANGEDB_ReserveInInfo *reserve = &batch->reserves[i];
181 :
182 60 : TALER_full_payto_hash (reserve->sender_account_details,
183 : &h_full_paytos[i]);
184 60 : TALER_full_payto_normalize_and_hash (reserve->sender_account_details,
185 : &h_normalized_paytos[i]);
186 60 : notify_s[i] = compute_notify_on_reserve (reserve->reserve_pub);
187 60 : reserve_pubs[i] = *reserve->reserve_pub;
188 60 : balances[i] = *reserve->balance;
189 60 : execution_times[i] = reserve->execution_time;
190 60 : payto_uris[i] = reserve->sender_account_details.full_payto;
191 60 : wire_references[i] = reserve->wire_reference;
192 : }
193 135 : for (unsigned int i = 0; i<klen; i++)
194 : {
195 20 : const struct TALER_EXCHANGEDB_KycauthInInfo *ka = &batch->kycauths[i];
196 :
197 20 : TALER_full_payto_hash (ka->sender_account_details,
198 : &ka_h_full_paytos[i]);
199 20 : TALER_full_payto_normalize_and_hash (ka->sender_account_details,
200 : &ka_h_normalized_paytos[i]);
201 20 : ka_notify_s[i] = compute_notify_on_kycauth (&ka_h_normalized_paytos[i]);
202 20 : ka_account_pubs[i] = *ka->account_pub;
203 20 : ka_balances[i] = *ka->balance;
204 20 : ka_execution_times[i] = ka->execution_time;
205 20 : ka_payto_uris[i] = ka->sender_account_details.full_payto;
206 20 : ka_wire_references[i] = ka->wire_reference;
207 : }
208 115 : for (unsigned int i = 0; i<wlen; i++)
209 : {
210 0 : const struct TALER_EXCHANGEDB_WadInInfo *wad = &batch->wads[i];
211 :
212 0 : wad_ids[i] = *wad->wad_id;
213 0 : wad_origin_exchange_urls[i] = wad->origin_exchange_url;
214 0 : wad_balances[i] = *wad->balance;
215 0 : wad_execution_times[i] = wad->execution_time;
216 : }
217 :
218 115 : PREPARE (pg,
219 : "do_import_credits",
220 : "SELECT"
221 : " out_duplicate"
222 : " FROM exchange_do_import_credits"
223 : " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17"
224 : " ,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28);");
225 : {
226 115 : struct GNUNET_PQ_QueryParam params[] = {
227 115 : GNUNET_PQ_query_param_string (batch->exchange_account_name),
228 115 : GNUNET_PQ_query_param_timestamp (&gc),
229 115 : GNUNET_PQ_query_param_timestamp (&reserve_expiration),
230 : /* reserve transfers */
231 115 : GNUNET_PQ_query_param_array_auto_from_type (rlen,
232 : reserve_pubs,
233 : pg->conn),
234 115 : GNUNET_PQ_query_param_array_uint64 (rlen,
235 : wire_references,
236 : pg->conn),
237 115 : TALER_PQ_query_param_array_amount (rlen,
238 : balances,
239 : pg->conn),
240 115 : GNUNET_PQ_query_param_array_timestamp (rlen,
241 : execution_times,
242 : pg->conn),
243 115 : GNUNET_PQ_query_param_array_auto_from_type (rlen,
244 : h_full_paytos,
245 : pg->conn),
246 115 : GNUNET_PQ_query_param_array_auto_from_type (rlen,
247 : h_normalized_paytos,
248 : pg->conn),
249 115 : GNUNET_PQ_query_param_array_ptrs_string (rlen,
250 : payto_uris,
251 : pg->conn),
252 115 : GNUNET_PQ_query_param_array_ptrs_string (rlen,
253 : (const char **) notify_s,
254 : pg->conn),
255 : /* KYC authentication transfers */
256 115 : GNUNET_PQ_query_param_array_auto_from_type (klen,
257 : ka_account_pubs,
258 : pg->conn),
259 115 : GNUNET_PQ_query_param_array_uint64 (klen,
260 : ka_wire_references,
261 : pg->conn),
262 115 : TALER_PQ_query_param_array_amount (klen,
263 : ka_balances,
264 : pg->conn),
265 115 : GNUNET_PQ_query_param_array_timestamp (klen,
266 : ka_execution_times,
267 : pg->conn),
268 115 : GNUNET_PQ_query_param_array_auto_from_type (klen,
269 : ka_h_full_paytos,
270 : pg->conn),
271 115 : GNUNET_PQ_query_param_array_auto_from_type (klen,
272 : ka_h_normalized_paytos,
273 : pg->conn),
274 115 : GNUNET_PQ_query_param_array_ptrs_string (klen,
275 : ka_payto_uris,
276 : pg->conn),
277 115 : GNUNET_PQ_query_param_array_ptrs_string (klen,
278 : (const char **) ka_notify_s,
279 : pg->conn),
280 : /* WAD transfers */
281 115 : GNUNET_PQ_query_param_array_auto_from_type (wlen,
282 : wad_ids,
283 : pg->conn),
284 115 : GNUNET_PQ_query_param_array_ptrs_string (wlen,
285 : wad_origin_exchange_urls,
286 : pg->conn),
287 115 : TALER_PQ_query_param_array_amount (wlen,
288 : wad_balances,
289 : pg->conn),
290 115 : GNUNET_PQ_query_param_array_timestamp (wlen,
291 : wad_execution_times,
292 : pg->conn),
293 : /* shard bookkeeping */
294 115 : GNUNET_PQ_query_param_string (batch->job_name),
295 115 : GNUNET_PQ_query_param_uint64 (&batch->shard_start),
296 115 : GNUNET_PQ_query_param_uint64 (&batch->shard_end),
297 115 : GNUNET_PQ_query_param_uint64 (&batch->progress_row),
298 115 : GNUNET_PQ_query_param_absolute_time (&lease_until),
299 : GNUNET_PQ_query_param_end
300 : };
301 115 : struct Context ctx = {
302 : .transaction_duplicates = transaction_duplicates,
303 : .length = rlen,
304 : .status = GNUNET_OK
305 : };
306 :
307 115 : qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
308 : "do_import_credits",
309 : params,
310 : &helper_cb,
311 : &ctx);
312 115 : GNUNET_PQ_cleanup_query_params_closures (params);
313 115 : if ( (qs >= 0) &&
314 115 : (GNUNET_OK != ctx.status) )
315 0 : qs = GNUNET_DB_STATUS_HARD_ERROR;
316 115 : if (qs < 0)
317 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
318 : "Failed to import credit batch (%d)\n",
319 : qs);
320 : }
321 :
322 175 : for (unsigned int i = 0; i<rlen; i++)
323 60 : GNUNET_free (notify_s[i]);
324 135 : for (unsigned int i = 0; i<klen; i++)
325 20 : GNUNET_free (ka_notify_s[i]);
326 115 : if (qs < 0)
327 0 : return qs;
328 175 : for (unsigned int i = 0; i<rlen; i++)
329 : {
330 60 : if (transaction_duplicates[i])
331 2 : dups++;
332 60 : reserve_results[i] = transaction_duplicates[i]
333 : ? GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
334 60 : : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
335 : }
336 115 : GNUNET_PQ_event_do_poll (pg->conn);
337 115 : if (0 != dups)
338 2 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
339 : "%u/%u duplicates among incoming transactions. Try increasing WIREWATCH_IDLE_SLEEP_INTERVAL in the [exchange] configuration section (if this happens a lot).\n",
340 : dups,
341 : rlen);
342 115 : return qs;
343 : }
|