Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2023-2024 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-merchant-reconciliation.c
18 : * @brief Process that reconciles information about incoming bank transfers with orders by asking the exchange
19 : * @author Christian Grothoff
20 : */
21 : #include "platform.h"
22 : #include "microhttpd.h"
23 : #include <gnunet/gnunet_util_lib.h>
24 : #include <jansson.h>
25 : #include <pthread.h>
26 : #include <taler/taler_dbevents.h>
27 : #include "taler_merchant_util.h"
28 : #include "taler_merchant_bank_lib.h"
29 : #include "taler_merchantdb_lib.h"
30 : #include "taler_merchantdb_plugin.h"
31 :
32 : /**
33 : * Timeout for the exchange interaction. Rather long as we should do
34 : * long-polling and do not want to wake up too often.
35 : */
36 : #define EXCHANGE_TIMEOUT GNUNET_TIME_relative_multiply ( \
37 : GNUNET_TIME_UNIT_MINUTES, \
38 : 30)
39 :
40 : /**
41 : * How many inquiries do we process concurrently at most.
42 : */
43 : #define OPEN_INQUIRY_LIMIT 1024
44 :
45 : /**
46 : * How many inquiries do we process concurrently per exchange at most.
47 : */
48 : #define EXCHANGE_INQUIRY_LIMIT 16
49 :
50 :
51 : /**
52 : * Information about an inquiry job.
53 : */
54 : struct Inquiry;
55 :
56 :
57 : /**
58 : * Information about an exchange.
59 : */
60 : struct Exchange
61 : {
62 : /**
63 : * Kept in a DLL.
64 : */
65 : struct Exchange *next;
66 :
67 : /**
68 : * Kept in a DLL.
69 : */
70 : struct Exchange *prev;
71 :
72 : /**
73 : * Head of active inquiries.
74 : */
75 : struct Inquiry *w_head;
76 :
77 : /**
78 : * Tail of active inquiries.
79 : */
80 : struct Inquiry *w_tail;
81 :
82 : /**
83 : * Which exchange are we tracking here.
84 : */
85 : char *exchange_url;
86 :
87 : /**
88 : * The keys of this exchange
89 : */
90 : struct TALER_EXCHANGE_Keys *keys;
91 :
92 : /**
93 : * How many active inquiries do we have right now with this exchange.
94 : */
95 : unsigned int exchange_inquiries;
96 :
97 : /**
98 : * How long should we wait between requests
99 : * for transfer details?
100 : */
101 : struct GNUNET_TIME_Relative transfer_delay;
102 :
103 : };
104 :
105 :
106 : /**
107 : * Information about an inquiry job.
108 : */
109 : struct Inquiry
110 : {
111 : /**
112 : * Kept in a DLL.
113 : */
114 : struct Inquiry *next;
115 :
116 : /**
117 : * Kept in a DLL.
118 : */
119 : struct Inquiry *prev;
120 :
121 : /**
122 : * Handle to the exchange that made the transfer.
123 : */
124 : struct Exchange *exchange;
125 :
126 : /**
127 : * Task where we retry fetching transfer details from the exchange.
128 : */
129 : struct GNUNET_SCHEDULER_Task *task;
130 :
131 : /**
132 : * For which merchant instance is this tracking request?
133 : */
134 : char *instance_id;
135 :
136 : /**
137 : * payto:// URI used for the transfer.
138 : */
139 : struct TALER_FullPayto payto_uri;
140 :
141 : /**
142 : * Handle for the /wire/transfers request.
143 : */
144 : struct TALER_EXCHANGE_TransfersGetHandle *wdh;
145 :
146 : /**
147 : * When did the transfer happen?
148 : */
149 : struct GNUNET_TIME_Timestamp execution_time;
150 :
151 : /**
152 : * Argument for the /wire/transfers request.
153 : */
154 : struct TALER_WireTransferIdentifierRawP wtid;
155 :
156 : /**
157 : * Amount of the wire transfer.
158 : */
159 : struct TALER_Amount total;
160 :
161 : /**
162 : * Row of the wire transfer in our database.
163 : */
164 : uint64_t rowid;
165 :
166 : };
167 :
168 :
169 : /**
170 : * Head of known exchanges.
171 : */
172 : static struct Exchange *e_head;
173 :
174 : /**
175 : * Tail of known exchanges.
176 : */
177 : static struct Exchange *e_tail;
178 :
179 : /**
180 : * The merchant's configuration.
181 : */
182 : static const struct GNUNET_CONFIGURATION_Handle *cfg;
183 :
184 : /**
185 : * Our database plugin.
186 : */
187 : static struct TALER_MERCHANTDB_Plugin *db_plugin;
188 :
189 : /**
190 : * Handle to the context for interacting with the bank.
191 : */
192 : static struct GNUNET_CURL_Context *ctx;
193 :
194 : /**
195 : * Scheduler context for running the @e ctx.
196 : */
197 : static struct GNUNET_CURL_RescheduleContext *rc;
198 :
199 : /**
200 : * Main task for #find_work().
201 : */
202 : static struct GNUNET_SCHEDULER_Task *task;
203 :
204 : /**
205 : * Event handler to learn that there are new transfers
206 : * to check.
207 : */
208 : static struct GNUNET_DB_EventHandler *eh;
209 :
210 : /**
211 : * Event handler to learn that there may be new exchange
212 : * keys to check.
213 : */
214 : static struct GNUNET_DB_EventHandler *eh_keys;
215 :
216 : /**
217 : * How many active inquiries do we have right now.
218 : */
219 : static unsigned int active_inquiries;
220 :
221 : /**
222 : * Set to true if we ever encountered any problem.
223 : */
224 : static bool found_problem;
225 :
226 : /**
227 : * Value to return from main(). 0 on success, non-zero on errors.
228 : */
229 : static int global_ret;
230 :
231 : /**
232 : * #GNUNET_YES if we are in test mode and should exit when idle.
233 : */
234 : static int test_mode;
235 :
236 : /**
237 : * True if the last DB query was limited by the
238 : * #OPEN_INQUIRY_LIMIT and we thus should check again
239 : * as soon as we are substantially below that limit,
240 : * and not only when we get a DB notification.
241 : */
242 : static bool at_limit;
243 :
244 :
245 : /**
246 : * Initiate download from exchange.
247 : *
248 : * @param cls a `struct Inquiry *`
249 : */
250 : static void
251 : exchange_request (void *cls);
252 :
253 :
254 : /**
255 : * The exchange @a e is ready to handle more inquiries,
256 : * prepare to launch them.
257 : *
258 : * @param[in,out] e exchange to potentially launch inquiries on
259 : */
260 : static void
261 10 : launch_inquiries_at_exchange (struct Exchange *e)
262 : {
263 10 : for (struct Inquiry *w = e->w_head;
264 10 : NULL != w;
265 0 : w = w->next)
266 : {
267 0 : if (e->exchange_inquiries > EXCHANGE_INQUIRY_LIMIT)
268 0 : break;
269 0 : if ( (NULL == w->task) &&
270 0 : (NULL == w->wdh) )
271 : {
272 0 : e->exchange_inquiries++;
273 0 : w->task = GNUNET_SCHEDULER_add_now (&exchange_request,
274 : w);
275 : }
276 : }
277 10 : }
278 :
279 :
280 : /**
281 : * Updates the transaction status for inquiry @a w to the given values.
282 : *
283 : * @param w inquiry to update status for
284 : * @param next_attempt when should we retry @a w (if ever)
285 : * @param ec error code to use (if any)
286 : * @param failed failure status (if ultimately failed)
287 : * @param verified success status (if ultimately successful)
288 : */
289 : static void
290 30 : update_transaction_status (const struct Inquiry *w,
291 : struct GNUNET_TIME_Absolute next_attempt,
292 : enum TALER_ErrorCode ec,
293 : bool failed,
294 : bool verified)
295 : {
296 : enum GNUNET_DB_QueryStatus qs;
297 :
298 30 : if (failed)
299 0 : found_problem = true;
300 30 : qs = db_plugin->update_transfer_status (db_plugin->cls,
301 30 : w->exchange->exchange_url,
302 : &w->wtid,
303 : next_attempt,
304 : ec,
305 : failed,
306 : verified);
307 30 : if (qs < 0)
308 : {
309 0 : GNUNET_break (0);
310 0 : global_ret = EXIT_FAILURE;
311 0 : GNUNET_SCHEDULER_shutdown ();
312 0 : return;
313 : }
314 : }
315 :
316 :
317 : /**
318 : * Interact with the database to get the current set
319 : * of exchange keys known to us.
320 : *
321 : * @param e the exchange to check
322 : */
323 : static void
324 10 : sync_keys (struct Exchange *e)
325 : {
326 : enum GNUNET_DB_QueryStatus qs;
327 : struct TALER_EXCHANGE_Keys *keys;
328 : struct GNUNET_TIME_Absolute first_retry;
329 :
330 10 : qs = db_plugin->select_exchange_keys (db_plugin->cls,
331 10 : e->exchange_url,
332 : &first_retry,
333 : &keys);
334 10 : if (qs < 0)
335 : {
336 0 : GNUNET_break (0);
337 0 : return;
338 : }
339 10 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
340 : {
341 0 : GNUNET_break (0);
342 0 : return;
343 : }
344 10 : TALER_EXCHANGE_keys_decref (e->keys);
345 10 : e->keys = keys;
346 10 : launch_inquiries_at_exchange (e);
347 : }
348 :
349 :
350 : /**
351 : * Lookup our internal data structure for the given
352 : * @a exchange_url or create one if we do not yet have
353 : * one.
354 : *
355 : * @param exchange_url base URL of the exchange
356 : * @return our state for this exchange
357 : */
358 : static struct Exchange *
359 10 : find_exchange (const char *exchange_url)
360 : {
361 : struct Exchange *e;
362 :
363 10 : for (e = e_head; NULL != e; e = e->next)
364 0 : if (0 == strcmp (exchange_url,
365 0 : e->exchange_url))
366 0 : return e;
367 10 : e = GNUNET_new (struct Exchange);
368 10 : e->exchange_url = GNUNET_strdup (exchange_url);
369 10 : GNUNET_CONTAINER_DLL_insert (e_head,
370 : e_tail,
371 : e);
372 10 : sync_keys (e);
373 10 : return e;
374 : }
375 :
376 :
377 : /**
378 : * Finds new transfers that require work in the merchant database.
379 : *
380 : * @param cls NULL
381 : */
382 : static void
383 : find_work (void *cls);
384 :
385 :
386 : /**
387 : * Free resources of @a w.
388 : *
389 : * @param[in] w inquiry job to terminate
390 : */
391 : static void
392 10 : end_inquiry (struct Inquiry *w)
393 : {
394 10 : struct Exchange *e = w->exchange;
395 :
396 10 : GNUNET_assert (active_inquiries > 0);
397 10 : active_inquiries--;
398 10 : if (NULL != w->wdh)
399 : {
400 0 : TALER_EXCHANGE_transfers_get_cancel (w->wdh);
401 0 : w->wdh = NULL;
402 : }
403 10 : GNUNET_free (w->instance_id);
404 10 : GNUNET_free (w->payto_uri.full_payto);
405 10 : GNUNET_CONTAINER_DLL_remove (e->w_head,
406 : e->w_tail,
407 : w);
408 10 : GNUNET_free (w);
409 10 : if ( (active_inquiries < OPEN_INQUIRY_LIMIT / 2) &&
410 10 : (NULL == task) &&
411 : (at_limit) )
412 : {
413 0 : at_limit = false;
414 0 : GNUNET_assert (NULL == task);
415 0 : task = GNUNET_SCHEDULER_add_now (&find_work,
416 : NULL);
417 : }
418 10 : if ( (NULL == task) &&
419 10 : (! at_limit) &&
420 10 : (0 == active_inquiries) &&
421 : (test_mode) )
422 : {
423 10 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
424 : "No more open inquiries and in test mode. Existing.\n");
425 10 : GNUNET_SCHEDULER_shutdown ();
426 10 : return;
427 : }
428 : }
429 :
430 :
431 : /**
432 : * We're being aborted with CTRL-C (or SIGTERM). Shut down.
433 : *
434 : * @param cls closure (NULL)
435 : */
436 : static void
437 10 : shutdown_task (void *cls)
438 : {
439 : (void) cls;
440 10 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
441 : "Running shutdown\n");
442 20 : while (NULL != e_head)
443 : {
444 10 : struct Exchange *e = e_head;
445 :
446 10 : while (NULL != e->w_head)
447 : {
448 0 : struct Inquiry *w = e->w_head;
449 :
450 0 : end_inquiry (w);
451 : }
452 10 : GNUNET_free (e->exchange_url);
453 10 : if (NULL != e->keys)
454 : {
455 10 : TALER_EXCHANGE_keys_decref (e->keys);
456 10 : e->keys = NULL;
457 : }
458 10 : GNUNET_CONTAINER_DLL_remove (e_head,
459 : e_tail,
460 : e);
461 10 : GNUNET_free (e);
462 : }
463 10 : if (NULL != eh)
464 : {
465 10 : db_plugin->event_listen_cancel (eh);
466 10 : eh = NULL;
467 : }
468 10 : if (NULL != eh_keys)
469 : {
470 10 : db_plugin->event_listen_cancel (eh_keys);
471 10 : eh_keys = NULL;
472 : }
473 10 : if (NULL != task)
474 : {
475 0 : GNUNET_SCHEDULER_cancel (task);
476 0 : task = NULL;
477 : }
478 10 : TALER_MERCHANTDB_plugin_unload (db_plugin);
479 10 : db_plugin = NULL;
480 10 : cfg = NULL;
481 10 : if (NULL != ctx)
482 : {
483 10 : GNUNET_CURL_fini (ctx);
484 10 : ctx = NULL;
485 : }
486 10 : if (NULL != rc)
487 : {
488 10 : GNUNET_CURL_gnunet_rc_destroy (rc);
489 10 : rc = NULL;
490 : }
491 10 : }
492 :
493 :
494 : /**
495 : * Check that the given @a wire_fee is what the @a e should charge
496 : * at the @a execution_time. If the fee is correct (according to our
497 : * database), return #GNUNET_OK. If we do not have the fee structure in our
498 : * DB, we just accept it and return #GNUNET_NO; if we have proof that the fee
499 : * is bogus, we respond with the proof to the client and return
500 : * #GNUNET_SYSERR.
501 : *
502 : * @param w inquiry to check fees of
503 : * @param execution_time time of the wire transfer
504 : * @param wire_fee fee claimed by the exchange
505 : * @return #GNUNET_SYSERR if we returned hard proof of
506 : * missbehavior from the exchange to the client
507 : */
508 : static enum GNUNET_GenericReturnValue
509 10 : check_wire_fee (struct Inquiry *w,
510 : struct GNUNET_TIME_Timestamp execution_time,
511 : const struct TALER_Amount *wire_fee)
512 : {
513 10 : struct Exchange *e = w->exchange;
514 10 : const struct TALER_EXCHANGE_Keys *keys = e->keys;
515 : struct TALER_WireFeeSet fees;
516 : struct TALER_MasterSignatureP master_sig;
517 : struct GNUNET_TIME_Timestamp start_date;
518 : struct GNUNET_TIME_Timestamp end_date;
519 : enum GNUNET_DB_QueryStatus qs;
520 : char *wire_method;
521 :
522 10 : if (NULL == keys)
523 : {
524 0 : GNUNET_break (0);
525 0 : return GNUNET_NO;
526 : }
527 10 : wire_method = TALER_payto_get_method (w->payto_uri.full_payto);
528 10 : qs = db_plugin->lookup_wire_fee (db_plugin->cls,
529 : &keys->master_pub,
530 : wire_method,
531 : execution_time,
532 : &fees,
533 : &start_date,
534 : &end_date,
535 : &master_sig);
536 10 : switch (qs)
537 : {
538 0 : case GNUNET_DB_STATUS_HARD_ERROR:
539 0 : GNUNET_break (0);
540 0 : GNUNET_free (wire_method);
541 0 : return GNUNET_SYSERR;
542 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
543 0 : GNUNET_free (wire_method);
544 0 : return GNUNET_NO;
545 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
546 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
547 : "Failed to find wire fee for `%s' and method `%s' at %s in DB, accepting blindly that the fee is %s\n",
548 : TALER_B2S (&keys->master_pub),
549 : wire_method,
550 : GNUNET_TIME_timestamp2s (execution_time),
551 : TALER_amount2s (wire_fee));
552 0 : GNUNET_free (wire_method);
553 0 : return GNUNET_OK;
554 10 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
555 10 : break;
556 : }
557 10 : if ( (GNUNET_OK !=
558 10 : TALER_amount_cmp_currency (&fees.wire,
559 10 : wire_fee)) ||
560 10 : (0 > TALER_amount_cmp (&fees.wire,
561 : wire_fee)) )
562 : {
563 0 : GNUNET_break_op (0);
564 0 : GNUNET_free (wire_method);
565 0 : return GNUNET_SYSERR; /* expected_fee >= wire_fee */
566 : }
567 10 : GNUNET_free (wire_method);
568 10 : return GNUNET_OK;
569 : }
570 :
571 :
572 : /**
573 : * Closure for #check_transfer()
574 : */
575 : struct CheckTransferContext
576 : {
577 :
578 : /**
579 : * Pointer to the detail that we are currently
580 : * checking in #check_transfer().
581 : */
582 : const struct TALER_TrackTransferDetails *current_detail;
583 :
584 : /**
585 : * Which transaction detail are we currently looking at?
586 : */
587 : unsigned int current_offset;
588 :
589 : /**
590 : * #GNUNET_NO if we did not find a matching coin.
591 : * #GNUNET_SYSERR if we found a matching coin, but the amounts do not match.
592 : * #GNUNET_OK if we did find a matching coin.
593 : */
594 : enum GNUNET_GenericReturnValue check_transfer_result;
595 :
596 : /**
597 : * Set to error code, if any.
598 : */
599 : enum TALER_ErrorCode ec;
600 :
601 : /**
602 : * Set to true if @e ec indicates a permanent failure.
603 : */
604 : bool failure;
605 : };
606 :
607 :
608 : /**
609 : * This function checks that the information about the coin which
610 : * was paid back by _this_ wire transfer matches what _we_ (the merchant)
611 : * knew about this coin.
612 : *
613 : * @param cls closure with our `struct CheckTransferContext *`
614 : * @param exchange_url URL of the exchange that issued @a coin_pub
615 : * @param amount_with_fee amount the exchange will transfer for this coin
616 : * @param deposit_fee fee the exchange will charge for this coin
617 : * @param refund_fee fee the exchange will charge for refunding this coin
618 : * @param wire_fee paid wire fee
619 : * @param h_wire hash of merchant's wire details
620 : * @param deposit_timestamp when did the exchange receive the deposit
621 : * @param refund_deadline until when are refunds allowed
622 : * @param exchange_sig signature by the exchange
623 : * @param exchange_pub exchange signing key used for @a exchange_sig
624 : */
625 : static void
626 9 : check_transfer (void *cls,
627 : const char *exchange_url,
628 : const struct TALER_Amount *amount_with_fee,
629 : const struct TALER_Amount *deposit_fee,
630 : const struct TALER_Amount *refund_fee,
631 : const struct TALER_Amount *wire_fee,
632 : const struct TALER_MerchantWireHashP *h_wire,
633 : struct GNUNET_TIME_Timestamp deposit_timestamp,
634 : struct GNUNET_TIME_Timestamp refund_deadline,
635 : const struct TALER_ExchangeSignatureP *exchange_sig,
636 : const struct TALER_ExchangePublicKeyP *exchange_pub)
637 : {
638 9 : struct CheckTransferContext *ctc = cls;
639 9 : const struct TALER_TrackTransferDetails *ttd = ctc->current_detail;
640 :
641 9 : if (GNUNET_SYSERR == ctc->check_transfer_result)
642 : {
643 0 : GNUNET_break (0);
644 0 : return; /* already had a serious issue; odd that we're called more than once as well... */
645 : }
646 9 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
647 : "Checking coin with value %s\n",
648 : TALER_amount2s (amount_with_fee));
649 9 : if ( (GNUNET_OK !=
650 9 : TALER_amount_cmp_currency (amount_with_fee,
651 9 : &ttd->coin_value)) ||
652 9 : (0 != TALER_amount_cmp (amount_with_fee,
653 : &ttd->coin_value)) )
654 : {
655 : /* Disagreement between the exchange and us about how much this
656 : coin is worth! */
657 0 : GNUNET_break_op (0);
658 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
659 : "Disagreement about coin value %s\n",
660 : TALER_amount2s (amount_with_fee));
661 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
662 : "Exchange gave it a value of %s\n",
663 : TALER_amount2s (&ttd->coin_value));
664 0 : ctc->check_transfer_result = GNUNET_SYSERR;
665 : /* Build the `TrackTransferConflictDetails` */
666 0 : ctc->ec = TALER_EC_MERCHANT_PRIVATE_POST_TRANSFERS_CONFLICTING_REPORTS;
667 0 : ctc->failure = true;
668 : /* FIXME-#9426: this should be reported to the auditor (once the auditor has an API for this) */
669 0 : return;
670 : }
671 9 : if ( (GNUNET_OK !=
672 9 : TALER_amount_cmp_currency (deposit_fee,
673 9 : &ttd->coin_fee)) ||
674 9 : (0 != TALER_amount_cmp (deposit_fee,
675 : &ttd->coin_fee)) )
676 : {
677 : /* Disagreement between the exchange and us about how much this
678 : coin is worth! */
679 0 : GNUNET_break_op (0);
680 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
681 : "Expected fee is %s\n",
682 : TALER_amount2s (&ttd->coin_fee));
683 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
684 : "Fee claimed by exchange is %s\n",
685 : TALER_amount2s (deposit_fee));
686 0 : ctc->check_transfer_result = GNUNET_SYSERR;
687 : /* Build the `TrackTransferConflictDetails` */
688 0 : ctc->ec = TALER_EC_MERCHANT_PRIVATE_POST_TRANSFERS_CONFLICTING_REPORTS;
689 0 : ctc->failure = true;
690 : /* FIXME-#9426: this should be reported to the auditor (once the auditor has an API for this) */
691 0 : return;
692 : }
693 9 : ctc->check_transfer_result = GNUNET_OK;
694 : }
695 :
696 :
697 : /**
698 : * Function called with detailed wire transfer data, including all
699 : * of the coin transactions that were combined into the wire transfer.
700 : *
701 : * @param cls closure a `struct Inquiry *`
702 : * @param tgr response details
703 : */
704 : static void
705 10 : wire_transfer_cb (void *cls,
706 : const struct TALER_EXCHANGE_TransfersGetResponse *tgr)
707 : {
708 10 : struct Inquiry *w = cls;
709 10 : struct Exchange *e = w->exchange;
710 10 : const struct TALER_EXCHANGE_TransferData *td = NULL;
711 :
712 10 : e->exchange_inquiries--;
713 10 : w->wdh = NULL;
714 10 : if (EXCHANGE_INQUIRY_LIMIT - 1 == e->exchange_inquiries)
715 0 : launch_inquiries_at_exchange (e);
716 10 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
717 : "Got response code %u from exchange for GET /transfers/$WTID\n",
718 : tgr->hr.http_status);
719 10 : switch (tgr->hr.http_status)
720 : {
721 10 : case MHD_HTTP_OK:
722 10 : td = &tgr->details.ok.td;
723 10 : w->execution_time = td->execution_time;
724 10 : e->transfer_delay = GNUNET_TIME_UNIT_ZERO;
725 10 : break;
726 0 : case MHD_HTTP_BAD_REQUEST:
727 : case MHD_HTTP_FORBIDDEN:
728 0 : update_transaction_status (w,
729 0 : GNUNET_TIME_UNIT_FOREVER_ABS,
730 : TALER_EC_MERCHANT_EXCHANGE_TRANSFERS_HARD_FAILURE,
731 : true,
732 : false);
733 0 : end_inquiry (w);
734 0 : return;
735 0 : case MHD_HTTP_NOT_FOUND:
736 0 : update_transaction_status (w,
737 0 : GNUNET_TIME_UNIT_FOREVER_ABS,
738 : TALER_EC_MERCHANT_EXCHANGE_TRANSFERS_FATAL_NOT_FOUND,
739 : true,
740 : false);
741 0 : end_inquiry (w);
742 0 : return;
743 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
744 : case MHD_HTTP_BAD_GATEWAY:
745 : case MHD_HTTP_GATEWAY_TIMEOUT:
746 0 : e->transfer_delay = GNUNET_TIME_STD_BACKOFF (e->transfer_delay);
747 0 : update_transaction_status (w,
748 : GNUNET_TIME_relative_to_absolute (
749 : e->transfer_delay),
750 : TALER_EC_MERCHANT_EXCHANGE_TRANSFERS_TRANSIENT_FAILURE,
751 : false,
752 : false);
753 0 : end_inquiry (w);
754 0 : return;
755 0 : default:
756 0 : e->transfer_delay = GNUNET_TIME_STD_BACKOFF (e->transfer_delay);
757 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
758 : "Unexpected HTTP status %u\n",
759 : tgr->hr.http_status);
760 0 : update_transaction_status (w,
761 : GNUNET_TIME_relative_to_absolute (
762 : e->transfer_delay),
763 : TALER_EC_MERCHANT_EXCHANGE_TRANSFERS_TRANSIENT_FAILURE,
764 : false,
765 : false);
766 0 : end_inquiry (w);
767 0 : return;
768 : }
769 10 : db_plugin->preflight (db_plugin->cls);
770 :
771 : {
772 : enum GNUNET_DB_QueryStatus qs;
773 :
774 10 : qs = db_plugin->insert_transfer_details (db_plugin->cls,
775 10 : w->instance_id,
776 10 : w->exchange->exchange_url,
777 : w->payto_uri,
778 10 : &w->wtid,
779 : td);
780 10 : if (0 > qs)
781 : {
782 : /* Always report on DB error as well to enable diagnostics */
783 0 : GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
784 0 : global_ret = EXIT_FAILURE;
785 0 : GNUNET_SCHEDULER_shutdown ();
786 0 : return;
787 : }
788 10 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
789 : {
790 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
791 : "Transfer already known. Ignoring duplicate.\n");
792 0 : return;
793 : }
794 : }
795 :
796 : {
797 10 : struct CheckTransferContext ctc = {
798 : .ec = TALER_EC_NONE,
799 : .failure = false
800 : };
801 :
802 20 : for (unsigned int i = 0; i<td->details_length; i++)
803 : {
804 10 : const struct TALER_TrackTransferDetails *ttd = &td->details[i];
805 : enum GNUNET_DB_QueryStatus qs;
806 :
807 10 : if (TALER_EC_NONE != ctc.ec)
808 0 : break; /* already encountered an error */
809 10 : ctc.current_offset = i;
810 10 : ctc.current_detail = ttd;
811 : /* Set the coin as "never seen" before. */
812 10 : ctc.check_transfer_result = GNUNET_NO;
813 10 : qs = db_plugin->lookup_deposits_by_contract_and_coin (
814 10 : db_plugin->cls,
815 10 : w->instance_id,
816 : &ttd->h_contract_terms,
817 : &ttd->coin_pub,
818 : &check_transfer,
819 : &ctc);
820 10 : switch (qs)
821 : {
822 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
823 0 : GNUNET_break (0);
824 0 : ctc.ec = TALER_EC_GENERIC_DB_FETCH_FAILED;
825 0 : break;
826 0 : case GNUNET_DB_STATUS_HARD_ERROR:
827 0 : GNUNET_break (0);
828 0 : ctc.ec = TALER_EC_GENERIC_DB_FETCH_FAILED;
829 0 : break;
830 1 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
831 : /* The exchange says we made this deposit, but WE do not
832 : recall making it (corrupted / unreliable database?)!
833 : Well, let's say thanks and accept the money! */
834 1 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
835 : "Failed to find payment data in DB\n");
836 1 : ctc.check_transfer_result = GNUNET_OK;
837 1 : break;
838 9 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
839 9 : break;
840 : }
841 10 : switch (ctc.check_transfer_result)
842 : {
843 0 : case GNUNET_NO:
844 : /* Internal error: how can we have called #check_transfer()
845 : but still have no result? */
846 0 : GNUNET_break (0);
847 0 : ctc.ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
848 0 : return;
849 0 : case GNUNET_SYSERR:
850 : /* #check_transfer() failed, report conflict! */
851 0 : GNUNET_break_op (0);
852 0 : GNUNET_assert (TALER_EC_NONE != ctc.ec);
853 0 : break;
854 10 : case GNUNET_OK:
855 10 : break;
856 : }
857 : }
858 10 : if (TALER_EC_NONE != ctc.ec)
859 : {
860 0 : update_transaction_status (
861 : w,
862 0 : ctc.failure
863 : ? GNUNET_TIME_UNIT_FOREVER_ABS
864 0 : : GNUNET_TIME_relative_to_absolute (
865 : GNUNET_TIME_UNIT_MINUTES),
866 : ctc.ec,
867 0 : ctc.failure,
868 : false);
869 0 : end_inquiry (w);
870 0 : return;
871 : }
872 : }
873 :
874 10 : if (GNUNET_SYSERR ==
875 10 : check_wire_fee (w,
876 : td->execution_time,
877 : &td->wire_fee))
878 : {
879 0 : GNUNET_break_op (0);
880 0 : update_transaction_status (w,
881 0 : GNUNET_TIME_UNIT_FOREVER_ABS,
882 : TALER_EC_MERCHANT_PRIVATE_POST_TRANSFERS_BAD_WIRE_FEE,
883 : true,
884 : false);
885 0 : end_inquiry (w);
886 0 : return;
887 : }
888 :
889 10 : if ( (GNUNET_OK !=
890 10 : TALER_amount_cmp_currency (&td->total_amount,
891 20 : &w->total)) ||
892 : (0 !=
893 10 : TALER_amount_cmp (&td->total_amount,
894 10 : &w->total)) )
895 : {
896 0 : GNUNET_break_op (0);
897 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
898 : "Wire transfer total value was %s\n",
899 : TALER_amount2s (&w->total));
900 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
901 : "Exchange claimed total value to be %s\n",
902 : TALER_amount2s (&td->total_amount));
903 0 : update_transaction_status (w,
904 0 : GNUNET_TIME_UNIT_FOREVER_ABS,
905 : TALER_EC_MERCHANT_EXCHANGE_TRANSFERS_CONFLICTING_TRANSFERS,
906 : true,
907 : false);
908 0 : end_inquiry (w);
909 0 : return;
910 : }
911 : /* set transaction to successful */
912 10 : update_transaction_status (w,
913 10 : GNUNET_TIME_UNIT_FOREVER_ABS,
914 : TALER_EC_NONE,
915 : false,
916 : true);
917 10 : end_inquiry (w);
918 : }
919 :
920 :
921 : /**
922 : * Initiate download from an exchange for a given inquiry.
923 : *
924 : * @param cls a `struct Inquiry *`
925 : */
926 : static void
927 10 : exchange_request (void *cls)
928 : {
929 10 : struct Inquiry *w = cls;
930 10 : struct Exchange *e = w->exchange;
931 :
932 10 : w->task = NULL;
933 10 : if (NULL == e->keys)
934 0 : return;
935 20 : w->wdh = TALER_EXCHANGE_transfers_get (
936 : ctx,
937 10 : e->exchange_url,
938 : e->keys,
939 10 : &w->wtid,
940 : &wire_transfer_cb,
941 : w);
942 10 : if (NULL == w->wdh)
943 : {
944 0 : GNUNET_break (0);
945 0 : e->exchange_inquiries--;
946 0 : e->transfer_delay = GNUNET_TIME_STD_BACKOFF (e->transfer_delay);
947 0 : update_transaction_status (w,
948 : GNUNET_TIME_relative_to_absolute (
949 : e->transfer_delay),
950 : TALER_EC_MERCHANT_EXCHANGE_TRANSFERS_TRANSIENT_FAILURE,
951 : false,
952 : false);
953 0 : end_inquiry (w);
954 0 : return;
955 : }
956 : /* Wait at least 1m for the network transfer */
957 10 : update_transaction_status (w,
958 : GNUNET_TIME_relative_to_absolute (
959 : GNUNET_TIME_UNIT_MINUTES),
960 : TALER_EC_MERCHANT_EXCHANGE_TRANSFERS_AWAITING_LIST,
961 : false,
962 : false);
963 : }
964 :
965 :
966 : /**
967 : * Function called with information about a transfer we
968 : * should ask the exchange about.
969 : *
970 : * @param cls closure (NULL)
971 : * @param rowid row of the transfer in the merchant database
972 : * @param instance_id instance that received the transfer
973 : * @param exchange_url base URL of the exchange that initiated the transfer
974 : * @param payto_uri account of the merchant that received the transfer
975 : * @param wtid wire transfer subject identifying the aggregation
976 : * @param total total amount that was wired
977 : * @param next_attempt when should we next try to interact with the exchange
978 : */
979 : static void
980 10 : start_inquiry (
981 : void *cls,
982 : uint64_t rowid,
983 : const char *instance_id,
984 : const char *exchange_url,
985 : struct TALER_FullPayto payto_uri,
986 : const struct TALER_WireTransferIdentifierRawP *wtid,
987 : const struct TALER_Amount *total,
988 : struct GNUNET_TIME_Absolute next_attempt)
989 : {
990 : struct Exchange *e;
991 : struct Inquiry *w;
992 :
993 : (void) cls;
994 10 : if (GNUNET_TIME_absolute_is_future (next_attempt))
995 : {
996 0 : if (NULL == task)
997 0 : task = GNUNET_SCHEDULER_add_at (next_attempt,
998 : &find_work,
999 : NULL);
1000 0 : return;
1001 : }
1002 10 : e = find_exchange (exchange_url);
1003 10 : for (w = e->w_head; NULL != w; w = w->next)
1004 : {
1005 0 : if (0 == GNUNET_memcmp (&w->wtid,
1006 : wtid))
1007 : {
1008 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1009 : "Already processing inquiry. Aborting ongoing inquiry\n");
1010 0 : end_inquiry (w);
1011 0 : break;
1012 : }
1013 : }
1014 :
1015 10 : active_inquiries++;
1016 10 : w = GNUNET_new (struct Inquiry);
1017 10 : w->payto_uri.full_payto = GNUNET_strdup (payto_uri.full_payto);
1018 10 : w->instance_id = GNUNET_strdup (instance_id);
1019 10 : w->rowid = rowid;
1020 10 : w->wtid = *wtid;
1021 10 : w->total = *total;
1022 10 : GNUNET_CONTAINER_DLL_insert (e->w_head,
1023 : e->w_tail,
1024 : w);
1025 10 : w->exchange = e;
1026 10 : if (NULL != w->exchange->keys)
1027 10 : w->task = GNUNET_SCHEDULER_add_now (&exchange_request,
1028 : w);
1029 : /* Wait at least 1 minute for /keys */
1030 10 : update_transaction_status (w,
1031 : GNUNET_TIME_relative_to_absolute (
1032 : GNUNET_TIME_UNIT_MINUTES),
1033 : TALER_EC_MERCHANT_EXCHANGE_TRANSFERS_AWAITING_KEYS,
1034 : false,
1035 : false);
1036 : }
1037 :
1038 :
1039 : static void
1040 10 : find_work (void *cls)
1041 : {
1042 : enum GNUNET_DB_QueryStatus qs;
1043 : int limit;
1044 :
1045 : (void) cls;
1046 10 : task = NULL;
1047 10 : GNUNET_assert (OPEN_INQUIRY_LIMIT >= active_inquiries);
1048 10 : limit = OPEN_INQUIRY_LIMIT - active_inquiries;
1049 10 : if (0 == limit)
1050 : {
1051 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1052 : "Not looking for work: at limit\n");
1053 0 : at_limit = true;
1054 0 : return;
1055 : }
1056 10 : at_limit = false;
1057 10 : qs = db_plugin->select_open_transfers (db_plugin->cls,
1058 : limit,
1059 : &start_inquiry,
1060 : NULL);
1061 10 : if (qs < 0)
1062 : {
1063 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1064 : "Failed to obtain open transfers from database\n");
1065 0 : GNUNET_SCHEDULER_shutdown ();
1066 0 : return;
1067 : }
1068 10 : if (qs == limit)
1069 : {
1070 : /* DB limited response, re-trigger DB interaction
1071 : the moment we significantly fall below the
1072 : limit */
1073 0 : at_limit = true;
1074 : }
1075 10 : if (0 == active_inquiries)
1076 : {
1077 0 : if (test_mode)
1078 : {
1079 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1080 : "No more open inquiries and in test mode. Existing.\n");
1081 0 : GNUNET_SCHEDULER_shutdown ();
1082 0 : return;
1083 : }
1084 0 : GNUNET_log (
1085 : GNUNET_ERROR_TYPE_INFO,
1086 : "No open inquiries found, waiting for notification to resume\n");
1087 : }
1088 : }
1089 :
1090 :
1091 : /**
1092 : * Function called when transfers are added to the merchant database. We look
1093 : * for more work.
1094 : *
1095 : * @param cls closure (NULL)
1096 : * @param extra additional event data provided
1097 : * @param extra_size number of bytes in @a extra
1098 : */
1099 : static void
1100 0 : transfer_added (void *cls,
1101 : const void *extra,
1102 : size_t extra_size)
1103 : {
1104 : (void) cls;
1105 : (void) extra;
1106 : (void) extra_size;
1107 0 : if (active_inquiries > OPEN_INQUIRY_LIMIT / 2)
1108 : {
1109 : /* Trigger DB only once we are substantially below the limit */
1110 0 : at_limit = true;
1111 0 : return;
1112 : }
1113 0 : if (NULL != task)
1114 0 : return;
1115 0 : task = GNUNET_SCHEDULER_add_now (&find_work,
1116 : NULL);
1117 : }
1118 :
1119 :
1120 : /**
1121 : * Function called when keys were changed in the
1122 : * merchant database. Updates ours.
1123 : *
1124 : * @param cls closure (NULL)
1125 : * @param extra additional event data provided
1126 : * @param extra_size number of bytes in @a extra
1127 : */
1128 : static void
1129 0 : keys_changed (void *cls,
1130 : const void *extra,
1131 : size_t extra_size)
1132 : {
1133 0 : const char *url = extra;
1134 : struct Exchange *e;
1135 :
1136 : (void) cls;
1137 0 : if ( (NULL == extra) ||
1138 : (0 == extra_size) )
1139 : {
1140 0 : GNUNET_break (0);
1141 0 : return;
1142 : }
1143 0 : if ('\0' != url[extra_size - 1])
1144 : {
1145 0 : GNUNET_break (0);
1146 0 : return;
1147 : }
1148 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1149 : "Received keys change notification: reload `%s'\n",
1150 : url);
1151 0 : e = find_exchange (url);
1152 0 : sync_keys (e);
1153 : }
1154 :
1155 :
1156 : /**
1157 : * First task.
1158 : *
1159 : * @param cls closure, NULL
1160 : * @param args remaining command-line arguments
1161 : * @param cfgfile name of the configuration file used (for saving, can be NULL!)
1162 : * @param c configuration
1163 : */
1164 : static void
1165 10 : run (void *cls,
1166 : char *const *args,
1167 : const char *cfgfile,
1168 : const struct GNUNET_CONFIGURATION_Handle *c)
1169 : {
1170 : (void) args;
1171 : (void) cfgfile;
1172 :
1173 10 : cfg = c;
1174 10 : GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
1175 : NULL);
1176 10 : ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
1177 : &rc);
1178 10 : rc = GNUNET_CURL_gnunet_rc_create (ctx);
1179 10 : if (NULL == ctx)
1180 : {
1181 0 : GNUNET_break (0);
1182 0 : GNUNET_SCHEDULER_shutdown ();
1183 0 : global_ret = EXIT_FAILURE;
1184 0 : return;
1185 : }
1186 10 : if (NULL ==
1187 10 : (db_plugin = TALER_MERCHANTDB_plugin_load (cfg)))
1188 : {
1189 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1190 : "Failed to initialize DB subsystem\n");
1191 0 : GNUNET_SCHEDULER_shutdown ();
1192 0 : global_ret = EXIT_NOTCONFIGURED;
1193 0 : return;
1194 : }
1195 10 : if (GNUNET_OK !=
1196 10 : db_plugin->connect (db_plugin->cls))
1197 : {
1198 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1199 : "Failed to connect to database. Consider running taler-merchant-dbinit!\n");
1200 0 : GNUNET_SCHEDULER_shutdown ();
1201 0 : global_ret = EXIT_FAILURE;
1202 0 : return;
1203 : }
1204 : {
1205 10 : struct GNUNET_DB_EventHeaderP es = {
1206 10 : .size = htons (sizeof (es)),
1207 10 : .type = htons (TALER_DBEVENT_MERCHANT_WIRE_TRANSFER_CONFIRMED)
1208 : };
1209 :
1210 20 : eh = db_plugin->event_listen (db_plugin->cls,
1211 : &es,
1212 10 : GNUNET_TIME_UNIT_FOREVER_REL,
1213 : &transfer_added,
1214 : NULL);
1215 : }
1216 : {
1217 10 : struct GNUNET_DB_EventHeaderP es = {
1218 10 : .size = htons (sizeof (es)),
1219 10 : .type = htons (TALER_DBEVENT_MERCHANT_EXCHANGE_KEYS)
1220 : };
1221 :
1222 : eh_keys
1223 20 : = db_plugin->event_listen (db_plugin->cls,
1224 : &es,
1225 10 : GNUNET_TIME_UNIT_FOREVER_REL,
1226 : &keys_changed,
1227 : NULL);
1228 : }
1229 :
1230 10 : GNUNET_assert (NULL == task);
1231 10 : task = GNUNET_SCHEDULER_add_now (&find_work,
1232 : NULL);
1233 : }
1234 :
1235 :
1236 : /**
1237 : * The main function of taler-merchant-reconciliation
1238 : *
1239 : * @param argc number of arguments from the command line
1240 : * @param argv command line arguments
1241 : * @return 0 ok, 1 on error
1242 : */
1243 : int
1244 10 : main (int argc,
1245 : char *const *argv)
1246 : {
1247 10 : struct GNUNET_GETOPT_CommandLineOption options[] = {
1248 10 : GNUNET_GETOPT_option_timetravel ('T',
1249 : "timetravel"),
1250 10 : GNUNET_GETOPT_option_flag ('t',
1251 : "test",
1252 : "run in test mode and exit when idle",
1253 : &test_mode),
1254 10 : GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION),
1255 : GNUNET_GETOPT_OPTION_END
1256 : };
1257 : enum GNUNET_GenericReturnValue ret;
1258 :
1259 10 : ret = GNUNET_PROGRAM_run (
1260 : TALER_MERCHANT_project_data (),
1261 : argc, argv,
1262 : "taler-merchant-reconciliation",
1263 : gettext_noop (
1264 : "background process that reconciles bank transfers with orders by asking the exchange"),
1265 : options,
1266 : &run, NULL);
1267 10 : if (GNUNET_SYSERR == ret)
1268 0 : return EXIT_INVALIDARGUMENT;
1269 10 : if (GNUNET_NO == ret)
1270 0 : return EXIT_SUCCESS;
1271 10 : if ( (found_problem) &&
1272 0 : (0 == global_ret) )
1273 0 : global_ret = 7;
1274 10 : return global_ret;
1275 : }
1276 :
1277 :
1278 : /* end of taler-merchant-reconciliation.c */
|