Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 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-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 12 : shutdown_task (void *cls)
265 : {
266 : struct Child *c;
267 : struct ExchangeInteraction *w;
268 :
269 : (void) cls;
270 12 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
271 : "Running shutdown\n");
272 12 : if (NULL != eh)
273 : {
274 8 : db_plugin->event_listen_cancel (eh);
275 8 : eh = NULL;
276 : }
277 12 : if (NULL != keys_eh)
278 : {
279 8 : db_plugin->event_listen_cancel (keys_eh);
280 8 : keys_eh = NULL;
281 : }
282 12 : if (NULL != task)
283 : {
284 0 : GNUNET_SCHEDULER_cancel (task);
285 0 : task = NULL;
286 : }
287 12 : 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 8 : GNUNET_CONTAINER_DLL_remove (c_head,
304 : c_tail,
305 : c);
306 8 : if (NULL != c->rt)
307 : {
308 0 : GNUNET_SCHEDULER_cancel (c->rt);
309 0 : c->rt = NULL;
310 : }
311 8 : if (NULL != c->cwh)
312 : {
313 0 : GNUNET_wait_child_cancel (c->cwh);
314 0 : c->cwh = NULL;
315 : }
316 8 : 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 8 : GNUNET_free (c->base_url);
339 8 : GNUNET_free (c);
340 : }
341 12 : if (NULL != db_plugin)
342 : {
343 8 : db_plugin->rollback (db_plugin->cls); /* just in case */
344 8 : TALER_MERCHANTDB_plugin_unload (db_plugin);
345 8 : db_plugin = NULL;
346 : }
347 12 : cfg = NULL;
348 12 : if (NULL != ctx)
349 : {
350 8 : GNUNET_CURL_fini (ctx);
351 8 : ctx = NULL;
352 : }
353 12 : if (NULL != rc)
354 : {
355 8 : GNUNET_CURL_gnunet_rc_destroy (rc);
356 8 : rc = NULL;
357 : }
358 12 : }
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 0 : deposit_get_cb (
413 : void *cls,
414 : const struct TALER_EXCHANGE_GetDepositResponse *dr)
415 : {
416 0 : struct ExchangeInteraction *w = cls;
417 : struct GNUNET_TIME_Absolute future_retry;
418 :
419 0 : w->dgh = NULL;
420 : future_retry
421 0 : = GNUNET_TIME_relative_to_absolute (w->retry_backoff);
422 0 : switch (dr->hr.http_status)
423 : {
424 0 : case MHD_HTTP_OK:
425 : {
426 : enum GNUNET_DB_QueryStatus qs;
427 0 : bool cleared = false;
428 :
429 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
430 : "Exchange returned wire transfer over %s for deposited coin %s\n",
431 : TALER_amount2s (&dr->details.ok.coin_contribution),
432 : TALER_B2S (&w->coin_pub));
433 0 : qs = db_plugin->insert_deposit_to_transfer (
434 0 : db_plugin->cls,
435 : w->deposit_serial,
436 : &dr->details.ok,
437 : &cleared);
438 0 : if (qs < 0)
439 : {
440 0 : GNUNET_break (0);
441 0 : GNUNET_SCHEDULER_shutdown ();
442 0 : return;
443 : }
444 0 : if (! cleared)
445 : {
446 0 : qs = db_plugin->update_deposit_confirmation_status (
447 0 : db_plugin->cls,
448 : w->deposit_serial,
449 : true, /* this failed, wire_pending remains true */
450 : GNUNET_TIME_absolute_to_timestamp (future_retry),
451 : w->retry_backoff,
452 : "wire transfer unknown");
453 : }
454 0 : if (qs < 0)
455 : {
456 0 : GNUNET_break (0);
457 0 : GNUNET_SCHEDULER_shutdown ();
458 0 : return;
459 : }
460 0 : break;
461 : }
462 0 : case MHD_HTTP_ACCEPTED:
463 : {
464 : /* got a 'preliminary' reply from the exchange,
465 : remember our target UUID */
466 : enum GNUNET_DB_QueryStatus qs;
467 : struct GNUNET_TIME_Timestamp now;
468 :
469 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
470 : "Exchange returned KYC requirement (%d) for deposited coin %s\n",
471 : dr->details.accepted.kyc_ok,
472 : TALER_B2S (&w->coin_pub));
473 0 : now = GNUNET_TIME_timestamp_get ();
474 0 : qs = db_plugin->account_kyc_set_failed (
475 0 : db_plugin->cls,
476 0 : w->instance_id,
477 0 : &w->h_wire,
478 : exchange_url,
479 : now,
480 : MHD_HTTP_ACCEPTED,
481 0 : dr->details.accepted.kyc_ok);
482 0 : if (qs < 0)
483 : {
484 0 : GNUNET_break (0);
485 0 : GNUNET_SCHEDULER_shutdown ();
486 0 : return;
487 : }
488 0 : if (dr->details.accepted.kyc_ok)
489 : {
490 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
491 : "Bumping wire transfer deadline in DB to %s as that is when we will retry\n",
492 : GNUNET_TIME_absolute2s (future_retry));
493 0 : qs = db_plugin->update_deposit_confirmation_status (
494 0 : db_plugin->cls,
495 : w->deposit_serial,
496 : true, /* wire_pending is still true! */
497 : GNUNET_TIME_absolute_to_timestamp (future_retry),
498 : w->retry_backoff,
499 : "Exchange reported 202 Accepted but no KYC block");
500 0 : if (qs < 0)
501 : {
502 0 : GNUNET_break (0);
503 0 : GNUNET_SCHEDULER_shutdown ();
504 0 : return;
505 : }
506 : }
507 : else
508 : {
509 : future_retry
510 0 : = GNUNET_TIME_absolute_max (
511 : future_retry,
512 : GNUNET_TIME_relative_to_absolute (
513 : KYC_RETRY_DELAY));
514 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
515 : "Bumping wire transfer deadline in DB to %s as that is when we will retry\n",
516 : GNUNET_TIME_absolute2s (future_retry));
517 0 : qs = db_plugin->update_deposit_confirmation_status (
518 0 : db_plugin->cls,
519 : w->deposit_serial,
520 : true,
521 : GNUNET_TIME_absolute_to_timestamp (future_retry),
522 : w->retry_backoff,
523 : "Exchange reported 202 Accepted due to KYC/AML block");
524 0 : if (qs < 0)
525 : {
526 0 : GNUNET_break (0);
527 0 : GNUNET_SCHEDULER_shutdown ();
528 0 : return;
529 : }
530 : }
531 0 : break;
532 : }
533 0 : default:
534 : {
535 : enum GNUNET_DB_QueryStatus qs;
536 : char *msg;
537 :
538 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
539 : "Exchange %s returned tracking failure for deposited coin %s\n",
540 : exchange_url,
541 : TALER_B2S (&w->coin_pub));
542 0 : GNUNET_asprintf (&msg,
543 : "Unexpected exchange status %u (#%d, %s)\n",
544 0 : dr->hr.http_status,
545 0 : (int) dr->hr.ec,
546 0 : dr->hr.hint);
547 0 : qs = db_plugin->update_deposit_confirmation_status (
548 0 : db_plugin->cls,
549 : w->deposit_serial,
550 : true, /* this failed, wire_pending remains true */
551 : GNUNET_TIME_absolute_to_timestamp (future_retry),
552 : w->retry_backoff,
553 : msg);
554 0 : GNUNET_free (msg);
555 0 : if (qs < 0)
556 : {
557 0 : GNUNET_break (0);
558 0 : GNUNET_SCHEDULER_shutdown ();
559 0 : return;
560 : }
561 0 : return;
562 : }
563 : } /* end switch */
564 :
565 0 : GNUNET_CONTAINER_DLL_remove (w_head,
566 : w_tail,
567 : w);
568 0 : w_count--;
569 0 : GNUNET_free (w->instance_id);
570 0 : GNUNET_free (w);
571 0 : GNUNET_assert (NULL != keys);
572 0 : if ( (w_count < CONCURRENCY_LIMIT / 2) ||
573 0 : (0 == w_count) )
574 : {
575 0 : if (NULL != task)
576 0 : GNUNET_SCHEDULER_cancel (task);
577 0 : task = GNUNET_SCHEDULER_add_now (&select_work,
578 : NULL);
579 : }
580 : }
581 :
582 :
583 : /**
584 : * Typically called by `select_work`.
585 : *
586 : * @param cls NULL
587 : * @param deposit_serial identifies the deposit operation
588 : * @param wire_deadline when is the wire due
589 : * @param retry_backoff current value for the retry backoff
590 : * @param h_contract_terms hash of the contract terms
591 : * @param merchant_priv private key of the merchant
592 : * @param instance_id row ID of the instance
593 : * @param h_wire hash of the merchant's wire account into * @param amount_with_fee amount the exchange will deposit for this coin
594 : * @param deposit_fee fee the exchange will charge for this coin which the deposit was made
595 : * @param coin_pub public key of the deposited coin
596 : */
597 : static void
598 0 : pending_deposits_cb (
599 : void *cls,
600 : uint64_t deposit_serial,
601 : struct GNUNET_TIME_Absolute wire_deadline,
602 : struct GNUNET_TIME_Relative retry_backoff,
603 : const struct TALER_PrivateContractHashP *h_contract_terms,
604 : const struct TALER_MerchantPrivateKeyP *merchant_priv,
605 : const char *instance_id,
606 : const struct TALER_MerchantWireHashP *h_wire,
607 : const struct TALER_Amount *amount_with_fee,
608 : const struct TALER_Amount *deposit_fee,
609 : const struct TALER_CoinSpendPublicKeyP *coin_pub)
610 : {
611 : struct ExchangeInteraction *w;
612 :
613 : (void) cls;
614 0 : if (GNUNET_TIME_absolute_is_future (wire_deadline))
615 : {
616 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
617 : "Pending deposit has deadline in the future at %s\n",
618 : GNUNET_TIME_absolute2s (wire_deadline));
619 0 : run_at (wire_deadline);
620 0 : return;
621 : }
622 0 : w = GNUNET_new (struct ExchangeInteraction);
623 0 : w->deposit_serial = deposit_serial;
624 0 : w->wire_deadline = wire_deadline;
625 0 : w->retry_backoff = GNUNET_TIME_STD_BACKOFF (retry_backoff);
626 0 : w->h_contract_terms = *h_contract_terms;
627 0 : w->merchant_priv = *merchant_priv;
628 0 : w->h_wire = *h_wire;
629 0 : w->amount_with_fee = *amount_with_fee;
630 0 : w->deposit_fee = *deposit_fee;
631 0 : w->coin_pub = *coin_pub;
632 0 : w->instance_id = GNUNET_strdup (instance_id);
633 0 : GNUNET_CONTAINER_DLL_insert (w_head,
634 : w_tail,
635 : w);
636 0 : w_count++;
637 0 : GNUNET_assert (NULL != keys);
638 0 : if (GNUNET_TIME_absolute_is_past (
639 0 : keys->key_data_expiration.abs_time))
640 : {
641 : /* Parent should re-start us, then we will re-fetch /keys */
642 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
643 : "/keys expired, shutting down\n");
644 0 : GNUNET_SCHEDULER_shutdown ();
645 0 : return;
646 : }
647 0 : GNUNET_assert (NULL == w->dgh);
648 0 : w->dgh = TALER_EXCHANGE_deposits_get (
649 : ctx,
650 : exchange_url,
651 : keys,
652 0 : &w->merchant_priv,
653 0 : &w->h_wire,
654 0 : &w->h_contract_terms,
655 0 : &w->coin_pub,
656 0 : GNUNET_TIME_UNIT_ZERO,
657 : &deposit_get_cb,
658 : w);
659 : }
660 :
661 :
662 : /**
663 : * Function called on events received from Postgres.
664 : *
665 : * @param cls closure, NULL
666 : * @param extra additional event data provided, timestamp with wire deadline
667 : * @param extra_size number of bytes in @a extra
668 : */
669 : static void
670 0 : db_notify (void *cls,
671 : const void *extra,
672 : size_t extra_size)
673 : {
674 : struct GNUNET_TIME_Absolute deadline;
675 : struct GNUNET_TIME_AbsoluteNBO nbo_deadline;
676 :
677 : (void) cls;
678 0 : if (sizeof (nbo_deadline) != extra_size)
679 : {
680 0 : GNUNET_break (0);
681 0 : return;
682 : }
683 0 : if (0 != w_count)
684 0 : return; /* already at work! */
685 0 : memcpy (&nbo_deadline,
686 : extra,
687 : extra_size);
688 0 : deadline = GNUNET_TIME_absolute_ntoh (nbo_deadline);
689 0 : run_at (deadline);
690 : }
691 :
692 :
693 : static void
694 8 : select_work (void *cls)
695 : {
696 8 : bool retry = false;
697 8 : uint64_t limit = CONCURRENCY_LIMIT - w_count;
698 :
699 : (void) cls;
700 8 : task = NULL;
701 8 : GNUNET_assert (w_count <= CONCURRENCY_LIMIT);
702 8 : GNUNET_assert (NULL != keys);
703 8 : if (0 == limit)
704 : {
705 0 : GNUNET_break (0);
706 0 : return;
707 : }
708 8 : if (GNUNET_TIME_absolute_is_past (
709 8 : keys->key_data_expiration.abs_time))
710 : {
711 : /* Parent should re-start us, then we will re-fetch /keys */
712 0 : GNUNET_SCHEDULER_shutdown ();
713 0 : return;
714 : }
715 : while (1)
716 0 : {
717 : enum GNUNET_DB_QueryStatus qs;
718 :
719 8 : db_plugin->preflight (db_plugin->cls);
720 8 : if (retry)
721 0 : limit = 1;
722 8 : qs = db_plugin->lookup_pending_deposits (
723 8 : db_plugin->cls,
724 : exchange_url,
725 : limit,
726 : retry,
727 : &pending_deposits_cb,
728 : NULL);
729 8 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
730 : "Looking up pending deposits query status was %d\n",
731 : (int) qs);
732 8 : switch (qs)
733 : {
734 0 : case GNUNET_DB_STATUS_HARD_ERROR:
735 : case GNUNET_DB_STATUS_SOFT_ERROR:
736 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
737 : "Transaction failed!\n");
738 0 : global_ret = EXIT_FAILURE;
739 0 : GNUNET_SCHEDULER_shutdown ();
740 0 : return;
741 8 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
742 8 : if (test_mode)
743 : {
744 8 : GNUNET_SCHEDULER_shutdown ();
745 8 : return;
746 : }
747 0 : if (retry)
748 0 : return; /* nothing left */
749 0 : retry = true;
750 0 : continue;
751 0 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
752 : default:
753 : /* wait for async completion, then select more work. */
754 0 : return;
755 : }
756 : }
757 : }
758 :
759 :
760 : /**
761 : * Start a copy of this process with the exchange URL
762 : * set to the given @a base_url
763 : *
764 : * @param base_url base URL to run with
765 : */
766 : static struct GNUNET_OS_Process *
767 8 : start_worker (const char *base_url)
768 : {
769 8 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
770 : "Launching worker for exchange `%s' using `%s`\n",
771 : base_url,
772 : NULL == cfg_filename
773 : ? "<default>"
774 : : cfg_filename);
775 8 : if (NULL == cfg_filename)
776 0 : return GNUNET_OS_start_process (
777 : GNUNET_OS_INHERIT_STD_ALL,
778 : NULL,
779 : NULL,
780 : NULL,
781 : "taler-merchant-depositcheck",
782 : "taler-merchant-depositcheck",
783 : "-e", base_url,
784 : "-L", "INFO",
785 0 : test_mode ? "-t" : NULL,
786 : NULL);
787 8 : return GNUNET_OS_start_process (
788 : GNUNET_OS_INHERIT_STD_ALL,
789 : NULL,
790 : NULL,
791 : NULL,
792 : "taler-merchant-depositcheck",
793 : "taler-merchant-depositcheck",
794 : "-c", cfg_filename,
795 : "-e", base_url,
796 : "-L", "INFO",
797 8 : test_mode ? "-t" : NULL,
798 : NULL);
799 : }
800 :
801 :
802 : /**
803 : * Restart worker process for the given child.
804 : *
805 : * @param cls a `struct Child *` that needs a worker.
806 : */
807 : static void
808 : restart_child (void *cls);
809 :
810 :
811 : /**
812 : * Function called upon death or completion of a child process.
813 : *
814 : * @param cls a `struct Child *`
815 : * @param type type of the process
816 : * @param exit_code status code of the process
817 : */
818 : static void
819 8 : child_done_cb (void *cls,
820 : enum GNUNET_OS_ProcessStatusType type,
821 : long unsigned int exit_code)
822 : {
823 8 : struct Child *c = cls;
824 :
825 8 : c->cwh = NULL;
826 8 : GNUNET_OS_process_destroy (c->process);
827 8 : c->process = NULL;
828 8 : if ( (GNUNET_OS_PROCESS_EXITED != type) ||
829 : (0 != exit_code) )
830 : {
831 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
832 : "Process for exchange %s had trouble (%d/%d)\n",
833 : c->base_url,
834 : (int) type,
835 : (int) exit_code);
836 0 : GNUNET_SCHEDULER_shutdown ();
837 0 : global_ret = EXIT_NOTINSTALLED;
838 0 : return;
839 : }
840 8 : if (test_mode &&
841 8 : (! GNUNET_TIME_relative_is_zero (c->rd)) )
842 : {
843 8 : return;
844 : }
845 0 : if (GNUNET_TIME_absolute_is_future (c->next_start))
846 0 : c->rd = GNUNET_TIME_STD_BACKOFF (c->rd);
847 : else
848 0 : c->rd = GNUNET_TIME_UNIT_SECONDS;
849 0 : c->rt = GNUNET_SCHEDULER_add_at (c->next_start,
850 : &restart_child,
851 : c);
852 : }
853 :
854 :
855 : static void
856 8 : restart_child (void *cls)
857 : {
858 8 : struct Child *c = cls;
859 :
860 8 : c->rt = NULL;
861 8 : c->next_start = GNUNET_TIME_relative_to_absolute (c->rd);
862 8 : c->process = start_worker (c->base_url);
863 8 : if (NULL == c->process)
864 : {
865 0 : GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
866 : "exec");
867 0 : global_ret = EXIT_NO_RESTART;
868 0 : GNUNET_SCHEDULER_shutdown ();
869 0 : return;
870 : }
871 8 : c->cwh = GNUNET_wait_child (c->process,
872 : &child_done_cb,
873 : c);
874 : }
875 :
876 :
877 : /**
878 : * Function to iterate over section.
879 : *
880 : * @param cls closure
881 : * @param section name of the section
882 : */
883 : static void
884 163 : cfg_iter_cb (void *cls,
885 : const char *section)
886 : {
887 : char *base_url;
888 : struct Child *c;
889 :
890 163 : if (0 !=
891 163 : strncasecmp (section,
892 : "merchant-exchange-",
893 : strlen ("merchant-exchange-")))
894 155 : return;
895 12 : if (GNUNET_YES ==
896 12 : GNUNET_CONFIGURATION_get_value_yesno (cfg,
897 : section,
898 : "DISABLED"))
899 4 : return;
900 8 : if (GNUNET_OK !=
901 8 : GNUNET_CONFIGURATION_get_value_string (cfg,
902 : section,
903 : "EXCHANGE_BASE_URL",
904 : &base_url))
905 : {
906 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
907 : section,
908 : "EXCHANGE_BASE_URL");
909 0 : return;
910 : }
911 8 : c = GNUNET_new (struct Child);
912 8 : c->rd = GNUNET_TIME_UNIT_SECONDS;
913 8 : c->base_url = base_url;
914 8 : GNUNET_CONTAINER_DLL_insert (c_head,
915 : c_tail,
916 : c);
917 8 : c->rt = GNUNET_SCHEDULER_add_now (&restart_child,
918 : c);
919 : }
920 :
921 :
922 : /**
923 : * Trigger (re)loading of keys from DB.
924 : *
925 : * @param cls NULL
926 : * @param extra base URL of the exchange that changed
927 : * @param extra_len number of bytes in @a extra
928 : */
929 : static void
930 8 : update_exchange_keys (void *cls,
931 : const void *extra,
932 : size_t extra_len)
933 : {
934 8 : const char *url = extra;
935 :
936 8 : if ( (NULL == extra) ||
937 : (0 == extra_len) )
938 : {
939 0 : GNUNET_break (0);
940 0 : return;
941 : }
942 8 : if ('\0' != url[extra_len - 1])
943 : {
944 0 : GNUNET_break (0);
945 0 : return;
946 : }
947 8 : if (0 != strcmp (url,
948 : exchange_url))
949 0 : return; /* not relevant for us */
950 :
951 : {
952 : enum GNUNET_DB_QueryStatus qs;
953 : struct GNUNET_TIME_Absolute earliest_retry;
954 :
955 8 : if (NULL != keys)
956 : {
957 0 : TALER_EXCHANGE_keys_decref (keys);
958 0 : keys = NULL;
959 : }
960 8 : qs = db_plugin->select_exchange_keys (db_plugin->cls,
961 : exchange_url,
962 : &earliest_retry,
963 : &keys);
964 8 : if (qs < 0)
965 : {
966 0 : GNUNET_break (0);
967 0 : GNUNET_SCHEDULER_shutdown ();
968 0 : return;
969 : }
970 8 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
971 : {
972 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
973 : "No keys yet for `%s'\n",
974 : exchange_url);
975 : }
976 : }
977 8 : if (NULL == keys)
978 : {
979 0 : if (NULL != task)
980 : {
981 0 : GNUNET_SCHEDULER_cancel (task);
982 0 : task = NULL;
983 : }
984 : }
985 : else
986 : {
987 8 : if (NULL == task)
988 8 : task = GNUNET_SCHEDULER_add_now (&select_work,
989 : NULL);
990 : }
991 : }
992 :
993 :
994 : /**
995 : * First task.
996 : *
997 : * @param cls closure, NULL
998 : * @param args remaining command-line arguments
999 : * @param cfgfile name of the configuration file used (for saving, can be NULL!)
1000 : * @param c configuration
1001 : */
1002 : static void
1003 12 : run (void *cls,
1004 : char *const *args,
1005 : const char *cfgfile,
1006 : const struct GNUNET_CONFIGURATION_Handle *c)
1007 : {
1008 : (void) args;
1009 :
1010 12 : cfg = c;
1011 12 : if (NULL != cfgfile)
1012 12 : cfg_filename = GNUNET_strdup (cfgfile);
1013 12 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1014 : "Running with configuration %s\n",
1015 : cfgfile);
1016 12 : GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
1017 : NULL);
1018 12 : if (NULL == exchange_url)
1019 : {
1020 4 : GNUNET_CONFIGURATION_iterate_sections (c,
1021 : &cfg_iter_cb,
1022 : NULL);
1023 4 : if (NULL == c_head)
1024 : {
1025 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1026 : "No exchanges found in configuration\n");
1027 0 : global_ret = EXIT_NOTCONFIGURED;
1028 0 : GNUNET_SCHEDULER_shutdown ();
1029 0 : return;
1030 : }
1031 4 : return;
1032 : }
1033 :
1034 8 : ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
1035 : &rc);
1036 8 : rc = GNUNET_CURL_gnunet_rc_create (ctx);
1037 8 : if (NULL == ctx)
1038 : {
1039 0 : GNUNET_break (0);
1040 0 : GNUNET_SCHEDULER_shutdown ();
1041 0 : global_ret = EXIT_NO_RESTART;
1042 0 : return;
1043 : }
1044 8 : if (NULL ==
1045 8 : (db_plugin = TALER_MERCHANTDB_plugin_load (cfg)))
1046 : {
1047 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1048 : "Failed to initialize DB subsystem\n");
1049 0 : GNUNET_SCHEDULER_shutdown ();
1050 0 : global_ret = EXIT_NOTCONFIGURED;
1051 0 : return;
1052 : }
1053 8 : if (GNUNET_OK !=
1054 8 : db_plugin->connect (db_plugin->cls))
1055 : {
1056 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1057 : "Failed to connect to database. Consider running taler-merchant-dbinit!\n");
1058 0 : GNUNET_SCHEDULER_shutdown ();
1059 0 : global_ret = EXIT_NO_RESTART;
1060 0 : return;
1061 : }
1062 : {
1063 8 : struct GNUNET_DB_EventHeaderP es = {
1064 8 : .size = htons (sizeof (es)),
1065 8 : .type = htons (TALER_DBEVENT_MERCHANT_NEW_WIRE_DEADLINE)
1066 : };
1067 :
1068 16 : eh = db_plugin->event_listen (db_plugin->cls,
1069 : &es,
1070 8 : GNUNET_TIME_UNIT_FOREVER_REL,
1071 : &db_notify,
1072 : NULL);
1073 : }
1074 : {
1075 8 : struct GNUNET_DB_EventHeaderP es = {
1076 8 : .size = ntohs (sizeof (es)),
1077 8 : .type = ntohs (TALER_DBEVENT_MERCHANT_EXCHANGE_KEYS)
1078 : };
1079 :
1080 16 : keys_eh = db_plugin->event_listen (db_plugin->cls,
1081 : &es,
1082 8 : GNUNET_TIME_UNIT_FOREVER_REL,
1083 : &update_exchange_keys,
1084 : NULL);
1085 : }
1086 :
1087 8 : update_exchange_keys (NULL,
1088 : exchange_url,
1089 8 : strlen (exchange_url) + 1);
1090 : }
1091 :
1092 :
1093 : /**
1094 : * The main function of the taler-merchant-depositcheck
1095 : *
1096 : * @param argc number of arguments from the command line
1097 : * @param argv command line arguments
1098 : * @return 0 ok, 1 on error
1099 : */
1100 : int
1101 12 : main (int argc,
1102 : char *const *argv)
1103 : {
1104 12 : struct GNUNET_GETOPT_CommandLineOption options[] = {
1105 12 : GNUNET_GETOPT_option_string ('e',
1106 : "exchange",
1107 : "BASE_URL",
1108 : "limit us to checking deposits of this exchange",
1109 : &exchange_url),
1110 12 : GNUNET_GETOPT_option_timetravel ('T',
1111 : "timetravel"),
1112 12 : GNUNET_GETOPT_option_flag ('t',
1113 : "test",
1114 : "run in test mode and exit when idle",
1115 : &test_mode),
1116 12 : GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION),
1117 : GNUNET_GETOPT_OPTION_END
1118 : };
1119 : enum GNUNET_GenericReturnValue ret;
1120 :
1121 12 : ret = GNUNET_PROGRAM_run (
1122 : TALER_MERCHANT_project_data (),
1123 : argc, argv,
1124 : "taler-merchant-depositcheck",
1125 : gettext_noop (
1126 : "background process that checks with the exchange on deposits that are past the wire deadline"),
1127 : options,
1128 : &run, NULL);
1129 12 : if (GNUNET_SYSERR == ret)
1130 0 : return EXIT_INVALIDARGUMENT;
1131 12 : if (GNUNET_NO == ret)
1132 0 : return EXIT_SUCCESS;
1133 12 : return global_ret;
1134 : }
1135 :
1136 :
1137 : /* end of taler-merchant-depositcheck.c */
|