Line data Source code
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 "platform.h"
22 : #include <gnunet/gnunet_util_lib.h>
23 : #include <jansson.h>
24 : #include <microhttpd.h>
25 : #include <pthread.h>
26 : #include "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_json_lib.h"
31 : #include "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.
63 : */
64 : struct TALER_Amount deposit_value;
65 :
66 : /**
67 : * Fees charged by the exchange for the deposit of this coin.
68 : */
69 : struct TALER_Amount deposit_fee;
70 : };
71 :
72 :
73 : /**
74 : * A merchant asked for transaction details about a wire transfer.
75 : * Provide them. Generates the 200 reply.
76 : *
77 : * @param connection connection to the client
78 : * @param total total amount that was transferred
79 : * @param merchant_pub public key of the merchant
80 : * @param payto_uri destination account
81 : * @param wire_fee wire fee that was charged
82 : * @param exec_time execution time of the wire transfer
83 : * @param wdd_head linked list with details about the combined deposits
84 : * @return MHD result code
85 : */
86 : static MHD_RESULT
87 0 : reply_transfer_details (struct MHD_Connection *connection,
88 : const struct TALER_Amount *total,
89 : const struct TALER_MerchantPublicKeyP *merchant_pub,
90 : const char *payto_uri,
91 : const struct TALER_Amount *wire_fee,
92 : struct GNUNET_TIME_Timestamp exec_time,
93 : const struct AggregatedDepositDetail *wdd_head)
94 : {
95 : json_t *deposits;
96 : struct GNUNET_HashContext *hash_context;
97 : struct GNUNET_HashCode h_details;
98 : struct TALER_ExchangePublicKeyP pub;
99 : struct TALER_ExchangeSignatureP sig;
100 : struct TALER_PaytoHashP h_payto;
101 :
102 0 : deposits = json_array ();
103 0 : GNUNET_assert (NULL != deposits);
104 0 : hash_context = GNUNET_CRYPTO_hash_context_start ();
105 0 : for (const struct AggregatedDepositDetail *wdd_pos = wdd_head;
106 : NULL != wdd_pos;
107 0 : wdd_pos = wdd_pos->next)
108 : {
109 0 : TALER_exchange_online_wire_deposit_append (hash_context,
110 : &wdd_pos->h_contract_terms,
111 : exec_time,
112 : &wdd_pos->coin_pub,
113 : &wdd_pos->deposit_value,
114 : &wdd_pos->deposit_fee);
115 0 : if (0 !=
116 0 : json_array_append_new (
117 : deposits,
118 0 : GNUNET_JSON_PACK (
119 : GNUNET_JSON_pack_data_auto ("h_contract_terms",
120 : &wdd_pos->h_contract_terms),
121 : GNUNET_JSON_pack_data_auto ("coin_pub",
122 : &wdd_pos->coin_pub),
123 : TALER_JSON_pack_amount ("deposit_value",
124 : &wdd_pos->deposit_value),
125 : TALER_JSON_pack_amount ("deposit_fee",
126 : &wdd_pos->deposit_fee))))
127 : {
128 0 : json_decref (deposits);
129 0 : GNUNET_CRYPTO_hash_context_abort (hash_context);
130 0 : return TALER_MHD_reply_with_error (connection,
131 : MHD_HTTP_INTERNAL_SERVER_ERROR,
132 : TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
133 : "json_array_append_new() failed");
134 : }
135 : }
136 0 : GNUNET_CRYPTO_hash_context_finish (hash_context,
137 : &h_details);
138 : {
139 : enum TALER_ErrorCode ec;
140 :
141 0 : if (TALER_EC_NONE !=
142 0 : (ec = TALER_exchange_online_wire_deposit_sign (
143 : &TEH_keys_exchange_sign_,
144 : total,
145 : wire_fee,
146 : merchant_pub,
147 : payto_uri,
148 : &h_details,
149 : &pub,
150 : &sig)))
151 : {
152 0 : json_decref (deposits);
153 0 : return TALER_MHD_reply_with_ec (connection,
154 : ec,
155 : NULL);
156 : }
157 : }
158 :
159 0 : TALER_payto_hash (payto_uri,
160 : &h_payto);
161 0 : return TALER_MHD_REPLY_JSON_PACK (
162 : connection,
163 : MHD_HTTP_OK,
164 : TALER_JSON_pack_amount ("total",
165 : total),
166 : TALER_JSON_pack_amount ("wire_fee",
167 : wire_fee),
168 : GNUNET_JSON_pack_data_auto ("merchant_pub",
169 : merchant_pub),
170 : GNUNET_JSON_pack_data_auto ("h_payto",
171 : &h_payto),
172 : GNUNET_JSON_pack_timestamp ("execution_time",
173 : exec_time),
174 : GNUNET_JSON_pack_array_steal ("deposits",
175 : deposits),
176 : GNUNET_JSON_pack_data_auto ("exchange_sig",
177 : &sig),
178 : GNUNET_JSON_pack_data_auto ("exchange_pub",
179 : &pub));
180 : }
181 :
182 :
183 : /**
184 : * Closure for #handle_transaction_data.
185 : */
186 : struct WtidTransactionContext
187 : {
188 :
189 : /**
190 : * Identifier of the wire transfer to track.
191 : */
192 : struct TALER_WireTransferIdentifierRawP wtid;
193 :
194 : /**
195 : * Total amount of the wire transfer, as calculated by
196 : * summing up the individual amounts. To be rounded down
197 : * to calculate the real transfer amount at the end.
198 : * Only valid if @e is_valid is #GNUNET_YES.
199 : */
200 : struct TALER_Amount total;
201 :
202 : /**
203 : * Public key of the merchant, only valid if @e is_valid
204 : * is #GNUNET_YES.
205 : */
206 : struct TALER_MerchantPublicKeyP merchant_pub;
207 :
208 : /**
209 : * Wire fees applicable at @e exec_time.
210 : */
211 : struct TALER_WireFeeSet fees;
212 :
213 : /**
214 : * Execution time of the wire transfer
215 : */
216 : struct GNUNET_TIME_Timestamp exec_time;
217 :
218 : /**
219 : * Head of DLL with deposit details for transfers GET response.
220 : */
221 : struct AggregatedDepositDetail *wdd_head;
222 :
223 : /**
224 : * Tail of DLL with deposit details for transfers GET response.
225 : */
226 : struct AggregatedDepositDetail *wdd_tail;
227 :
228 : /**
229 : * Where were the funds wired?
230 : */
231 : char *payto_uri;
232 :
233 : /**
234 : * JSON array with details about the individual deposits.
235 : */
236 : json_t *deposits;
237 :
238 : /**
239 : * Initially #GNUNET_NO, if we found no deposits so far. Set to
240 : * #GNUNET_YES if we got transaction data, and the database replies
241 : * remained consistent with respect to @e merchant_pub and @e h_wire
242 : * (as they should). Set to #GNUNET_SYSERR if we encountered an
243 : * internal error.
244 : */
245 : enum GNUNET_GenericReturnValue is_valid;
246 :
247 : };
248 :
249 :
250 : /**
251 : * Function called with the results of the lookup of the individual deposits
252 : * that were aggregated for the given wire transfer.
253 : *
254 : * @param cls our context for transmission
255 : * @param rowid which row in the DB is the information from (for diagnostics), ignored
256 : * @param merchant_pub public key of the merchant (should be same for all callbacks with the same @e cls)
257 : * @param account_payto_uri where the funds were sent
258 : * @param h_payto hash over @a account_payto_uri as it is in the DB
259 : * @param exec_time execution time of the wire transfer (should be same for all callbacks with the same @e cls)
260 : * @param h_contract_terms which proposal was this payment about
261 : * @param denom_pub denomination public key of the @a coin_pub (ignored)
262 : * @param coin_pub which public key was this payment about
263 : * @param deposit_value amount contributed by this coin in total
264 : * @param deposit_fee deposit fee charged by exchange for this coin
265 : */
266 : static void
267 0 : handle_deposit_data (void *cls,
268 : uint64_t rowid,
269 : const struct TALER_MerchantPublicKeyP *merchant_pub,
270 : const char *account_payto_uri,
271 : const struct TALER_PaytoHashP *h_payto,
272 : struct GNUNET_TIME_Timestamp exec_time,
273 : const struct TALER_PrivateContractHashP *h_contract_terms,
274 : const struct TALER_DenominationPublicKey *denom_pub,
275 : const struct TALER_CoinSpendPublicKeyP *coin_pub,
276 : const struct TALER_Amount *deposit_value,
277 : const struct TALER_Amount *deposit_fee)
278 : {
279 0 : struct WtidTransactionContext *ctx = cls;
280 :
281 : (void) rowid;
282 : (void) denom_pub;
283 : (void) h_payto;
284 0 : if (GNUNET_SYSERR == ctx->is_valid)
285 0 : return;
286 0 : if (GNUNET_NO == ctx->is_valid)
287 : {
288 : /* First one we encounter, setup general information in 'ctx' */
289 0 : ctx->merchant_pub = *merchant_pub;
290 0 : ctx->payto_uri = GNUNET_strdup (account_payto_uri);
291 0 : ctx->exec_time = exec_time;
292 0 : ctx->is_valid = GNUNET_YES;
293 0 : if (0 >
294 0 : TALER_amount_subtract (&ctx->total,
295 : deposit_value,
296 : deposit_fee))
297 : {
298 0 : GNUNET_break (0);
299 0 : ctx->is_valid = GNUNET_SYSERR;
300 0 : return;
301 : }
302 : }
303 : else
304 : {
305 : struct TALER_Amount delta;
306 :
307 : /* Subsequent data, check general information matches that in 'ctx';
308 : (it should, otherwise the deposits should not have been aggregated) */
309 0 : if ( (0 != GNUNET_memcmp (&ctx->merchant_pub,
310 0 : merchant_pub)) ||
311 0 : (0 != strcmp (account_payto_uri,
312 0 : ctx->payto_uri)) )
313 : {
314 0 : GNUNET_break (0);
315 0 : ctx->is_valid = GNUNET_SYSERR;
316 0 : return;
317 : }
318 0 : if (0 >
319 0 : TALER_amount_subtract (&delta,
320 : deposit_value,
321 : deposit_fee))
322 : {
323 0 : GNUNET_break (0);
324 0 : ctx->is_valid = GNUNET_SYSERR;
325 0 : return;
326 : }
327 0 : if (0 >
328 0 : TALER_amount_add (&ctx->total,
329 0 : &ctx->total,
330 : &delta))
331 : {
332 0 : GNUNET_break (0);
333 0 : ctx->is_valid = GNUNET_SYSERR;
334 0 : return;
335 : }
336 : }
337 :
338 : {
339 : struct AggregatedDepositDetail *wdd;
340 :
341 0 : wdd = GNUNET_new (struct AggregatedDepositDetail);
342 0 : wdd->deposit_value = *deposit_value;
343 0 : wdd->deposit_fee = *deposit_fee;
344 0 : wdd->h_contract_terms = *h_contract_terms;
345 0 : wdd->coin_pub = *coin_pub;
346 0 : GNUNET_CONTAINER_DLL_insert (ctx->wdd_head,
347 : ctx->wdd_tail,
348 : wdd);
349 : }
350 : }
351 :
352 :
353 : /**
354 : * Free data structure reachable from @a ctx, but not @a ctx itself.
355 : *
356 : * @param ctx context to free
357 : */
358 : static void
359 0 : free_ctx (struct WtidTransactionContext *ctx)
360 : {
361 : struct AggregatedDepositDetail *wdd;
362 :
363 0 : while (NULL != (wdd = ctx->wdd_head))
364 : {
365 0 : GNUNET_CONTAINER_DLL_remove (ctx->wdd_head,
366 : ctx->wdd_tail,
367 : wdd);
368 0 : GNUNET_free (wdd);
369 : }
370 0 : GNUNET_free (ctx->payto_uri);
371 0 : }
372 :
373 :
374 : /**
375 : * Execute a "/transfers" GET operation. Returns the deposit details of the
376 : * deposits that were aggregated to create the given wire transfer.
377 : *
378 : * If it returns a non-error code, the transaction logic MUST
379 : * NOT queue a MHD response. IF it returns an hard error, the
380 : * transaction logic MUST queue a MHD response and set @a mhd_ret. IF
381 : * it returns the soft error code, the function MAY be called again to
382 : * retry and MUST not queue a MHD response.
383 : *
384 : * @param cls closure
385 : * @param connection MHD request which triggered the transaction
386 : * @param[out] mhd_ret set to MHD response status for @a connection,
387 : * if transaction failed (!)
388 : * @return transaction status
389 : */
390 : static enum GNUNET_DB_QueryStatus
391 0 : get_transfer_deposits (void *cls,
392 : struct MHD_Connection *connection,
393 : MHD_RESULT *mhd_ret)
394 : {
395 0 : struct WtidTransactionContext *ctx = cls;
396 : enum GNUNET_DB_QueryStatus qs;
397 : struct GNUNET_TIME_Timestamp wire_fee_start_date;
398 : struct GNUNET_TIME_Timestamp wire_fee_end_date;
399 : struct TALER_MasterSignatureP wire_fee_master_sig;
400 :
401 : /* resetting to NULL/0 in case transaction was repeated after
402 : serialization failure */
403 0 : free_ctx (ctx);
404 0 : qs = TEH_plugin->lookup_wire_transfer (TEH_plugin->cls,
405 0 : &ctx->wtid,
406 : &handle_deposit_data,
407 : ctx);
408 0 : if (0 > qs)
409 : {
410 0 : if (GNUNET_DB_STATUS_HARD_ERROR == qs)
411 : {
412 0 : GNUNET_break (0);
413 0 : *mhd_ret = TALER_MHD_reply_with_error (connection,
414 : MHD_HTTP_INTERNAL_SERVER_ERROR,
415 : TALER_EC_GENERIC_DB_FETCH_FAILED,
416 : "wire transfer");
417 : }
418 0 : return qs;
419 : }
420 0 : if (GNUNET_SYSERR == ctx->is_valid)
421 : {
422 0 : GNUNET_break (0);
423 0 : *mhd_ret = TALER_MHD_reply_with_error (connection,
424 : MHD_HTTP_INTERNAL_SERVER_ERROR,
425 : TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
426 : "wire history malformed");
427 0 : return GNUNET_DB_STATUS_HARD_ERROR;
428 : }
429 0 : if (GNUNET_NO == ctx->is_valid)
430 : {
431 0 : *mhd_ret = TALER_MHD_reply_with_error (connection,
432 : MHD_HTTP_NOT_FOUND,
433 : TALER_EC_EXCHANGE_TRANSFERS_GET_WTID_NOT_FOUND,
434 : NULL);
435 0 : return GNUNET_DB_STATUS_HARD_ERROR;
436 : }
437 : {
438 : char *wire_method;
439 :
440 0 : wire_method = TALER_payto_get_method (ctx->payto_uri);
441 0 : if (NULL == wire_method)
442 : {
443 0 : GNUNET_break (0);
444 0 : *mhd_ret = TALER_MHD_reply_with_error (connection,
445 : MHD_HTTP_INTERNAL_SERVER_ERROR,
446 : TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
447 : "payto:// without wire method encountered");
448 0 : return GNUNET_DB_STATUS_HARD_ERROR;
449 : }
450 0 : qs = TEH_plugin->get_wire_fee (TEH_plugin->cls,
451 : wire_method,
452 : ctx->exec_time,
453 : &wire_fee_start_date,
454 : &wire_fee_end_date,
455 : &ctx->fees,
456 : &wire_fee_master_sig);
457 0 : GNUNET_free (wire_method);
458 : }
459 0 : if (0 >= qs)
460 : {
461 0 : if ( (GNUNET_DB_STATUS_HARD_ERROR == qs) ||
462 : (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) )
463 : {
464 0 : GNUNET_break (0);
465 0 : *mhd_ret = TALER_MHD_reply_with_error (connection,
466 : MHD_HTTP_INTERNAL_SERVER_ERROR,
467 : TALER_EC_EXCHANGE_TRANSFERS_GET_WIRE_FEE_NOT_FOUND,
468 : NULL);
469 : }
470 0 : return qs;
471 : }
472 0 : if (0 >
473 0 : TALER_amount_subtract (&ctx->total,
474 0 : &ctx->total,
475 0 : &ctx->fees.wire))
476 : {
477 0 : GNUNET_break (0);
478 0 : *mhd_ret = TALER_MHD_reply_with_error (connection,
479 : MHD_HTTP_INTERNAL_SERVER_ERROR,
480 : TALER_EC_EXCHANGE_TRANSFERS_GET_WIRE_FEE_INCONSISTENT,
481 : NULL);
482 0 : return GNUNET_DB_STATUS_HARD_ERROR;
483 : }
484 0 : return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
485 : }
486 :
487 :
488 : MHD_RESULT
489 0 : TEH_handler_transfers_get (struct TEH_RequestContext *rc,
490 : const char *const args[1])
491 : {
492 : struct WtidTransactionContext ctx;
493 : MHD_RESULT mhd_ret;
494 :
495 0 : memset (&ctx,
496 : 0,
497 : sizeof (ctx));
498 0 : if (GNUNET_OK !=
499 0 : GNUNET_STRINGS_string_to_data (args[0],
500 : strlen (args[0]),
501 : &ctx.wtid,
502 : sizeof (ctx.wtid)))
503 : {
504 0 : GNUNET_break_op (0);
505 0 : return TALER_MHD_reply_with_error (rc->connection,
506 : MHD_HTTP_BAD_REQUEST,
507 : TALER_EC_EXCHANGE_TRANSFERS_GET_WTID_MALFORMED,
508 : args[0]);
509 : }
510 0 : if (GNUNET_OK !=
511 0 : TEH_DB_run_transaction (rc->connection,
512 : "run transfers GET",
513 : TEH_MT_REQUEST_OTHER,
514 : &mhd_ret,
515 : &get_transfer_deposits,
516 : &ctx))
517 : {
518 0 : free_ctx (&ctx);
519 0 : return mhd_ret;
520 : }
521 0 : mhd_ret = reply_transfer_details (rc->connection,
522 : &ctx.total,
523 : &ctx.merchant_pub,
524 0 : ctx.payto_uri,
525 : &ctx.fees.wire,
526 : ctx.exec_time,
527 0 : ctx.wdd_head);
528 0 : free_ctx (&ctx);
529 0 : return mhd_ret;
530 : }
531 :
532 :
533 : /* end of taler-exchange-httpd_transfers_get.c */
|