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 "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 4 : 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 4 : deposits = json_array ();
109 4 : GNUNET_assert (NULL != deposits);
110 4 : hash_context = GNUNET_CRYPTO_hash_context_start ();
111 4 : for (const struct AggregatedDepositDetail *wdd_pos = wdd_head;
112 21 : NULL != wdd_pos;
113 17 : wdd_pos = wdd_pos->next)
114 : {
115 17 : 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 17 : if (0 !=
122 17 : json_array_append_new (
123 : deposits,
124 17 : 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 0 : json_decref (deposits);
142 0 : GNUNET_CRYPTO_hash_context_abort (hash_context);
143 0 : 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 4 : GNUNET_CRYPTO_hash_context_finish (hash_context,
150 : &h_details);
151 : {
152 : enum TALER_ErrorCode ec;
153 :
154 4 : if (TALER_EC_NONE !=
155 4 : (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 0 : json_decref (deposits);
166 0 : return TALER_MHD_reply_with_ec (connection,
167 : ec,
168 : NULL);
169 : }
170 : }
171 :
172 4 : TALER_full_payto_hash (payto_uri,
173 : &h_payto);
174 4 : 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 0 : add_refunds (void *cls,
271 : const struct TALER_Amount *amount_with_fee)
272 :
273 : {
274 0 : struct TALER_Amount *total = cls;
275 :
276 0 : if (0 >
277 0 : TALER_amount_add (total,
278 : total,
279 : amount_with_fee))
280 : {
281 0 : GNUNET_break (0);
282 0 : return GNUNET_SYSERR;
283 : }
284 0 : 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 17 : 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 17 : 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 17 : if (GNUNET_SYSERR == ctx->is_valid)
328 0 : return;
329 17 : GNUNET_assert (GNUNET_OK ==
330 : TALER_amount_set_zero (deposit_value->currency,
331 : &total_refunds));
332 17 : 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 17 : if (qs < 0)
339 : {
340 0 : GNUNET_break (0);
341 0 : ctx->is_valid = GNUNET_SYSERR;
342 0 : return;
343 : }
344 17 : if (1 ==
345 17 : TALER_amount_cmp (&total_refunds,
346 : deposit_value))
347 : {
348 : /* Refunds exceeded total deposit? not OK! */
349 0 : GNUNET_break (0);
350 0 : ctx->is_valid = GNUNET_SYSERR;
351 0 : return;
352 : }
353 17 : if (0 ==
354 17 : 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 0 : GNUNET_assert (GNUNET_OK ==
361 : TALER_amount_set_zero (deposit_value->currency,
362 : &dval));
363 0 : 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 17 : GNUNET_assert (0 <
372 : TALER_amount_subtract (&dval,
373 : deposit_value,
374 : &total_refunds));
375 17 : if (-1 ==
376 17 : 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 0 : dfee = dval;
383 : }
384 : else
385 : {
386 : /* Partial refund, deposit fee remains */
387 17 : dfee = *deposit_fee;
388 : }
389 : }
390 :
391 17 : if (GNUNET_NO == ctx->is_valid)
392 : {
393 : /* First one we encounter, setup general information in 'ctx' */
394 4 : ctx->merchant_pub = *merchant_pub;
395 4 : ctx->payto_uri.full_payto = GNUNET_strdup (account_payto_uri.full_payto);
396 4 : ctx->exec_time = exec_time;
397 4 : ctx->is_valid = GNUNET_YES;
398 4 : if (0 >
399 4 : TALER_amount_subtract (&ctx->total,
400 : &dval,
401 : &dfee))
402 : {
403 0 : GNUNET_break (0);
404 0 : ctx->is_valid = GNUNET_SYSERR;
405 0 : 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 13 : if ( (0 != GNUNET_memcmp (&ctx->merchant_pub,
415 13 : merchant_pub)) ||
416 13 : (0 != TALER_full_payto_cmp (account_payto_uri,
417 : ctx->payto_uri)) )
418 : {
419 0 : GNUNET_break (0);
420 0 : ctx->is_valid = GNUNET_SYSERR;
421 0 : return;
422 : }
423 13 : if (0 >
424 13 : TALER_amount_subtract (&delta,
425 : &dval,
426 : &dfee))
427 : {
428 0 : GNUNET_break (0);
429 0 : ctx->is_valid = GNUNET_SYSERR;
430 0 : return;
431 : }
432 13 : if (0 >
433 13 : TALER_amount_add (&ctx->total,
434 13 : &ctx->total,
435 : &delta))
436 : {
437 0 : GNUNET_break (0);
438 0 : ctx->is_valid = GNUNET_SYSERR;
439 0 : return;
440 : }
441 : }
442 :
443 : {
444 : struct AggregatedDepositDetail *wdd;
445 :
446 17 : wdd = GNUNET_new (struct AggregatedDepositDetail);
447 17 : wdd->deposit_value = dval;
448 17 : wdd->deposit_fee = dfee;
449 17 : wdd->refund_total = total_refunds;
450 17 : wdd->h_contract_terms = *h_contract_terms;
451 17 : wdd->coin_pub = *coin_pub;
452 17 : 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 12 : free_ctx (struct WtidTransactionContext *ctx)
466 : {
467 : struct AggregatedDepositDetail *wdd;
468 :
469 29 : while (NULL != (wdd = ctx->wdd_head))
470 : {
471 17 : GNUNET_CONTAINER_DLL_remove (ctx->wdd_head,
472 : ctx->wdd_tail,
473 : wdd);
474 17 : GNUNET_free (wdd);
475 : }
476 12 : GNUNET_free (ctx->payto_uri.full_payto);
477 12 : }
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 6 : get_transfer_deposits (void *cls,
498 : struct MHD_Connection *connection,
499 : MHD_RESULT *mhd_ret)
500 : {
501 6 : 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 6 : free_ctx (ctx);
510 6 : qs = TEH_plugin->lookup_wire_transfer (TEH_plugin->cls,
511 6 : &ctx->wtid,
512 : &handle_deposit_data,
513 : ctx);
514 6 : if (0 > qs)
515 : {
516 0 : if (GNUNET_DB_STATUS_HARD_ERROR == qs)
517 : {
518 0 : GNUNET_break (0);
519 0 : *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 0 : return qs;
525 : }
526 6 : if (GNUNET_SYSERR == ctx->is_valid)
527 : {
528 0 : GNUNET_break (0);
529 0 : *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 0 : return GNUNET_DB_STATUS_HARD_ERROR;
534 : }
535 6 : if (GNUNET_NO == ctx->is_valid)
536 : {
537 2 : *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 2 : return GNUNET_DB_STATUS_HARD_ERROR;
542 : }
543 : {
544 : char *wire_method;
545 : uint64_t rowid;
546 :
547 4 : wire_method = TALER_payto_get_method (ctx->payto_uri.full_payto);
548 4 : if (NULL == wire_method)
549 : {
550 0 : GNUNET_break (0);
551 0 : *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 0 : return GNUNET_DB_STATUS_HARD_ERROR;
556 : }
557 4 : 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 4 : GNUNET_free (wire_method);
566 : }
567 4 : if (0 >= qs)
568 : {
569 0 : if ( (GNUNET_DB_STATUS_HARD_ERROR == qs) ||
570 : (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) )
571 : {
572 0 : GNUNET_break (0);
573 0 : *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 0 : return qs;
579 : }
580 4 : if (0 >
581 4 : TALER_amount_subtract (&ctx->total,
582 4 : &ctx->total,
583 4 : &ctx->fees.wire))
584 : {
585 0 : GNUNET_break (0);
586 0 : *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 0 : return GNUNET_DB_STATUS_HARD_ERROR;
591 : }
592 4 : return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
593 : }
594 :
595 :
596 : MHD_RESULT
597 6 : 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 6 : memset (&ctx,
604 : 0,
605 : sizeof (ctx));
606 6 : if (GNUNET_OK !=
607 6 : GNUNET_STRINGS_string_to_data (args[0],
608 : strlen (args[0]),
609 : &ctx.wtid,
610 : sizeof (ctx.wtid)))
611 : {
612 0 : GNUNET_break_op (0);
613 0 : 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 6 : if (GNUNET_OK !=
619 6 : 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 2 : free_ctx (&ctx);
627 2 : return mhd_ret;
628 : }
629 8 : 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 4 : ctx.wdd_head);
636 4 : free_ctx (&ctx);
637 4 : return mhd_ret;
638 : }
639 :
640 :
641 : /* end of taler-exchange-httpd_transfers_get.c */
|