Line data Source code
1 : /*
2 : This file is part of TALER
3 : (C) 2014-2022 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 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 taler-merchant-httpd_private-post-transfers.c
18 : * @brief implement API for registering wire transfers
19 : * @author Marcello Stanisci
20 : * @author Christian Grothoff
21 : */
22 : #include "platform.h"
23 : #include <jansson.h>
24 : #include <taler/taler_signatures.h>
25 : #include <taler/taler_json_lib.h>
26 : #include "taler-merchant-httpd_auditors.h"
27 : #include "taler-merchant-httpd_exchanges.h"
28 : #include "taler-merchant-httpd_helper.h"
29 : #include "taler-merchant-httpd_private-post-transfers.h"
30 :
31 :
32 : /**
33 : * How long to wait before giving up processing with the exchange?
34 : */
35 : #define TRANSFER_GENERIC_TIMEOUT (GNUNET_TIME_relative_multiply ( \
36 : GNUNET_TIME_UNIT_SECONDS, \
37 : 15))
38 :
39 : /**
40 : * How often do we retry the simple INSERT database transaction?
41 : */
42 : #define MAX_RETRIES 3
43 :
44 : /**
45 : * Context used for handing POST /private/transfers requests.
46 : */
47 : struct PostTransfersContext
48 : {
49 :
50 : /**
51 : * Kept in a DLL.
52 : */
53 : struct PostTransfersContext *next;
54 :
55 : /**
56 : * Kept in a DLL.
57 : */
58 : struct PostTransfersContext *prev;
59 :
60 : /**
61 : * Argument for the /wire/transfers request.
62 : */
63 : struct TALER_WireTransferIdentifierRawP wtid;
64 :
65 : /**
66 : * Amount of the wire transfer.
67 : */
68 : struct TALER_Amount amount;
69 :
70 : /**
71 : * URL of the exchange.
72 : */
73 : const char *exchange_url;
74 :
75 : /**
76 : * payto:// URI used for the transfer.
77 : */
78 : const char *payto_uri;
79 :
80 : /**
81 : * Master public key of the exchange at @e exchange_url.
82 : */
83 : struct TALER_MasterPublicKeyP master_pub;
84 :
85 : /**
86 : * Handle for the /wire/transfers request.
87 : */
88 : struct TALER_EXCHANGE_TransfersGetHandle *wdh;
89 :
90 : /**
91 : * For which merchant instance is this tracking request?
92 : */
93 : struct TMH_HandlerContext *hc;
94 :
95 : /**
96 : * HTTP connection we are handling.
97 : */
98 : struct MHD_Connection *connection;
99 :
100 : /**
101 : * Response to return upon resume.
102 : */
103 : struct MHD_Response *response;
104 :
105 : /**
106 : * Handle for operation to lookup /keys (and auditors) from
107 : * the exchange used for this transaction; NULL if no operation is
108 : * pending.
109 : */
110 : struct TMH_EXCHANGES_FindOperation *fo;
111 :
112 : /**
113 : * Task run on timeout.
114 : */
115 : struct GNUNET_SCHEDULER_Task *timeout_task;
116 :
117 : /**
118 : * Pointer to the detail that we are currently
119 : * checking in #check_transfer().
120 : */
121 : const struct TALER_TrackTransferDetails *current_detail;
122 :
123 : /**
124 : * Which transaction detail are we currently looking at?
125 : */
126 : unsigned int current_offset;
127 :
128 : /**
129 : * Response code to return.
130 : */
131 : unsigned int response_code;
132 :
133 : /**
134 : * #GNUNET_NO if we did not find a matching coin.
135 : * #GNUNET_SYSERR if we found a matching coin, but the amounts do not match.
136 : * #GNUNET_OK if we did find a matching coin.
137 : */
138 : enum GNUNET_GenericReturnValue check_transfer_result;
139 :
140 : /**
141 : * Did we suspend @a connection and are thus in
142 : * the #ptc_head DLL (#GNUNET_YES). Set to
143 : * #GNUNET_NO if we are not suspended, and to
144 : * #GNUNET_SYSERR if we should close the connection
145 : * without a response due to shutdown.
146 : */
147 : enum GNUNET_GenericReturnValue suspended;
148 :
149 : /**
150 : * Should we retry the transaction due to a serialization error?
151 : */
152 : bool soft_retry;
153 :
154 : /**
155 : * Did we just download the exchange reply?
156 : */
157 : bool downloaded;
158 :
159 : };
160 :
161 :
162 : /**
163 : * Head of list of suspended requests.
164 : */
165 : static struct PostTransfersContext *ptc_head;
166 :
167 : /**
168 : * Tail of list of suspended requests.
169 : */
170 : static struct PostTransfersContext *ptc_tail;
171 :
172 :
173 : void
174 0 : TMH_force_post_transfers_resume ()
175 : {
176 : struct PostTransfersContext *ptc;
177 :
178 0 : while (NULL != (ptc = ptc_head))
179 : {
180 0 : GNUNET_CONTAINER_DLL_remove (ptc_head,
181 : ptc_tail,
182 : ptc);
183 0 : ptc->suspended = GNUNET_SYSERR;
184 0 : MHD_resume_connection (ptc->connection);
185 0 : if (NULL != ptc->timeout_task)
186 : {
187 0 : GNUNET_SCHEDULER_cancel (ptc->timeout_task);
188 0 : ptc->timeout_task = NULL;
189 : }
190 : }
191 0 : }
192 :
193 :
194 : /**
195 : * Resume the given /track/transfer operation and send the given response.
196 : * Stores the response in the @a ptc and signals MHD to resume
197 : * the connection. Also ensures MHD runs immediately.
198 : *
199 : * @param ptc transfer tracking context
200 : * @param response_code response code to use
201 : * @param response response data to send back
202 : */
203 : static void
204 0 : resume_transfer_with_response (struct PostTransfersContext *ptc,
205 : unsigned int response_code,
206 : struct MHD_Response *response)
207 : {
208 0 : ptc->response_code = response_code;
209 0 : ptc->response = response;
210 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
211 : "Resuming POST /transfers handling as exchange interaction is done (%u)\n",
212 : response_code);
213 0 : if (NULL != ptc->timeout_task)
214 : {
215 0 : GNUNET_SCHEDULER_cancel (ptc->timeout_task);
216 0 : ptc->timeout_task = NULL;
217 : }
218 0 : GNUNET_CONTAINER_DLL_remove (ptc_head,
219 : ptc_tail,
220 : ptc);
221 0 : ptc->suspended = GNUNET_NO;
222 0 : MHD_resume_connection (ptc->connection);
223 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
224 0 : }
225 :
226 :
227 : /**
228 : * Resume the given POST /transfers operation with an error.
229 : *
230 : * @param ptc transfer tracking context
231 : * @param response_code response code to use
232 : * @param ec error code to use
233 : * @param hint hint text to provide
234 : */
235 : static void
236 0 : resume_transfer_with_error (struct PostTransfersContext *ptc,
237 : unsigned int response_code,
238 : enum TALER_ErrorCode ec,
239 : const char *hint)
240 : {
241 0 : resume_transfer_with_response (ptc,
242 : response_code,
243 : TALER_MHD_make_error (ec,
244 : hint));
245 0 : }
246 :
247 :
248 : /**
249 : * Custom cleanup routine for a `struct PostTransfersContext`.
250 : *
251 : * @param cls the `struct PostTransfersContext` to clean up.
252 : */
253 : static void
254 0 : transfer_cleanup (void *cls)
255 : {
256 0 : struct PostTransfersContext *ptc = cls;
257 :
258 0 : if (NULL != ptc->fo)
259 : {
260 0 : TMH_EXCHANGES_find_exchange_cancel (ptc->fo);
261 0 : ptc->fo = NULL;
262 : }
263 0 : if (NULL != ptc->timeout_task)
264 : {
265 0 : GNUNET_SCHEDULER_cancel (ptc->timeout_task);
266 0 : ptc->timeout_task = NULL;
267 : }
268 0 : if (NULL != ptc->wdh)
269 : {
270 0 : TALER_EXCHANGE_transfers_get_cancel (ptc->wdh);
271 0 : ptc->wdh = NULL;
272 : }
273 0 : if (NULL != ptc->response)
274 : {
275 0 : MHD_destroy_response (ptc->response);
276 0 : ptc->response = NULL;
277 : }
278 0 : GNUNET_free (ptc);
279 0 : }
280 :
281 :
282 : /**
283 : * This function checks that the information about the coin which
284 : * was paid back by _this_ wire transfer matches what _we_ (the merchant)
285 : * knew about this coin.
286 : *
287 : * @param cls closure with our `struct PostTransfersContext *`
288 : * @param exchange_url URL of the exchange that issued @a coin_pub
289 : * @param amount_with_fee amount the exchange will transfer for this coin
290 : * @param deposit_fee fee the exchange will charge for this coin
291 : * @param refund_fee fee the exchange will charge for refunding this coin
292 : * @param wire_fee paid wire fee
293 : * @param h_wire hash of merchant's wire details
294 : * @param deposit_timestamp when did the exchange receive the deposit
295 : * @param refund_deadline until when are refunds allowed
296 : * @param exchange_sig signature by the exchange
297 : * @param exchange_pub exchange signing key used for @a exchange_sig
298 : */
299 : static void
300 0 : check_transfer (void *cls,
301 : const char *exchange_url,
302 : const struct TALER_Amount *amount_with_fee,
303 : const struct TALER_Amount *deposit_fee,
304 : const struct TALER_Amount *refund_fee,
305 : const struct TALER_Amount *wire_fee,
306 : const struct TALER_MerchantWireHashP *h_wire,
307 : struct GNUNET_TIME_Timestamp deposit_timestamp,
308 : struct GNUNET_TIME_Timestamp refund_deadline,
309 : const struct TALER_ExchangeSignatureP *exchange_sig,
310 : const struct TALER_ExchangePublicKeyP *exchange_pub)
311 : {
312 0 : struct PostTransfersContext *ptc = cls;
313 0 : const struct TALER_TrackTransferDetails *ttd = ptc->current_detail;
314 :
315 0 : if (GNUNET_SYSERR == ptc->check_transfer_result)
316 0 : return; /* already had a serious issue; odd that we're called more than once as well... */
317 0 : if ( (0 != TALER_amount_cmp (amount_with_fee,
318 0 : &ttd->coin_value)) ||
319 0 : (0 != TALER_amount_cmp (deposit_fee,
320 : &ttd->coin_fee)) )
321 : {
322 : /* Disagreement between the exchange and us about how much this
323 : coin is worth! */
324 0 : GNUNET_break_op (0);
325 0 : ptc->check_transfer_result = GNUNET_SYSERR;
326 : /* Build the `TrackTransferConflictDetails` */
327 0 : ptc->response_code = MHD_HTTP_ACCEPTED;
328 : ptc->response
329 0 : = TALER_MHD_MAKE_JSON_PACK (
330 : TALER_JSON_pack_ec (
331 : TALER_EC_MERCHANT_PRIVATE_POST_TRANSFERS_CONFLICTING_REPORTS),
332 : GNUNET_JSON_pack_string ("exchange_url",
333 : exchange_url),
334 : GNUNET_JSON_pack_timestamp ("deposit_timestamp",
335 : deposit_timestamp),
336 : GNUNET_JSON_pack_timestamp ("refund_deadline",
337 : refund_deadline),
338 : GNUNET_JSON_pack_uint64 ("conflict_offset",
339 : ptc->current_offset),
340 : GNUNET_JSON_pack_data_auto ("coin_pub",
341 : &ttd->coin_pub),
342 : GNUNET_JSON_pack_data_auto ("h_wire",
343 : h_wire),
344 : GNUNET_JSON_pack_data_auto ("deposit_exchange_sig",
345 : exchange_sig),
346 : GNUNET_JSON_pack_data_auto ("deposit_exchange_pub",
347 : exchange_pub),
348 : GNUNET_JSON_pack_data_auto ("h_contract_terms",
349 : &ttd->h_contract_terms),
350 : TALER_JSON_pack_amount ("amount_with_fee",
351 : amount_with_fee),
352 : TALER_JSON_pack_amount ("coin_value",
353 : &ttd->coin_value),
354 : TALER_JSON_pack_amount ("coin_fee",
355 : &ttd->coin_fee),
356 : TALER_JSON_pack_amount ("deposit_fee",
357 : deposit_fee));
358 0 : return;
359 : }
360 0 : ptc->check_transfer_result = GNUNET_OK;
361 : }
362 :
363 :
364 : /**
365 : * Check that the given @a wire_fee is what the @a exchange_pub should charge
366 : * at the @a execution_time. If the fee is correct (according to our
367 : * database), return #GNUNET_OK. If we do not have the fee structure in our
368 : * DB, we just accept it and return #GNUNET_NO; if we have proof that the fee
369 : * is bogus, we respond with the proof to the client and return
370 : * #GNUNET_SYSERR.
371 : *
372 : * @param ptc context of the transfer to respond to
373 : * @param execution_time time of the wire transfer
374 : * @param wire_fee fee claimed by the exchange
375 : * @return #GNUNET_SYSERR if we returned hard proof of
376 : * missbehavior from the exchange to the client
377 : */
378 : static enum GNUNET_GenericReturnValue
379 0 : check_wire_fee (struct PostTransfersContext *ptc,
380 : struct GNUNET_TIME_Timestamp execution_time,
381 : const struct TALER_Amount *wire_fee)
382 : {
383 : struct TALER_WireFeeSet fees;
384 : struct TALER_MasterSignatureP master_sig;
385 : struct GNUNET_TIME_Timestamp start_date;
386 : struct GNUNET_TIME_Timestamp end_date;
387 : enum GNUNET_DB_QueryStatus qs;
388 : char *wire_method;
389 :
390 0 : wire_method = TALER_payto_get_method (ptc->payto_uri);
391 0 : qs = TMH_db->lookup_wire_fee (TMH_db->cls,
392 0 : &ptc->master_pub,
393 : wire_method,
394 : execution_time,
395 : &fees,
396 : &start_date,
397 : &end_date,
398 : &master_sig);
399 0 : switch (qs)
400 : {
401 0 : case GNUNET_DB_STATUS_HARD_ERROR:
402 0 : GNUNET_break (0);
403 0 : ptc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
404 0 : ptc->response = TALER_MHD_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED,
405 : "lookup_wire_fee");
406 0 : return GNUNET_SYSERR;
407 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
408 0 : ptc->soft_retry = true;
409 0 : return GNUNET_NO;
410 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
411 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
412 : "Failed to find wire fee for `%s' and method `%s' at %s in DB, accepting blindly that the fee is %s\n",
413 : TALER_B2S (&ptc->master_pub),
414 : wire_method,
415 : GNUNET_TIME_timestamp2s (execution_time),
416 : TALER_amount2s (wire_fee));
417 0 : GNUNET_free (wire_method);
418 0 : return GNUNET_NO;
419 0 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
420 0 : break;
421 : }
422 0 : if (0 <= TALER_amount_cmp (&fees.wire,
423 : wire_fee))
424 : {
425 0 : GNUNET_free (wire_method);
426 0 : return GNUNET_OK; /* expected_fee >= wire_fee */
427 : }
428 : /* Wire fee check failed, export proof to client */
429 0 : ptc->response_code = MHD_HTTP_ACCEPTED;
430 0 : ptc->response =
431 0 : TALER_MHD_MAKE_JSON_PACK (
432 : TALER_JSON_pack_ec (
433 : TALER_EC_MERCHANT_PRIVATE_POST_TRANSFERS_BAD_WIRE_FEE),
434 : TALER_JSON_pack_amount ("wire_fee",
435 : wire_fee),
436 : GNUNET_JSON_pack_timestamp ("execution_time",
437 : execution_time),
438 : TALER_JSON_pack_amount ("expected_wire_fee",
439 : &fees.wire),
440 : TALER_JSON_pack_amount ("expected_closing_fee",
441 : &fees.closing),
442 : TALER_JSON_pack_amount ("expected_wad_fee",
443 : &fees.wad),
444 : GNUNET_JSON_pack_timestamp ("start_date",
445 : start_date),
446 : GNUNET_JSON_pack_timestamp ("end_date",
447 : end_date),
448 : GNUNET_JSON_pack_data_auto ("master_sig",
449 : &master_sig),
450 : GNUNET_JSON_pack_data_auto ("master_pub",
451 : &ptc->master_pub));
452 0 : GNUNET_free (wire_method);
453 0 : return GNUNET_SYSERR;
454 : }
455 :
456 :
457 : /**
458 : * Function called with detailed wire transfer data, including all
459 : * of the coin transactions that were combined into the wire transfer.
460 : *
461 : * @param cls closure
462 : * @param hr HTTP response details
463 : * @param td transfer data
464 : */
465 : static void
466 0 : wire_transfer_cb (void *cls,
467 : const struct TALER_EXCHANGE_HttpResponse *hr,
468 : const struct TALER_EXCHANGE_TransferData *td)
469 : {
470 0 : struct PostTransfersContext *ptc = cls;
471 0 : const char *instance_id = ptc->hc->instance->settings.id;
472 : enum GNUNET_DB_QueryStatus qs;
473 :
474 0 : ptc->wdh = NULL;
475 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
476 : "Got response code %u from exchange for GET /transfers/$WTID\n",
477 : hr->http_status);
478 0 : switch (hr->http_status)
479 : {
480 0 : case MHD_HTTP_OK:
481 0 : break;
482 0 : case MHD_HTTP_NOT_FOUND:
483 0 : resume_transfer_with_response (
484 : ptc,
485 : MHD_HTTP_BAD_GATEWAY,
486 0 : TALER_MHD_MAKE_JSON_PACK (
487 : TALER_JSON_pack_ec (
488 : TALER_EC_MERCHANT_PRIVATE_POST_TRANSFERS_EXCHANGE_UNKNOWN),
489 : TMH_pack_exchange_reply (hr)));
490 0 : return;
491 0 : default:
492 0 : resume_transfer_with_response (
493 : ptc,
494 : MHD_HTTP_BAD_GATEWAY,
495 0 : TALER_MHD_MAKE_JSON_PACK (
496 : TALER_JSON_pack_ec (
497 : TALER_EC_MERCHANT_GENERIC_EXCHANGE_UNEXPECTED_STATUS),
498 : TMH_pack_exchange_reply (hr)));
499 0 : return;
500 : }
501 0 : TMH_db->preflight (TMH_db->cls);
502 : /* Ok, exchange answer is acceptable, store it */
503 0 : qs = TMH_db->insert_transfer_details (TMH_db->cls,
504 : instance_id,
505 : ptc->exchange_url,
506 : ptc->payto_uri,
507 0 : &ptc->wtid,
508 : td);
509 0 : if (0 > qs)
510 : {
511 : /* Always report on DB error as well to enable diagnostics */
512 0 : GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
513 0 : resume_transfer_with_error (
514 : ptc,
515 : MHD_HTTP_INTERNAL_SERVER_ERROR,
516 : (GNUNET_DB_STATUS_HARD_ERROR == qs)
517 : ? TALER_EC_GENERIC_DB_COMMIT_FAILED
518 : : TALER_EC_GENERIC_DB_SOFT_FAILURE,
519 : NULL);
520 0 : return;
521 : }
522 0 : if (0 == qs)
523 : {
524 0 : GNUNET_break (0);
525 0 : resume_transfer_with_error (
526 : ptc,
527 : MHD_HTTP_INTERNAL_SERVER_ERROR,
528 : TALER_EC_GENERIC_DB_STORE_FAILED,
529 : "insert-transfer-details");
530 0 : return;
531 : }
532 0 : if (0 !=
533 0 : TALER_amount_cmp (&td->total_amount,
534 0 : &ptc->amount))
535 : {
536 0 : resume_transfer_with_error (
537 : ptc,
538 : MHD_HTTP_CONFLICT,
539 : TALER_EC_MERCHANT_PRIVATE_POST_TRANSFERS_CONFLICTING_TRANSFERS,
540 : NULL);
541 0 : return;
542 : }
543 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
544 : "Transfer details inserted, resuming request...\n");
545 : /* resume processing, main function will build the response */
546 0 : resume_transfer_with_response (ptc,
547 : 0,
548 : NULL);
549 : }
550 :
551 :
552 : /**
553 : * Function called with the result of our exchange lookup.
554 : *
555 : * @param cls the `struct PostTransfersContext`
556 : * @param hr HTTP response details
557 : * @param eh NULL if exchange was not found to be acceptable
558 : * @param payto_uri payto://-URI of the exchange
559 : * @param wire_fee NULL (we did not specify a wire method)
560 : * @param exchange_trusted #GNUNET_YES if this exchange is trusted by config
561 : */
562 : static void
563 0 : process_transfer_with_exchange (void *cls,
564 : const struct TALER_EXCHANGE_HttpResponse *hr,
565 : struct TALER_EXCHANGE_Handle *eh,
566 : const char *payto_uri,
567 : const struct TALER_Amount *wire_fee,
568 : bool exchange_trusted)
569 : {
570 0 : struct PostTransfersContext *ptc = cls;
571 :
572 : (void) payto_uri;
573 : (void) exchange_trusted;
574 0 : ptc->fo = NULL;
575 0 : if (NULL == hr)
576 : {
577 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
578 : "Exchange failed to respond!\n");
579 0 : resume_transfer_with_response (
580 : ptc,
581 : MHD_HTTP_GATEWAY_TIMEOUT,
582 0 : TALER_MHD_MAKE_JSON_PACK (
583 : TALER_JSON_pack_ec (TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT)));
584 0 : return;
585 : }
586 0 : if (NULL == eh)
587 : {
588 : /* The request failed somehow */
589 0 : GNUNET_break_op (0);
590 0 : resume_transfer_with_response (
591 : ptc,
592 : MHD_HTTP_BAD_GATEWAY,
593 0 : TALER_MHD_MAKE_JSON_PACK (
594 : TALER_JSON_pack_ec (
595 : TALER_EC_MERCHANT_GENERIC_EXCHANGE_CONNECT_FAILURE),
596 : TMH_pack_exchange_reply (hr)));
597 0 : return;
598 : }
599 :
600 : /* keep master key for later */
601 : {
602 : const struct TALER_EXCHANGE_Keys *keys;
603 :
604 0 : keys = TALER_EXCHANGE_get_keys (eh);
605 0 : if (NULL == keys)
606 : {
607 0 : GNUNET_break (0);
608 0 : resume_transfer_with_error (ptc,
609 : MHD_HTTP_BAD_GATEWAY,
610 : TALER_EC_MERCHANT_GENERIC_EXCHANGE_KEYS_FAILURE,
611 : NULL);
612 0 : return;
613 : }
614 0 : ptc->master_pub = keys->master_pub;
615 : }
616 :
617 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
618 : "Requesting transfer details from exchange\n");
619 0 : ptc->wdh = TALER_EXCHANGE_transfers_get (eh,
620 0 : &ptc->wtid,
621 : &wire_transfer_cb,
622 : ptc);
623 0 : if (NULL == ptc->wdh)
624 : {
625 0 : GNUNET_break (0);
626 0 : resume_transfer_with_error (ptc,
627 : MHD_HTTP_INTERNAL_SERVER_ERROR,
628 : TALER_EC_MERCHANT_PRIVATE_POST_TRANSFERS_REQUEST_ERROR,
629 : "failed to run GET /transfers/ on exchange");
630 : }
631 : }
632 :
633 :
634 : /**
635 : * Now we want to double-check that any (Taler coin) deposit which is
636 : * accounted into _this_ wire transfer, does exist into _our_ database. This
637 : * is the rationale: if the exchange paid us for it, we must have received it
638 : * _beforehands_!
639 : *
640 : * @param cls a `struct PostTransfersContext`
641 : * @param current_offset at which offset in the exchange's reply are the @a ttd
642 : * @param ttd details about an aggregated transfer (to check)
643 : */
644 : static void
645 0 : verify_exchange_claim_cb (void *cls,
646 : unsigned int current_offset,
647 : const struct TALER_TrackTransferDetails *ttd)
648 : {
649 0 : struct PostTransfersContext *ptc = cls;
650 : enum GNUNET_DB_QueryStatus qs;
651 :
652 0 : if (0 != ptc->response_code)
653 0 : return; /* already encountered an error */
654 0 : if (ptc->soft_retry)
655 0 : return; /* already encountered an error */
656 0 : ptc->current_offset = current_offset;
657 0 : ptc->current_detail = ttd;
658 : /* Set the coin as "never seen" before. */
659 0 : ptc->check_transfer_result = GNUNET_NO;
660 0 : qs = TMH_db->lookup_deposits_by_contract_and_coin (
661 0 : TMH_db->cls,
662 0 : ptc->hc->instance->settings.id,
663 : &ttd->h_contract_terms,
664 : &ttd->coin_pub,
665 : &check_transfer,
666 : ptc);
667 0 : switch (qs)
668 : {
669 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
670 0 : ptc->soft_retry = true;
671 0 : return;
672 0 : case GNUNET_DB_STATUS_HARD_ERROR:
673 0 : GNUNET_break (0);
674 0 : ptc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
675 : ptc->response
676 0 : = TALER_MHD_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED,
677 : "deposit by contract and coin");
678 0 : return;
679 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
680 : /* The exchange says we made this deposit, but WE do not
681 : recall making it (corrupted / unreliable database?)!
682 : Well, let's say thanks and accept the money! */
683 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
684 : "Failed to find payment data in DB\n");
685 0 : ptc->check_transfer_result = GNUNET_OK;
686 0 : break;
687 0 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
688 0 : break;
689 : }
690 0 : switch (ptc->check_transfer_result)
691 : {
692 0 : case GNUNET_NO:
693 : /* Internal error: how can we have called #check_transfer()
694 : but still have no result? */
695 0 : GNUNET_break (0);
696 0 : ptc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
697 0 : ptc->response =
698 0 : TALER_MHD_make_error (TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
699 : "check_transfer_result must not be GNUNET_NO");
700 0 : return;
701 0 : case GNUNET_SYSERR:
702 : /* #check_transfer() failed, report conflict! */
703 0 : GNUNET_break_op (0);
704 0 : GNUNET_assert (NULL != ptc->response);
705 0 : return;
706 0 : case GNUNET_OK:
707 0 : break;
708 : }
709 : }
710 :
711 :
712 : /**
713 : * Represents an entry in the table used to sum up
714 : * individual deposits for each h_contract_terms/order_id
715 : * (as the exchange gives us per coin, and we return
716 : * per order).
717 : */
718 : struct Entry
719 : {
720 :
721 : /**
722 : * Order of the entry.
723 : */
724 : char *order_id;
725 :
726 : /**
727 : * Sum accumulator for deposited value.
728 : */
729 : struct TALER_Amount deposit_value;
730 :
731 : /**
732 : * Sum accumulator for deposit fee.
733 : */
734 : struct TALER_Amount deposit_fee;
735 :
736 : };
737 :
738 :
739 : /**
740 : * Function called with information about a wire transfer identifier.
741 : * Generate a response array based on the given information.
742 : *
743 : * @param cls closure, a hashmap to update
744 : * @param order_id the order to which the deposits belong
745 : * @param deposit_value the amount deposited under @a order_id
746 : * @param deposit_fee the fee charged for @a deposit_value
747 : */
748 : static void
749 0 : transfer_summary_cb (void *cls,
750 : const char *order_id,
751 : const struct TALER_Amount *deposit_value,
752 : const struct TALER_Amount *deposit_fee)
753 : {
754 0 : struct GNUNET_CONTAINER_MultiHashMap *map = cls;
755 : struct Entry *current_entry;
756 : struct GNUNET_HashCode h_key;
757 :
758 0 : GNUNET_CRYPTO_hash (order_id,
759 : strlen (order_id),
760 : &h_key);
761 0 : current_entry = GNUNET_CONTAINER_multihashmap_get (map,
762 : &h_key);
763 0 : if (NULL != current_entry)
764 : {
765 : /* The map already knows this order, do aggregation */
766 0 : GNUNET_assert ( (0 <=
767 : TALER_amount_add (¤t_entry->deposit_value,
768 : ¤t_entry->deposit_value,
769 : deposit_value)) &&
770 : (0 <=
771 : TALER_amount_add (¤t_entry->deposit_fee,
772 : ¤t_entry->deposit_fee,
773 : deposit_fee)) );
774 : }
775 : else
776 : {
777 : /* First time in the map for this h_contract_terms*/
778 0 : current_entry = GNUNET_new (struct Entry);
779 0 : current_entry->deposit_value = *deposit_value;
780 0 : current_entry->deposit_fee = *deposit_fee;
781 0 : current_entry->order_id = GNUNET_strdup (order_id);
782 0 : GNUNET_assert (GNUNET_SYSERR !=
783 : GNUNET_CONTAINER_multihashmap_put (map,
784 : &h_key,
785 : current_entry,
786 : GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
787 : }
788 0 : }
789 :
790 :
791 : /**
792 : * Callback that frees all the elements in the hashmap, and @a cls
793 : * is non-NULL, appends them as JSON to the array
794 : *
795 : * @param cls closure, NULL or a `json_t *` array
796 : * @param key current key
797 : * @param value a `struct Entry`
798 : * @return #GNUNET_YES if the iteration should continue,
799 : * #GNUNET_NO otherwise.
800 : */
801 : static int
802 0 : hashmap_update_and_free (void *cls,
803 : const struct GNUNET_HashCode *key,
804 : void *value)
805 : {
806 0 : json_t *ja = cls;
807 0 : struct Entry *entry = value;
808 :
809 : (void) key;
810 0 : if (NULL != ja)
811 : {
812 0 : GNUNET_assert (
813 : 0 ==
814 : json_array_append_new (
815 : ja,
816 : GNUNET_JSON_PACK (
817 : GNUNET_JSON_pack_string ("order_id",
818 : entry->order_id),
819 : TALER_JSON_pack_amount ("deposit_value",
820 : &entry->deposit_value),
821 : TALER_JSON_pack_amount ("deposit_fee",
822 : &entry->deposit_fee))));
823 : }
824 0 : GNUNET_free (entry->order_id);
825 0 : GNUNET_free (entry);
826 0 : return GNUNET_YES;
827 : }
828 :
829 :
830 : /**
831 : * Handle a timeout for the processing of the track transfer request.
832 : *
833 : * @param cls closure
834 : */
835 : static void
836 0 : handle_transfer_timeout (void *cls)
837 : {
838 0 : struct PostTransfersContext *ptc = cls;
839 :
840 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
841 : "Resuming POST /private/transfers with error after timeout\n");
842 0 : ptc->timeout_task = NULL;
843 0 : if (NULL != ptc->fo)
844 : {
845 0 : TMH_EXCHANGES_find_exchange_cancel (ptc->fo);
846 0 : ptc->fo = NULL;
847 : }
848 0 : if (NULL != ptc->wdh)
849 : {
850 0 : TALER_EXCHANGE_transfers_get_cancel (ptc->wdh);
851 0 : ptc->wdh = NULL;
852 : }
853 0 : resume_transfer_with_error (ptc,
854 : MHD_HTTP_GATEWAY_TIMEOUT,
855 : TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT,
856 : NULL);
857 0 : }
858 :
859 :
860 : /**
861 : * We are *done* processing the request, just queue the response (!)
862 : *
863 : * @param ptc request context
864 : */
865 : static MHD_RESULT
866 0 : queue (struct PostTransfersContext *ptc)
867 : {
868 : MHD_RESULT ret;
869 :
870 0 : GNUNET_assert (0 != ptc->response_code);
871 0 : if (UINT_MAX == ptc->response_code)
872 : {
873 0 : GNUNET_break (0);
874 0 : return MHD_NO; /* hard error */
875 : }
876 0 : ret = MHD_queue_response (ptc->connection,
877 : ptc->response_code,
878 : ptc->response);
879 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
880 : "Queueing response (%u) for POST /private/transfers (%s).\n",
881 : (unsigned int) ptc->response_code,
882 : ret ? "OK" : "FAILED");
883 0 : return ret;
884 : }
885 :
886 :
887 : /**
888 : * Download transfer data from the exchange.
889 : *
890 : * @param ptc request context
891 : */
892 : static void
893 0 : download (struct PostTransfersContext *ptc)
894 : {
895 0 : ptc->downloaded = true;
896 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
897 : "Suspending POST /private/transfers handling while working with exchange\n");
898 0 : MHD_suspend_connection (ptc->connection);
899 0 : ptc->suspended = GNUNET_YES;
900 0 : GNUNET_CONTAINER_DLL_insert (ptc_head,
901 : ptc_tail,
902 : ptc);
903 0 : ptc->fo = TMH_EXCHANGES_find_exchange (ptc->exchange_url,
904 : NULL,
905 : GNUNET_NO,
906 : &process_transfer_with_exchange,
907 : ptc);
908 : ptc->timeout_task
909 0 : = GNUNET_SCHEDULER_add_delayed (TRANSFER_GENERIC_TIMEOUT,
910 : &handle_transfer_timeout,
911 : ptc);
912 0 : }
913 :
914 :
915 : MHD_RESULT
916 0 : TMH_private_post_transfers (const struct TMH_RequestHandler *rh,
917 : struct MHD_Connection *connection,
918 : struct TMH_HandlerContext *hc)
919 : {
920 0 : struct PostTransfersContext *ptc = hc->ctx;
921 : enum GNUNET_DB_QueryStatus qs;
922 :
923 0 : if (NULL == ptc)
924 : {
925 0 : ptc = GNUNET_new (struct PostTransfersContext);
926 0 : ptc->connection = connection;
927 0 : ptc->hc = hc;
928 0 : hc->ctx = ptc;
929 0 : hc->cc = &transfer_cleanup;
930 : }
931 0 : if (GNUNET_SYSERR == ptc->suspended)
932 0 : return MHD_NO; /* we are in shutdown */
933 : /* resume logic: did we get resumed after a reply was built? */
934 0 : if (0 != ptc->response_code)
935 0 : return queue (ptc);
936 0 : if ( (NULL != ptc->fo) ||
937 0 : (NULL != ptc->wdh) )
938 : {
939 : /* likely old MHD version causing spurious wake-up */
940 0 : GNUNET_break (GNUNET_NO == ptc->suspended);
941 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
942 : "Not sure why we are here, should be suspended\n");
943 0 : return MHD_YES; /* still work in progress */
944 : }
945 0 : if (NULL == ptc->exchange_url)
946 : {
947 : /* First request, parse it! */
948 : struct GNUNET_JSON_Specification spec[] = {
949 0 : TALER_JSON_spec_amount ("credit_amount",
950 : TMH_currency,
951 : &ptc->amount),
952 0 : GNUNET_JSON_spec_fixed_auto ("wtid",
953 : &ptc->wtid),
954 0 : GNUNET_JSON_spec_string ("payto_uri",
955 : &ptc->payto_uri),
956 0 : GNUNET_JSON_spec_string ("exchange_url",
957 : &ptc->exchange_url),
958 0 : GNUNET_JSON_spec_end ()
959 : };
960 : enum GNUNET_GenericReturnValue res;
961 :
962 0 : res = TALER_MHD_parse_json_data (connection,
963 0 : hc->request_body,
964 : spec);
965 0 : if (GNUNET_OK != res)
966 : return (GNUNET_NO == res)
967 : ? MHD_YES
968 0 : : MHD_NO;
969 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
970 : "New inbound wire transfer over %s to %s from %s\n",
971 : TALER_amount2s (&ptc->amount),
972 : ptc->payto_uri,
973 : ptc->exchange_url);
974 : }
975 :
976 : /* Check if transfer data is in database, if not, add it. */
977 0 : for (unsigned int retry = 0; retry<MAX_RETRIES; retry++)
978 : {
979 : struct GNUNET_TIME_Timestamp execution_time;
980 : struct TALER_Amount total_amount;
981 : struct TALER_Amount exchange_amount;
982 : struct TALER_Amount wire_fee;
983 : bool verified;
984 : bool have_exchange_sig;
985 :
986 0 : TMH_db->preflight (TMH_db->cls);
987 0 : if (GNUNET_OK !=
988 0 : TMH_db->start (TMH_db->cls,
989 : "post-transfers"))
990 : {
991 0 : GNUNET_break (0);
992 0 : return TALER_MHD_reply_with_error (connection,
993 : MHD_HTTP_INTERNAL_SERVER_ERROR,
994 : TALER_EC_GENERIC_DB_START_FAILED,
995 : "transfer");
996 : }
997 0 : qs = TMH_db->lookup_transfer (TMH_db->cls,
998 0 : ptc->hc->instance->settings.id,
999 : ptc->exchange_url,
1000 0 : &ptc->wtid,
1001 : &total_amount,
1002 : &wire_fee,
1003 : &exchange_amount,
1004 : &execution_time,
1005 : &have_exchange_sig,
1006 : &verified);
1007 0 : switch (qs)
1008 : {
1009 0 : case GNUNET_DB_STATUS_HARD_ERROR:
1010 0 : GNUNET_break (0);
1011 0 : TMH_db->rollback (TMH_db->cls);
1012 0 : return TALER_MHD_reply_with_error (connection,
1013 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1014 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1015 : "transfer");
1016 : break;
1017 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
1018 0 : TMH_db->rollback (TMH_db->cls);
1019 0 : continue;
1020 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
1021 : /* Transfer so far unknown; try to persist the wire transfer information
1022 : we have received in the database (it is not yet present). Upon
1023 : success, try to download the transfer details from the exchange. */
1024 : {
1025 : uint64_t account_serial;
1026 :
1027 : /* Make sure the bank account is configured. */
1028 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1029 : "Transfer is not yet known\n");
1030 0 : qs = TMH_db->lookup_account (TMH_db->cls,
1031 0 : ptc->hc->instance->settings.id,
1032 : ptc->payto_uri,
1033 : &account_serial);
1034 : switch (qs)
1035 : {
1036 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
1037 0 : TMH_db->rollback (TMH_db->cls);
1038 0 : continue;
1039 0 : case GNUNET_DB_STATUS_HARD_ERROR:
1040 0 : GNUNET_break (0);
1041 0 : TMH_db->rollback (TMH_db->cls);
1042 0 : return TALER_MHD_reply_with_error (connection,
1043 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1044 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1045 : "lookup_account");
1046 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
1047 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1048 : "Bank account `%s' not configured for instance `%s'\n",
1049 : ptc->payto_uri,
1050 : ptc->hc->instance->settings.id);
1051 0 : TMH_db->rollback (TMH_db->cls);
1052 0 : return TALER_MHD_reply_with_error (connection,
1053 : MHD_HTTP_NOT_FOUND,
1054 : TALER_EC_MERCHANT_PRIVATE_POST_TRANSFERS_ACCOUNT_NOT_FOUND,
1055 : ptc->payto_uri);
1056 0 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
1057 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1058 : "Bank account `%s' is configured at row %llu\n",
1059 : ptc->payto_uri,
1060 : (unsigned long long) account_serial);
1061 0 : break;
1062 : }
1063 :
1064 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1065 : "Inserting new transfer\n");
1066 0 : qs = TMH_db->insert_transfer (TMH_db->cls,
1067 0 : ptc->hc->instance->settings.id,
1068 : ptc->exchange_url,
1069 0 : &ptc->wtid,
1070 0 : &ptc->amount,
1071 : ptc->payto_uri,
1072 : true /* confirmed! */);
1073 : switch (qs)
1074 : {
1075 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
1076 0 : TMH_db->rollback (TMH_db->cls);
1077 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1078 : "Soft error, retrying...\n");
1079 0 : continue;
1080 0 : case GNUNET_DB_STATUS_HARD_ERROR:
1081 0 : GNUNET_break (0);
1082 0 : TMH_db->rollback (TMH_db->cls);
1083 0 : return TALER_MHD_reply_with_error (connection,
1084 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1085 : TALER_EC_GENERIC_DB_STORE_FAILED,
1086 : "transfer");
1087 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
1088 0 : TMH_db->rollback (TMH_db->cls);
1089 : /* Should not happen: we checked earlier! */
1090 0 : GNUNET_break (0);
1091 0 : return TALER_MHD_reply_with_error (connection,
1092 : MHD_HTTP_CONFLICT,
1093 : TALER_EC_GENERIC_DB_STORE_FAILED,
1094 : "not unique");
1095 0 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
1096 0 : break;
1097 : }
1098 :
1099 0 : qs = TMH_db->commit (TMH_db->cls);
1100 : switch (qs)
1101 : {
1102 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
1103 0 : TMH_db->rollback (TMH_db->cls);
1104 0 : continue;
1105 0 : case GNUNET_DB_STATUS_HARD_ERROR:
1106 0 : GNUNET_break (0);
1107 0 : TMH_db->rollback (TMH_db->cls);
1108 0 : return TALER_MHD_reply_with_error (connection,
1109 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1110 : TALER_EC_GENERIC_DB_COMMIT_FAILED,
1111 : NULL);
1112 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
1113 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
1114 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1115 : "post-transfer committed successfully\n");
1116 0 : break;
1117 : }
1118 0 : download (ptc);
1119 0 : return MHD_YES; /* download() always suspends */
1120 : }
1121 0 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
1122 : /* Transfer exists */
1123 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1124 : "Transfer exists in DB (verified: %s, exchange signature: %s)\n",
1125 : verified ? "true" : "false",
1126 : have_exchange_sig ? "true" : "false");
1127 0 : if (! verified)
1128 : {
1129 0 : if ( (! ptc->downloaded) &&
1130 0 : (! have_exchange_sig) )
1131 : {
1132 : /* We may have previously attempted and failed to
1133 : download the exchange data, do it again! */
1134 0 : TMH_db->rollback (TMH_db->cls);
1135 0 : download (ptc);
1136 0 : return MHD_YES; /* download always suspends */
1137 : }
1138 0 : if (! have_exchange_sig)
1139 : {
1140 : /* We tried to download and still failed to get
1141 : an exchange signture. Still, that should have
1142 : been handled there. */
1143 0 : TMH_db->rollback (TMH_db->cls);
1144 0 : GNUNET_break (0);
1145 0 : return TALER_MHD_reply_with_error (connection,
1146 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1147 : TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
1148 : "download but no exchange signature and no error");
1149 : }
1150 : /* verify */
1151 0 : if (GNUNET_SYSERR ==
1152 0 : check_wire_fee (ptc,
1153 : execution_time,
1154 : &wire_fee))
1155 : {
1156 0 : TMH_db->rollback (TMH_db->cls);
1157 0 : return queue (ptc); /* generate error */
1158 : }
1159 0 : if (ptc->soft_retry)
1160 : {
1161 : /* DB serialization failure */
1162 0 : ptc->soft_retry = false;
1163 0 : TMH_db->rollback (TMH_db->cls);
1164 0 : continue;
1165 : }
1166 0 : qs = TMH_db->lookup_transfer_details (TMH_db->cls,
1167 : ptc->exchange_url,
1168 0 : &ptc->wtid,
1169 : &verify_exchange_claim_cb,
1170 : ptc);
1171 : switch (qs)
1172 : {
1173 0 : case GNUNET_DB_STATUS_HARD_ERROR:
1174 0 : GNUNET_break (0);
1175 0 : TMH_db->rollback (TMH_db->cls);
1176 0 : return TALER_MHD_reply_with_error (connection,
1177 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1178 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1179 : "lookup_transfer_details");
1180 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
1181 0 : TMH_db->rollback (TMH_db->cls);
1182 0 : continue;
1183 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
1184 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
1185 : default:
1186 0 : break;
1187 : }
1188 0 : if (0 != ptc->response_code)
1189 : {
1190 0 : TMH_db->rollback (TMH_db->cls);
1191 0 : return queue (ptc); /* generate error */
1192 : }
1193 0 : if (ptc->soft_retry)
1194 : {
1195 : /* DB serialization failure */
1196 0 : ptc->soft_retry = false;
1197 0 : TMH_db->rollback (TMH_db->cls);
1198 0 : continue;
1199 : }
1200 :
1201 : {
1202 : struct TALER_Amount delta;
1203 :
1204 0 : if (0 >
1205 0 : TALER_amount_subtract (&delta,
1206 : &total_amount,
1207 : &wire_fee))
1208 : {
1209 0 : GNUNET_break (0);
1210 0 : TMH_db->rollback (TMH_db->cls);
1211 0 : return TALER_MHD_reply_with_error (
1212 : connection,
1213 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1214 : TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
1215 : NULL);
1216 : }
1217 0 : if (0 !=
1218 0 : TALER_amount_cmp (&exchange_amount,
1219 : &delta))
1220 : {
1221 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1222 : "Amount of expected was %s\n",
1223 : TALER_amount2s (&delta));
1224 0 : TMH_db->rollback (TMH_db->cls);
1225 0 : return TALER_MHD_reply_with_error (
1226 : connection,
1227 : MHD_HTTP_CONFLICT,
1228 : TALER_EC_MERCHANT_PRIVATE_POST_TRANSFERS_CONFLICTING_TRANSFERS,
1229 : TALER_amount2s (&exchange_amount));
1230 : }
1231 0 : if ( (GNUNET_OK !=
1232 0 : TALER_amount_cmp_currency (&ptc->amount,
1233 0 : &delta)) ||
1234 : (0 !=
1235 0 : TALER_amount_cmp (&ptc->amount,
1236 : &delta)) )
1237 : {
1238 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1239 : "Amount submitted was %s\n",
1240 : TALER_amount2s (&ptc->amount));
1241 0 : TMH_db->rollback (TMH_db->cls);
1242 0 : return TALER_MHD_reply_with_error (
1243 : connection,
1244 : MHD_HTTP_CONFLICT,
1245 : TALER_EC_MERCHANT_PRIVATE_POST_TRANSFERS_CONFLICTING_SUBMISSION,
1246 : TALER_amount2s (&exchange_amount));
1247 : }
1248 : }
1249 0 : verified = true;
1250 0 : qs = TMH_db->set_transfer_status_to_verified (TMH_db->cls,
1251 : ptc->exchange_url,
1252 0 : &ptc->wtid);
1253 : switch (qs)
1254 : {
1255 0 : case GNUNET_DB_STATUS_HARD_ERROR:
1256 0 : GNUNET_break (0);
1257 0 : TMH_db->rollback (TMH_db->cls);
1258 0 : return TALER_MHD_reply_with_error (connection,
1259 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1260 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1261 : "set_transfer_status_to_verified");
1262 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
1263 0 : TMH_db->rollback (TMH_db->cls);
1264 0 : continue;
1265 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
1266 0 : GNUNET_assert (0);
1267 : break;
1268 0 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
1269 0 : break;
1270 : }
1271 0 : } /* end of 'if (! verified)' */
1272 :
1273 : /* Short version: we verified that the exchange reply and
1274 : our own accounting match; generate the summary response */
1275 0 : GNUNET_assert (verified);
1276 : {
1277 : struct GNUNET_CONTAINER_MultiHashMap *map;
1278 : json_t *deposit_sums;
1279 :
1280 0 : map = GNUNET_CONTAINER_multihashmap_create (16,
1281 : GNUNET_NO);
1282 0 : qs = TMH_db->lookup_transfer_summary (TMH_db->cls,
1283 : ptc->exchange_url,
1284 0 : &ptc->wtid,
1285 : &transfer_summary_cb,
1286 : map);
1287 : switch (qs)
1288 : {
1289 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
1290 0 : TMH_db->rollback (TMH_db->cls);
1291 0 : continue;
1292 0 : case GNUNET_DB_STATUS_HARD_ERROR:
1293 0 : GNUNET_break (0);
1294 0 : TMH_db->rollback (TMH_db->cls);
1295 0 : GNUNET_CONTAINER_multihashmap_iterate (map,
1296 : &hashmap_update_and_free,
1297 : NULL);
1298 0 : GNUNET_CONTAINER_multihashmap_destroy (map);
1299 0 : return TALER_MHD_reply_with_error (connection,
1300 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1301 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1302 : "transfer summary");
1303 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
1304 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
1305 : default:
1306 0 : break;
1307 : }
1308 :
1309 0 : qs = TMH_db->commit (TMH_db->cls);
1310 : switch (qs)
1311 : {
1312 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
1313 0 : TMH_db->rollback (TMH_db->cls);
1314 0 : continue;
1315 0 : case GNUNET_DB_STATUS_HARD_ERROR:
1316 0 : GNUNET_break (0);
1317 0 : TMH_db->rollback (TMH_db->cls);
1318 0 : return TALER_MHD_reply_with_error (connection,
1319 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1320 : TALER_EC_GENERIC_DB_COMMIT_FAILED,
1321 : NULL);
1322 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
1323 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
1324 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1325 : "post-transfer committed uselessly\n");
1326 0 : break;
1327 : }
1328 :
1329 0 : deposit_sums = json_array ();
1330 0 : GNUNET_assert (NULL != deposit_sums);
1331 0 : GNUNET_CONTAINER_multihashmap_iterate (map,
1332 : &hashmap_update_and_free,
1333 : deposit_sums);
1334 0 : GNUNET_CONTAINER_multihashmap_destroy (map);
1335 0 : return TALER_MHD_REPLY_JSON_PACK (
1336 : connection,
1337 : MHD_HTTP_OK,
1338 : TALER_JSON_pack_amount ("total",
1339 : &total_amount),
1340 : TALER_JSON_pack_amount ("wire_fee",
1341 : &wire_fee),
1342 : GNUNET_JSON_pack_timestamp ("execution_time",
1343 : execution_time),
1344 : GNUNET_JSON_pack_array_steal ("deposit_sums",
1345 : deposit_sums));
1346 : } /* end of 'verified == true' (not an 'if'!) */
1347 : } /* end of 'switch (qs)' */
1348 0 : GNUNET_assert (0);
1349 : } /* end of 'for(retries...) */
1350 0 : return TALER_MHD_reply_with_error (connection,
1351 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1352 : TALER_EC_GENERIC_DB_SOFT_FAILURE,
1353 : "post-transfers");
1354 : }
1355 :
1356 :
1357 : /* end of taler-merchant-httpd_private-post-transfers.c */
|