Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2024, 2025 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-depositcheck.c
18 : * @brief Process that inquires with the exchange for deposits that should have been wired
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_merchant_util.h"
27 : #include "taler_merchantdb_lib.h"
28 : #include "taler_merchantdb_plugin.h"
29 : #include <taler/taler_dbevents.h>
30 :
31 : /**
32 : * How many requests do we make at most in parallel to the same exchange?
33 : */
34 : #define CONCURRENCY_LIMIT 32
35 :
36 : /**
37 : * How long do we not try a deposit check if the deposit
38 : * was put on hold due to a KYC/AML block?
39 : */
40 : #define KYC_RETRY_DELAY GNUNET_TIME_UNIT_HOURS
41 :
42 : /**
43 : * Information we keep per exchange.
44 : */
45 : struct Child
46 : {
47 :
48 : /**
49 : * Kept in a DLL.
50 : */
51 : struct Child *next;
52 :
53 : /**
54 : * Kept in a DLL.
55 : */
56 : struct Child *prev;
57 :
58 : /**
59 : * The child process.
60 : */
61 : struct GNUNET_OS_Process *process;
62 :
63 : /**
64 : * Wait handle.
65 : */
66 : struct GNUNET_ChildWaitHandle *cwh;
67 :
68 : /**
69 : * Which exchange is this state for?
70 : */
71 : char *base_url;
72 :
73 : /**
74 : * Task to restart the child.
75 : */
76 : struct GNUNET_SCHEDULER_Task *rt;
77 :
78 : /**
79 : * When should the child be restarted at the earliest?
80 : */
81 : struct GNUNET_TIME_Absolute next_start;
82 :
83 : /**
84 : * Current minimum delay between restarts, grows
85 : * exponentially if child exits before this time.
86 : */
87 : struct GNUNET_TIME_Relative rd;
88 :
89 : };
90 :
91 :
92 : /**
93 : * Information we keep per exchange interaction.
94 : */
95 : struct ExchangeInteraction
96 : {
97 : /**
98 : * Kept in a DLL.
99 : */
100 : struct ExchangeInteraction *next;
101 :
102 : /**
103 : * Kept in a DLL.
104 : */
105 : struct ExchangeInteraction *prev;
106 :
107 : /**
108 : * Handle for exchange interaction.
109 : */
110 : struct TALER_EXCHANGE_DepositGetHandle *dgh;
111 :
112 : /**
113 : * Wire deadline for the deposit.
114 : */
115 : struct GNUNET_TIME_Absolute wire_deadline;
116 :
117 : /**
118 : * Current value for the retry backoff
119 : */
120 : struct GNUNET_TIME_Relative retry_backoff;
121 :
122 : /**
123 : * Target account hash of the deposit.
124 : */
125 : struct TALER_MerchantWireHashP h_wire;
126 :
127 : /**
128 : * Deposited amount.
129 : */
130 : struct TALER_Amount amount_with_fee;
131 :
132 : /**
133 : * Deposit fee paid.
134 : */
135 : struct TALER_Amount deposit_fee;
136 :
137 : /**
138 : * Public key of the deposited coin.
139 : */
140 : struct TALER_CoinSpendPublicKeyP coin_pub;
141 :
142 : /**
143 : * Hash over the @e contract_terms.
144 : */
145 : struct TALER_PrivateContractHashP h_contract_terms;
146 :
147 : /**
148 : * Merchant instance's private key.
149 : */
150 : struct TALER_MerchantPrivateKeyP merchant_priv;
151 :
152 : /**
153 : * Serial number of the row in the deposits table
154 : * that we are processing.
155 : */
156 : uint64_t deposit_serial;
157 :
158 : /**
159 : * The instance the deposit belongs to.
160 : */
161 : char *instance_id;
162 :
163 : };
164 :
165 :
166 : /**
167 : * Head of list of children we forked.
168 : */
169 : static struct Child *c_head;
170 :
171 : /**
172 : * Tail of list of children we forked.
173 : */
174 : static struct Child *c_tail;
175 :
176 : /**
177 : * Key material of the exchange.
178 : */
179 : static struct TALER_EXCHANGE_Keys *keys;
180 :
181 : /**
182 : * Head of list of active exchange interactions.
183 : */
184 : static struct ExchangeInteraction *w_head;
185 :
186 : /**
187 : * Tail of list of active exchange interactions.
188 : */
189 : static struct ExchangeInteraction *w_tail;
190 :
191 : /**
192 : * Number of active entries in the @e w_head list.
193 : */
194 : static uint64_t w_count;
195 :
196 : /**
197 : * Notification handler from database on new work.
198 : */
199 : static struct GNUNET_DB_EventHandler *eh;
200 :
201 : /**
202 : * Notification handler from database on new keys.
203 : */
204 : static struct GNUNET_DB_EventHandler *keys_eh;
205 :
206 : /**
207 : * The merchant's configuration.
208 : */
209 : static const struct GNUNET_CONFIGURATION_Handle *cfg;
210 :
211 : /**
212 : * Name of the configuration file we use.
213 : */
214 : static char *cfg_filename;
215 :
216 : /**
217 : * Our database plugin.
218 : */
219 : static struct TALER_MERCHANTDB_Plugin *db_plugin;
220 :
221 : /**
222 : * Next wire deadline that @e task is scheduled for.
223 : */
224 : static struct GNUNET_TIME_Absolute next_deadline;
225 :
226 : /**
227 : * Next task to run, if any.
228 : */
229 : static struct GNUNET_SCHEDULER_Task *task;
230 :
231 : /**
232 : * Handle to the context for interacting with the exchange.
233 : */
234 : static struct GNUNET_CURL_Context *ctx;
235 :
236 : /**
237 : * Scheduler context for running the @e ctx.
238 : */
239 : static struct GNUNET_CURL_RescheduleContext *rc;
240 :
241 : /**
242 : * Which exchange are we monitoring? NULL if we
243 : * are the parent of the workers.
244 : */
245 : static char *exchange_url;
246 :
247 : /**
248 : * Value to return from main(). 0 on success, non-zero on errors.
249 : */
250 : static int global_ret;
251 :
252 : /**
253 : * #GNUNET_YES if we are in test mode and should exit when idle.
254 : */
255 : static int test_mode;
256 :
257 :
258 : /**
259 : * We're being aborted with CTRL-C (or SIGTERM). Shut down.
260 : *
261 : * @param cls closure
262 : */
263 : static void
264 15 : shutdown_task (void *cls)
265 : {
266 : struct Child *c;
267 : struct ExchangeInteraction *w;
268 :
269 : (void) cls;
270 15 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
271 : "Running shutdown\n");
272 15 : if (NULL != eh)
273 : {
274 10 : db_plugin->event_listen_cancel (eh);
275 10 : eh = NULL;
276 : }
277 15 : if (NULL != keys_eh)
278 : {
279 10 : db_plugin->event_listen_cancel (keys_eh);
280 10 : keys_eh = NULL;
281 : }
282 15 : if (NULL != task)
283 : {
284 0 : GNUNET_SCHEDULER_cancel (task);
285 0 : task = NULL;
286 : }
287 15 : while (NULL != (w = w_head))
288 : {
289 0 : GNUNET_CONTAINER_DLL_remove (w_head,
290 : w_tail,
291 : w);
292 0 : if (NULL != w->dgh)
293 : {
294 0 : TALER_EXCHANGE_deposits_get_cancel (w->dgh);
295 0 : w->dgh = NULL;
296 : }
297 0 : w_count--;
298 0 : GNUNET_free (w->instance_id);
299 0 : GNUNET_free (w);
300 : }
301 20 : while (NULL != (c = c_head))
302 : {
303 5 : GNUNET_CONTAINER_DLL_remove (c_head,
304 : c_tail,
305 : c);
306 5 : if (NULL != c->rt)
307 : {
308 0 : GNUNET_SCHEDULER_cancel (c->rt);
309 0 : c->rt = NULL;
310 : }
311 5 : if (NULL != c->cwh)
312 : {
313 0 : GNUNET_wait_child_cancel (c->cwh);
314 0 : c->cwh = NULL;
315 : }
316 5 : if (NULL != c->process)
317 : {
318 0 : enum GNUNET_OS_ProcessStatusType type
319 : = GNUNET_OS_PROCESS_UNKNOWN;
320 0 : unsigned long code = 0;
321 :
322 0 : GNUNET_break (0 ==
323 : GNUNET_OS_process_kill (c->process,
324 : SIGTERM));
325 0 : GNUNET_break (GNUNET_OK ==
326 : GNUNET_OS_process_wait_status (c->process,
327 : &type,
328 : &code));
329 0 : if ( (GNUNET_OS_PROCESS_EXITED != type) ||
330 0 : (0 != code) )
331 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
332 : "Process for exchange %s had trouble (%d/%d)\n",
333 : c->base_url,
334 : (int) type,
335 : (int) code);
336 0 : GNUNET_OS_process_destroy (c->process);
337 : }
338 5 : GNUNET_free (c->base_url);
339 5 : GNUNET_free (c);
340 : }
341 15 : if (NULL != db_plugin)
342 : {
343 10 : db_plugin->rollback (db_plugin->cls); /* just in case */
344 10 : TALER_MERCHANTDB_plugin_unload (db_plugin);
345 10 : db_plugin = NULL;
346 : }
347 15 : cfg = NULL;
348 15 : if (NULL != ctx)
349 : {
350 10 : GNUNET_CURL_fini (ctx);
351 10 : ctx = NULL;
352 : }
353 15 : if (NULL != rc)
354 : {
355 10 : GNUNET_CURL_gnunet_rc_destroy (rc);
356 10 : rc = NULL;
357 : }
358 15 : }
359 :
360 :
361 : /**
362 : * Task to get more deposits to work on from the database.
363 : *
364 : * @param cls NULL
365 : */
366 : static void
367 : select_work (void *cls);
368 :
369 :
370 : /**
371 : * Make sure to run the select_work() task at
372 : * the @a next_deadline.
373 : *
374 : * @param deadline time when work becomes ready
375 : */
376 : static void
377 0 : run_at (struct GNUNET_TIME_Absolute deadline)
378 : {
379 0 : if ( (NULL != task) &&
380 0 : (GNUNET_TIME_absolute_cmp (deadline,
381 : >,
382 : next_deadline)) )
383 : {
384 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
385 : "Not scheduling for %s yet, already have earlier task pending\n",
386 : GNUNET_TIME_absolute2s (deadline));
387 0 : return;
388 : }
389 0 : if (NULL == keys)
390 : {
391 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
392 : "Not scheduling for %s yet, no /keys available\n",
393 : GNUNET_TIME_absolute2s (deadline));
394 0 : return; /* too early */
395 : }
396 0 : next_deadline = deadline;
397 0 : if (NULL != task)
398 0 : GNUNET_SCHEDULER_cancel (task);
399 0 : task = GNUNET_SCHEDULER_add_at (deadline,
400 : &select_work,
401 : NULL);
402 : }
403 :
404 :
405 : /**
406 : * Function called with detailed wire transfer data.
407 : *
408 : * @param cls closure with a `struct ExchangeInteraction *`
409 : * @param dr HTTP response data
410 : */
411 : static void
412 8 : deposit_get_cb (
413 : void *cls,
414 : const struct TALER_EXCHANGE_GetDepositResponse *dr)
415 : {
416 8 : struct ExchangeInteraction *w = cls;
417 : struct GNUNET_TIME_Absolute future_retry;
418 :
419 8 : w->dgh = NULL;
420 : future_retry
421 8 : = GNUNET_TIME_relative_to_absolute (w->retry_backoff);
422 8 : switch (dr->hr.http_status)
423 : {
424 8 : case MHD_HTTP_OK:
425 : {
426 : enum GNUNET_DB_QueryStatus qs;
427 :
428 8 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
429 : "Exchange returned wire transfer over %s for deposited coin %s\n",
430 : TALER_amount2s (&dr->details.ok.coin_contribution),
431 : TALER_B2S (&w->coin_pub));
432 8 : qs = db_plugin->insert_deposit_to_transfer (
433 8 : db_plugin->cls,
434 : w->deposit_serial,
435 8 : &w->h_wire,
436 : exchange_url,
437 : &dr->details.ok);
438 8 : if (qs <= 0)
439 : {
440 0 : GNUNET_break (0);
441 0 : GNUNET_SCHEDULER_shutdown ();
442 0 : return;
443 : }
444 8 : break;
445 : }
446 0 : case MHD_HTTP_ACCEPTED:
447 : {
448 : /* got a 'preliminary' reply from the exchange,
449 : remember our target UUID */
450 : enum GNUNET_DB_QueryStatus qs;
451 : struct GNUNET_TIME_Timestamp now;
452 :
453 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
454 : "Exchange returned KYC requirement (%d) for deposited coin %s\n",
455 : dr->details.accepted.kyc_ok,
456 : TALER_B2S (&w->coin_pub));
457 0 : now = GNUNET_TIME_timestamp_get ();
458 0 : qs = db_plugin->account_kyc_set_failed (
459 0 : db_plugin->cls,
460 0 : w->instance_id,
461 0 : &w->h_wire,
462 : exchange_url,
463 : now,
464 : MHD_HTTP_ACCEPTED,
465 0 : dr->details.accepted.kyc_ok);
466 0 : if (qs < 0)
467 : {
468 0 : GNUNET_break (0);
469 0 : GNUNET_SCHEDULER_shutdown ();
470 0 : return;
471 : }
472 0 : if (dr->details.accepted.kyc_ok)
473 : {
474 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
475 : "Bumping wire transfer deadline in DB to %s as that is when we will retry\n",
476 : GNUNET_TIME_absolute2s (future_retry));
477 0 : qs = db_plugin->update_deposit_confirmation_status (
478 0 : db_plugin->cls,
479 : w->deposit_serial,
480 : true, /* need to try again in the future! */
481 : GNUNET_TIME_absolute_to_timestamp (future_retry),
482 : MHD_HTTP_ACCEPTED,
483 : TALER_EC_NONE,
484 : "Exchange reported 202 Accepted but no KYC block");
485 0 : if (qs < 0)
486 : {
487 0 : GNUNET_break (0);
488 0 : GNUNET_SCHEDULER_shutdown ();
489 0 : return;
490 : }
491 : }
492 : else
493 : {
494 : future_retry
495 0 : = GNUNET_TIME_absolute_max (
496 : future_retry,
497 : GNUNET_TIME_relative_to_absolute (
498 : KYC_RETRY_DELAY));
499 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
500 : "Bumping wire transfer deadline in DB to %s as that is when we will retry\n",
501 : GNUNET_TIME_absolute2s (future_retry));
502 0 : qs = db_plugin->update_deposit_confirmation_status (
503 0 : db_plugin->cls,
504 : w->deposit_serial,
505 : true /* need to try again in the future */,
506 : GNUNET_TIME_absolute_to_timestamp (future_retry),
507 : MHD_HTTP_ACCEPTED,
508 : TALER_EC_NONE,
509 : "Exchange reported 202 Accepted due to KYC/AML block");
510 0 : if (qs < 0)
511 : {
512 0 : GNUNET_break (0);
513 0 : GNUNET_SCHEDULER_shutdown ();
514 0 : return;
515 : }
516 : }
517 0 : break;
518 : }
519 0 : default:
520 : {
521 : enum GNUNET_DB_QueryStatus qs;
522 0 : bool retry_needed = false;
523 :
524 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
525 : "Exchange %s returned tracking failure for deposited coin %s: %u\n",
526 : exchange_url,
527 : TALER_B2S (&w->coin_pub),
528 : dr->hr.http_status);
529 : /* rough classification by HTTP status group */
530 0 : switch (dr->hr.http_status / 100)
531 : {
532 0 : case 0:
533 : /* timeout */
534 0 : retry_needed = true;
535 0 : break;
536 0 : case 1:
537 : case 2:
538 : case 3:
539 : /* very strange */
540 0 : retry_needed = false;
541 0 : break;
542 0 : case 4:
543 : /* likely fatal */
544 0 : retry_needed = false;
545 0 : break;
546 0 : case 5:
547 : /* likely transient */
548 0 : retry_needed = true;
549 0 : break;
550 : }
551 0 : qs = db_plugin->update_deposit_confirmation_status (
552 0 : db_plugin->cls,
553 : w->deposit_serial,
554 : retry_needed,
555 : GNUNET_TIME_absolute_to_timestamp (future_retry),
556 0 : (uint32_t) dr->hr.http_status,
557 0 : dr->hr.ec,
558 0 : dr->hr.hint);
559 0 : if (qs < 0)
560 : {
561 0 : GNUNET_break (0);
562 0 : GNUNET_SCHEDULER_shutdown ();
563 0 : return;
564 : }
565 0 : break;
566 : }
567 : } /* end switch */
568 :
569 8 : GNUNET_CONTAINER_DLL_remove (w_head,
570 : w_tail,
571 : w);
572 8 : w_count--;
573 8 : GNUNET_free (w->instance_id);
574 8 : GNUNET_free (w);
575 8 : GNUNET_assert (NULL != keys);
576 8 : if (0 == w_count)
577 : {
578 : /* We only SELECT() again after having finished
579 : all requests, as otherwise we'll most like
580 : just SELECT() those again that are already
581 : being requested; alternatively, we could
582 : update the retry_time already on SELECT(),
583 : but this should be easier on the DB. */
584 8 : if (NULL != task)
585 0 : GNUNET_SCHEDULER_cancel (task);
586 8 : task = GNUNET_SCHEDULER_add_now (&select_work,
587 : NULL);
588 : }
589 : }
590 :
591 :
592 : /**
593 : * Typically called by `select_work`.
594 : *
595 : * @param cls NULL
596 : * @param deposit_serial identifies the deposit operation
597 : * @param wire_deadline when is the wire due
598 : * @param retry_time current value for the retry backoff
599 : * @param h_contract_terms hash of the contract terms
600 : * @param merchant_priv private key of the merchant
601 : * @param instance_id row ID of the instance
602 : * @param h_wire hash of the merchant's wire account into
603 : * @param amount_with_fee amount the exchange will deposit for this coin
604 : * @param deposit_fee fee the exchange will charge for this coin which the deposit was made
605 : * @param coin_pub public key of the deposited coin
606 : */
607 : static void
608 8 : pending_deposits_cb (
609 : void *cls,
610 : uint64_t deposit_serial,
611 : struct GNUNET_TIME_Absolute wire_deadline,
612 : struct GNUNET_TIME_Absolute retry_time,
613 : const struct TALER_PrivateContractHashP *h_contract_terms,
614 : const struct TALER_MerchantPrivateKeyP *merchant_priv,
615 : const char *instance_id,
616 : const struct TALER_MerchantWireHashP *h_wire,
617 : const struct TALER_Amount *amount_with_fee,
618 : const struct TALER_Amount *deposit_fee,
619 : const struct TALER_CoinSpendPublicKeyP *coin_pub)
620 : {
621 : struct ExchangeInteraction *w;
622 : struct GNUNET_TIME_Absolute mx
623 8 : = GNUNET_TIME_absolute_max (wire_deadline,
624 : retry_time);
625 : struct GNUNET_TIME_Relative retry_backoff;
626 :
627 : (void) cls;
628 8 : if (GNUNET_TIME_absolute_is_future (mx))
629 : {
630 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
631 : "Pending deposit should be checked next at %s\n",
632 : GNUNET_TIME_absolute2s (mx));
633 0 : run_at (mx);
634 0 : return;
635 : }
636 8 : if (GNUNET_TIME_absolute_is_zero (retry_time))
637 0 : retry_backoff = GNUNET_TIME_absolute_get_duration (wire_deadline);
638 : else
639 8 : retry_backoff = GNUNET_TIME_absolute_get_difference (wire_deadline,
640 : retry_time);
641 8 : w = GNUNET_new (struct ExchangeInteraction);
642 8 : w->deposit_serial = deposit_serial;
643 8 : w->wire_deadline = wire_deadline;
644 8 : w->retry_backoff = GNUNET_TIME_randomized_backoff (retry_backoff,
645 : GNUNET_TIME_UNIT_DAYS);
646 8 : w->h_contract_terms = *h_contract_terms;
647 8 : w->merchant_priv = *merchant_priv;
648 8 : w->h_wire = *h_wire;
649 8 : w->amount_with_fee = *amount_with_fee;
650 8 : w->deposit_fee = *deposit_fee;
651 8 : w->coin_pub = *coin_pub;
652 8 : w->instance_id = GNUNET_strdup (instance_id);
653 8 : GNUNET_CONTAINER_DLL_insert (w_head,
654 : w_tail,
655 : w);
656 8 : w_count++;
657 8 : GNUNET_assert (NULL != keys);
658 8 : if (GNUNET_TIME_absolute_is_past (
659 8 : keys->key_data_expiration.abs_time))
660 : {
661 : /* Parent should re-start us, then we will re-fetch /keys */
662 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
663 : "/keys expired, shutting down\n");
664 0 : GNUNET_SCHEDULER_shutdown ();
665 0 : return;
666 : }
667 8 : GNUNET_assert (NULL == w->dgh);
668 8 : w->dgh = TALER_EXCHANGE_deposits_get (
669 : ctx,
670 : exchange_url,
671 : keys,
672 8 : &w->merchant_priv,
673 8 : &w->h_wire,
674 8 : &w->h_contract_terms,
675 8 : &w->coin_pub,
676 8 : GNUNET_TIME_UNIT_ZERO,
677 : &deposit_get_cb,
678 : w);
679 : }
680 :
681 :
682 : /**
683 : * Function called on events received from Postgres.
684 : *
685 : * @param cls closure, NULL
686 : * @param extra additional event data provided, timestamp with wire deadline
687 : * @param extra_size number of bytes in @a extra
688 : */
689 : static void
690 0 : db_notify (void *cls,
691 : const void *extra,
692 : size_t extra_size)
693 : {
694 : struct GNUNET_TIME_Absolute deadline;
695 : struct GNUNET_TIME_AbsoluteNBO nbo_deadline;
696 :
697 : (void) cls;
698 0 : if (sizeof (nbo_deadline) != extra_size)
699 : {
700 0 : GNUNET_break (0);
701 0 : return;
702 : }
703 0 : if (0 != w_count)
704 0 : return; /* already at work! */
705 0 : memcpy (&nbo_deadline,
706 : extra,
707 : extra_size);
708 0 : deadline = GNUNET_TIME_absolute_ntoh (nbo_deadline);
709 0 : run_at (deadline);
710 : }
711 :
712 :
713 : static void
714 18 : select_work (void *cls)
715 : {
716 18 : bool retry = false;
717 18 : uint64_t limit = CONCURRENCY_LIMIT - w_count;
718 :
719 : (void) cls;
720 18 : task = NULL;
721 18 : GNUNET_assert (w_count <= CONCURRENCY_LIMIT);
722 18 : GNUNET_assert (NULL != keys);
723 18 : if (0 == limit)
724 : {
725 0 : GNUNET_break (0);
726 0 : return;
727 : }
728 18 : if (GNUNET_TIME_absolute_is_past (
729 18 : keys->key_data_expiration.abs_time))
730 : {
731 : /* Parent should re-start us, then we will re-fetch /keys */
732 0 : GNUNET_SCHEDULER_shutdown ();
733 0 : return;
734 : }
735 : while (1)
736 0 : {
737 : enum GNUNET_DB_QueryStatus qs;
738 :
739 18 : db_plugin->preflight (db_plugin->cls);
740 18 : if (retry)
741 0 : limit = 1;
742 18 : qs = db_plugin->lookup_pending_deposits (
743 18 : db_plugin->cls,
744 : exchange_url,
745 : limit,
746 : retry,
747 : &pending_deposits_cb,
748 : NULL);
749 18 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
750 : "Looking up pending deposits query status was %d\n",
751 : (int) qs);
752 18 : switch (qs)
753 : {
754 0 : case GNUNET_DB_STATUS_HARD_ERROR:
755 : case GNUNET_DB_STATUS_SOFT_ERROR:
756 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
757 : "Transaction failed!\n");
758 0 : global_ret = EXIT_FAILURE;
759 0 : GNUNET_SCHEDULER_shutdown ();
760 0 : return;
761 10 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
762 10 : if (test_mode)
763 : {
764 10 : GNUNET_SCHEDULER_shutdown ();
765 10 : return;
766 : }
767 0 : if (retry)
768 0 : return; /* nothing left */
769 0 : retry = true;
770 0 : continue;
771 8 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
772 : default:
773 : /* wait for async completion, then select more work. */
774 8 : return;
775 : }
776 : }
777 : }
778 :
779 :
780 : /**
781 : * Start a copy of this process with the exchange URL
782 : * set to the given @a base_url
783 : *
784 : * @param base_url base URL to run with
785 : */
786 : static struct GNUNET_OS_Process *
787 5 : start_worker (const char *base_url)
788 : {
789 : char toff[30];
790 : long long zo;
791 :
792 5 : zo = GNUNET_TIME_get_offset ();
793 5 : GNUNET_snprintf (toff,
794 : sizeof (toff),
795 : "%lld",
796 : zo);
797 5 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
798 : "Launching worker for exchange `%s' using `%s`\n",
799 : base_url,
800 : NULL == cfg_filename
801 : ? "<default>"
802 : : cfg_filename);
803 5 : if (NULL == cfg_filename)
804 0 : return GNUNET_OS_start_process (
805 : GNUNET_OS_INHERIT_STD_ALL,
806 : NULL,
807 : NULL,
808 : NULL,
809 : "taler-merchant-depositcheck",
810 : "taler-merchant-depositcheck",
811 : "-e", base_url,
812 : "-L", "INFO",
813 : "-T", toff,
814 0 : test_mode ? "-t" : NULL,
815 : NULL);
816 5 : return GNUNET_OS_start_process (
817 : GNUNET_OS_INHERIT_STD_ALL,
818 : NULL,
819 : NULL,
820 : NULL,
821 : "taler-merchant-depositcheck",
822 : "taler-merchant-depositcheck",
823 : "-c", cfg_filename,
824 : "-e", base_url,
825 : "-L", "INFO",
826 : "-T", toff,
827 5 : test_mode ? "-t" : NULL,
828 : NULL);
829 : }
830 :
831 :
832 : /**
833 : * Restart worker process for the given child.
834 : *
835 : * @param cls a `struct Child *` that needs a worker.
836 : */
837 : static void
838 : restart_child (void *cls);
839 :
840 :
841 : /**
842 : * Function called upon death or completion of a child process.
843 : *
844 : * @param cls a `struct Child *`
845 : * @param type type of the process
846 : * @param exit_code status code of the process
847 : */
848 : static void
849 5 : child_done_cb (void *cls,
850 : enum GNUNET_OS_ProcessStatusType type,
851 : long unsigned int exit_code)
852 : {
853 5 : struct Child *c = cls;
854 :
855 5 : c->cwh = NULL;
856 5 : GNUNET_OS_process_destroy (c->process);
857 5 : c->process = NULL;
858 5 : if ( (GNUNET_OS_PROCESS_EXITED != type) ||
859 : (0 != exit_code) )
860 : {
861 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
862 : "Process for exchange %s had trouble (%d/%d)\n",
863 : c->base_url,
864 : (int) type,
865 : (int) exit_code);
866 0 : GNUNET_SCHEDULER_shutdown ();
867 0 : global_ret = EXIT_NOTINSTALLED;
868 0 : return;
869 : }
870 5 : if (test_mode &&
871 5 : (! GNUNET_TIME_relative_is_zero (c->rd)) )
872 : {
873 5 : return;
874 : }
875 0 : if (GNUNET_TIME_absolute_is_future (c->next_start))
876 0 : c->rd = GNUNET_TIME_STD_BACKOFF (c->rd);
877 : else
878 0 : c->rd = GNUNET_TIME_UNIT_SECONDS;
879 0 : c->rt = GNUNET_SCHEDULER_add_at (c->next_start,
880 : &restart_child,
881 : c);
882 : }
883 :
884 :
885 : static void
886 5 : restart_child (void *cls)
887 : {
888 5 : struct Child *c = cls;
889 :
890 5 : c->rt = NULL;
891 5 : c->next_start = GNUNET_TIME_relative_to_absolute (c->rd);
892 5 : c->process = start_worker (c->base_url);
893 5 : if (NULL == c->process)
894 : {
895 0 : GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
896 : "exec");
897 0 : global_ret = EXIT_NO_RESTART;
898 0 : GNUNET_SCHEDULER_shutdown ();
899 0 : return;
900 : }
901 5 : c->cwh = GNUNET_wait_child (c->process,
902 : &child_done_cb,
903 : c);
904 : }
905 :
906 :
907 : /**
908 : * Function to iterate over section.
909 : *
910 : * @param cls closure
911 : * @param section name of the section
912 : */
913 : static void
914 209 : cfg_iter_cb (void *cls,
915 : const char *section)
916 : {
917 : char *base_url;
918 : struct Child *c;
919 :
920 209 : if (0 !=
921 209 : strncasecmp (section,
922 : "merchant-exchange-",
923 : strlen ("merchant-exchange-")))
924 204 : return;
925 15 : if (GNUNET_YES ==
926 15 : GNUNET_CONFIGURATION_get_value_yesno (cfg,
927 : section,
928 : "DISABLED"))
929 10 : return;
930 5 : if (GNUNET_OK !=
931 5 : GNUNET_CONFIGURATION_get_value_string (cfg,
932 : section,
933 : "EXCHANGE_BASE_URL",
934 : &base_url))
935 : {
936 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
937 : section,
938 : "EXCHANGE_BASE_URL");
939 0 : return;
940 : }
941 5 : c = GNUNET_new (struct Child);
942 5 : c->rd = GNUNET_TIME_UNIT_SECONDS;
943 5 : c->base_url = base_url;
944 5 : GNUNET_CONTAINER_DLL_insert (c_head,
945 : c_tail,
946 : c);
947 5 : c->rt = GNUNET_SCHEDULER_add_now (&restart_child,
948 : c);
949 : }
950 :
951 :
952 : /**
953 : * Trigger (re)loading of keys from DB.
954 : *
955 : * @param cls NULL
956 : * @param extra base URL of the exchange that changed
957 : * @param extra_len number of bytes in @a extra
958 : */
959 : static void
960 10 : update_exchange_keys (void *cls,
961 : const void *extra,
962 : size_t extra_len)
963 : {
964 10 : const char *url = extra;
965 :
966 10 : if ( (NULL == extra) ||
967 : (0 == extra_len) )
968 : {
969 0 : GNUNET_break (0);
970 0 : return;
971 : }
972 10 : if ('\0' != url[extra_len - 1])
973 : {
974 0 : GNUNET_break (0);
975 0 : return;
976 : }
977 10 : if (0 != strcmp (url,
978 : exchange_url))
979 0 : return; /* not relevant for us */
980 :
981 : {
982 : enum GNUNET_DB_QueryStatus qs;
983 : struct GNUNET_TIME_Absolute earliest_retry;
984 :
985 10 : if (NULL != keys)
986 : {
987 0 : TALER_EXCHANGE_keys_decref (keys);
988 0 : keys = NULL;
989 : }
990 10 : qs = db_plugin->select_exchange_keys (db_plugin->cls,
991 : exchange_url,
992 : &earliest_retry,
993 : &keys);
994 10 : if (qs < 0)
995 : {
996 0 : GNUNET_break (0);
997 0 : GNUNET_SCHEDULER_shutdown ();
998 0 : return;
999 : }
1000 10 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
1001 : {
1002 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1003 : "No keys yet for `%s'\n",
1004 : exchange_url);
1005 : }
1006 : }
1007 10 : if (NULL == keys)
1008 : {
1009 0 : if (NULL != task)
1010 : {
1011 0 : GNUNET_SCHEDULER_cancel (task);
1012 0 : task = NULL;
1013 : }
1014 : }
1015 : else
1016 : {
1017 10 : if (NULL == task)
1018 10 : task = GNUNET_SCHEDULER_add_now (&select_work,
1019 : NULL);
1020 : }
1021 : }
1022 :
1023 :
1024 : /**
1025 : * First task.
1026 : *
1027 : * @param cls closure, NULL
1028 : * @param args remaining command-line arguments
1029 : * @param cfgfile name of the configuration file used (for saving, can be NULL!)
1030 : * @param c configuration
1031 : */
1032 : static void
1033 15 : run (void *cls,
1034 : char *const *args,
1035 : const char *cfgfile,
1036 : const struct GNUNET_CONFIGURATION_Handle *c)
1037 : {
1038 : (void) args;
1039 :
1040 15 : cfg = c;
1041 15 : if (NULL != cfgfile)
1042 15 : cfg_filename = GNUNET_strdup (cfgfile);
1043 15 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1044 : "Running with configuration %s\n",
1045 : cfgfile);
1046 15 : GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
1047 : NULL);
1048 15 : if (NULL == exchange_url)
1049 : {
1050 5 : GNUNET_CONFIGURATION_iterate_sections (c,
1051 : &cfg_iter_cb,
1052 : NULL);
1053 5 : if (NULL == c_head)
1054 : {
1055 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1056 : "No exchanges found in configuration\n");
1057 0 : global_ret = EXIT_NOTCONFIGURED;
1058 0 : GNUNET_SCHEDULER_shutdown ();
1059 0 : return;
1060 : }
1061 5 : return;
1062 : }
1063 :
1064 10 : ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
1065 : &rc);
1066 10 : rc = GNUNET_CURL_gnunet_rc_create (ctx);
1067 10 : if (NULL == ctx)
1068 : {
1069 0 : GNUNET_break (0);
1070 0 : GNUNET_SCHEDULER_shutdown ();
1071 0 : global_ret = EXIT_NO_RESTART;
1072 0 : return;
1073 : }
1074 10 : if (NULL ==
1075 10 : (db_plugin = TALER_MERCHANTDB_plugin_load (cfg)))
1076 : {
1077 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1078 : "Failed to initialize DB subsystem\n");
1079 0 : GNUNET_SCHEDULER_shutdown ();
1080 0 : global_ret = EXIT_NOTCONFIGURED;
1081 0 : return;
1082 : }
1083 10 : if (GNUNET_OK !=
1084 10 : db_plugin->connect (db_plugin->cls))
1085 : {
1086 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1087 : "Failed to connect to database. Consider running taler-merchant-dbinit!\n");
1088 0 : GNUNET_SCHEDULER_shutdown ();
1089 0 : global_ret = EXIT_NO_RESTART;
1090 0 : return;
1091 : }
1092 : {
1093 10 : struct GNUNET_DB_EventHeaderP es = {
1094 10 : .size = htons (sizeof (es)),
1095 10 : .type = htons (TALER_DBEVENT_MERCHANT_NEW_WIRE_DEADLINE)
1096 : };
1097 :
1098 20 : eh = db_plugin->event_listen (db_plugin->cls,
1099 : &es,
1100 10 : GNUNET_TIME_UNIT_FOREVER_REL,
1101 : &db_notify,
1102 : NULL);
1103 : }
1104 : {
1105 10 : struct GNUNET_DB_EventHeaderP es = {
1106 10 : .size = ntohs (sizeof (es)),
1107 10 : .type = ntohs (TALER_DBEVENT_MERCHANT_EXCHANGE_KEYS)
1108 : };
1109 :
1110 20 : keys_eh = db_plugin->event_listen (db_plugin->cls,
1111 : &es,
1112 10 : GNUNET_TIME_UNIT_FOREVER_REL,
1113 : &update_exchange_keys,
1114 : NULL);
1115 : }
1116 :
1117 10 : update_exchange_keys (NULL,
1118 : exchange_url,
1119 10 : strlen (exchange_url) + 1);
1120 : }
1121 :
1122 :
1123 : /**
1124 : * The main function of the taler-merchant-depositcheck
1125 : *
1126 : * @param argc number of arguments from the command line
1127 : * @param argv command line arguments
1128 : * @return 0 ok, 1 on error
1129 : */
1130 : int
1131 15 : main (int argc,
1132 : char *const *argv)
1133 : {
1134 15 : struct GNUNET_GETOPT_CommandLineOption options[] = {
1135 15 : GNUNET_GETOPT_option_string ('e',
1136 : "exchange",
1137 : "BASE_URL",
1138 : "limit us to checking deposits of this exchange",
1139 : &exchange_url),
1140 15 : GNUNET_GETOPT_option_timetravel ('T',
1141 : "timetravel"),
1142 15 : GNUNET_GETOPT_option_flag ('t',
1143 : "test",
1144 : "run in test mode and exit when idle",
1145 : &test_mode),
1146 15 : GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION),
1147 : GNUNET_GETOPT_OPTION_END
1148 : };
1149 : enum GNUNET_GenericReturnValue ret;
1150 :
1151 15 : ret = GNUNET_PROGRAM_run (
1152 : TALER_MERCHANT_project_data (),
1153 : argc, argv,
1154 : "taler-merchant-depositcheck",
1155 : gettext_noop (
1156 : "background process that checks with the exchange on deposits that are past the wire deadline"),
1157 : options,
1158 : &run, NULL);
1159 15 : if (GNUNET_SYSERR == ret)
1160 0 : return EXIT_INVALIDARGUMENT;
1161 15 : if (GNUNET_NO == ret)
1162 0 : return EXIT_SUCCESS;
1163 15 : return global_ret;
1164 : }
1165 :
1166 :
1167 : /* end of taler-merchant-depositcheck.c */
|