Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2018-2021 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it
6 : under the terms of the GNU General Public License as published by
7 : the Free Software Foundation; either version 3, or (at your
8 : option) any later version.
9 :
10 : TALER is distributed in the hope that it will be useful, but
11 : WITHOUT ANY WARRANTY; without even the implied warranty of
12 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 : General Public License for more details.
14 :
15 : You should have received a copy of the GNU General Public
16 : License along with TALER; see the file COPYING. If not, see
17 : <http://www.gnu.org/licenses/>
18 : */
19 : /**
20 : * @file testing/testing_api_cmd_bank_admin_add_incoming.c
21 : * @brief implementation of a bank /admin/add-incoming command
22 : * @author Christian Grothoff
23 : * @author Marcello Stanisci
24 : */
25 : #include "platform.h"
26 : #include "backoff.h"
27 : #include "taler_json_lib.h"
28 : #include <gnunet/gnunet_curl_lib.h>
29 : #include "taler_bank_service.h"
30 : #include "taler_fakebank_lib.h"
31 : #include "taler_signatures.h"
32 : #include "taler_testing_lib.h"
33 :
34 : /**
35 : * How long do we wait AT MOST when retrying?
36 : */
37 : #define MAX_BACKOFF GNUNET_TIME_relative_multiply ( \
38 : GNUNET_TIME_UNIT_MILLISECONDS, 100)
39 :
40 :
41 : /**
42 : * How often do we retry before giving up?
43 : */
44 : #define NUM_RETRIES 5
45 :
46 :
47 : /**
48 : * State for a "fakebank transfer" CMD.
49 : */
50 : struct AdminAddIncomingState
51 : {
52 :
53 : /**
54 : * Label of any command that can trait-offer a reserve priv.
55 : */
56 : const char *reserve_reference;
57 :
58 : /**
59 : * Wire transfer amount.
60 : */
61 : struct TALER_Amount amount;
62 :
63 : /**
64 : * Base URL of the credited account.
65 : */
66 : const char *exchange_credit_url;
67 :
68 : /**
69 : * Money sender payto URL.
70 : */
71 : const char *payto_debit_account;
72 :
73 : /**
74 : * Username to use for authentication.
75 : */
76 : struct TALER_BANK_AuthenticationData auth;
77 :
78 : /**
79 : * Set (by the interpreter) to the reserve's private key
80 : * we used to make a wire transfer subject line with.
81 : */
82 : struct TALER_ReservePrivateKeyP reserve_priv;
83 :
84 : /**
85 : * Whether we know the private key or not.
86 : */
87 : bool reserve_priv_known;
88 :
89 : /**
90 : * Reserve public key matching @e reserve_priv.
91 : */
92 : struct TALER_ReservePublicKeyP reserve_pub;
93 :
94 : /**
95 : * Handle to the pending request at the fakebank.
96 : */
97 : struct TALER_BANK_AdminAddIncomingHandle *aih;
98 :
99 : /**
100 : * Interpreter state.
101 : */
102 : struct TALER_TESTING_Interpreter *is;
103 :
104 : /**
105 : * Reserve history entry that corresponds to this operation.
106 : * Will be of type #TALER_EXCHANGE_RTT_CREDIT. Note that
107 : * the "sender_url" field is set to a 'const char *' and
108 : * MUST NOT be free()'ed.
109 : */
110 : struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history;
111 :
112 : /**
113 : * Set to the wire transfer's unique ID.
114 : */
115 : uint64_t serial_id;
116 :
117 : /**
118 : * Timestamp of the transaction (as returned from the bank).
119 : */
120 : struct GNUNET_TIME_Timestamp timestamp;
121 :
122 : /**
123 : * Merchant instance. Sometimes used to get the tip reserve
124 : * private key by reading the appropriate config section.
125 : */
126 : const char *instance;
127 :
128 : /**
129 : * Configuration filename. Used to get the tip reserve key
130 : * filename (used to obtain a public key to write in the
131 : * transfer subject).
132 : */
133 : const char *config_filename;
134 :
135 : /**
136 : * Task scheduled to try later.
137 : */
138 : struct GNUNET_SCHEDULER_Task *retry_task;
139 :
140 : /**
141 : * How long do we wait until we retry?
142 : */
143 : struct GNUNET_TIME_Relative backoff;
144 :
145 : /**
146 : * Was this command modified via
147 : * #TALER_TESTING_cmd_admin_add_incoming_with_retry to
148 : * enable retries? If so, how often should we still retry?
149 : */
150 : unsigned int do_retry;
151 :
152 : /**
153 : * Expected HTTP status code.
154 : */
155 : unsigned int expected_http_status;
156 : };
157 :
158 :
159 : /**
160 : * Run the "fakebank transfer" CMD.
161 : *
162 : * @param cls closure.
163 : * @param cmd CMD being run.
164 : * @param is interpreter state.
165 : */
166 : static void
167 : admin_add_incoming_run (void *cls,
168 : const struct TALER_TESTING_Command *cmd,
169 : struct TALER_TESTING_Interpreter *is);
170 :
171 :
172 : /**
173 : * Task scheduled to re-try #admin_add_incoming_run.
174 : *
175 : * @param cls a `struct AdminAddIncomingState`
176 : */
177 : static void
178 0 : do_retry (void *cls)
179 : {
180 0 : struct AdminAddIncomingState *fts = cls;
181 :
182 0 : fts->retry_task = NULL;
183 0 : fts->is->commands[fts->is->ip].last_req_time
184 0 : = GNUNET_TIME_absolute_get ();
185 0 : admin_add_incoming_run (fts,
186 : NULL,
187 : fts->is);
188 0 : }
189 :
190 :
191 : /**
192 : * This callback will process the fakebank response to the wire
193 : * transfer. It just checks whether the HTTP response code is
194 : * acceptable.
195 : *
196 : * @param cls closure with the interpreter state
197 : * @param http_status HTTP response code, #MHD_HTTP_OK (200) for
198 : * successful status request; 0 if the exchange's reply is
199 : * bogus (fails to follow the protocol)
200 : * @param ec taler-specific error code, #TALER_EC_NONE on success
201 : * @param serial_id unique ID of the wire transfer
202 : * @param timestamp time stamp of the transaction made.
203 : * @param json raw response
204 : */
205 : static void
206 6 : confirmation_cb (void *cls,
207 : unsigned int http_status,
208 : enum TALER_ErrorCode ec,
209 : uint64_t serial_id,
210 : struct GNUNET_TIME_Timestamp timestamp,
211 : const json_t *json)
212 : {
213 6 : struct AdminAddIncomingState *fts = cls;
214 6 : struct TALER_TESTING_Interpreter *is = fts->is;
215 :
216 : (void) json;
217 6 : fts->reserve_history.details.in_details.timestamp = timestamp;
218 6 : fts->reserve_history.details.in_details.wire_reference = serial_id;
219 6 : fts->aih = NULL;
220 : /**
221 : * Test case not caring about the HTTP status code.
222 : * That helps when Fakebank and Libeufin diverge in
223 : * the response status code. An example is the
224 : * /admin/add-incoming: libeufin return ALWAYS '200 OK'
225 : * (see note below) whereas the Fakebank responds with
226 : * '409 Conflict' upon a duplicate reserve public key.
227 : *
228 : * Note: this decision aims at avoiding to put Taler
229 : * logic into the Sandbox; that's because banks DO allow
230 : * their customers to wire the same subject multiple
231 : * times. Hence, instead of triggering any error, libeufin
232 : * bounces the payment back in the same way it does for
233 : * malformed reserve public keys.
234 : */
235 6 : if (-1 == (int) fts->expected_http_status)
236 : {
237 2 : TALER_TESTING_interpreter_next (is);
238 2 : return;
239 : }
240 4 : if (http_status != fts->expected_http_status)
241 : {
242 0 : GNUNET_break (0);
243 0 : TALER_TESTING_interpreter_fail (is);
244 0 : return;
245 : }
246 4 : switch (http_status)
247 : {
248 4 : case MHD_HTTP_OK:
249 4 : fts->serial_id = serial_id;
250 4 : fts->timestamp = timestamp;
251 4 : TALER_TESTING_interpreter_next (is);
252 4 : return;
253 0 : case MHD_HTTP_UNAUTHORIZED:
254 0 : switch (fts->auth.method)
255 : {
256 0 : case TALER_BANK_AUTH_NONE:
257 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
258 : "Authentication required, but none configure.\n");
259 0 : break;
260 0 : case TALER_BANK_AUTH_BASIC:
261 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
262 : "Basic authentication (%s) failed.\n",
263 : fts->auth.details.basic.username);
264 0 : break;
265 : }
266 0 : break;
267 0 : case MHD_HTTP_CONFLICT:
268 0 : TALER_TESTING_interpreter_next (is);
269 0 : return;
270 0 : default:
271 0 : if (0 != fts->do_retry)
272 : {
273 0 : fts->do_retry--;
274 0 : if ( (0 == http_status) ||
275 0 : (TALER_EC_GENERIC_DB_SOFT_FAILURE == ec) ||
276 : (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) )
277 : {
278 0 : GNUNET_log (
279 : GNUNET_ERROR_TYPE_INFO,
280 : "Retrying fakebank transfer failed with %u/%d\n",
281 : http_status,
282 : (int) ec);
283 : /* on DB conflicts, do not use backoff */
284 0 : if (TALER_EC_GENERIC_DB_SOFT_FAILURE == ec)
285 0 : fts->backoff = GNUNET_TIME_UNIT_ZERO;
286 : else
287 0 : fts->backoff = GNUNET_TIME_randomized_backoff (fts->backoff,
288 : MAX_BACKOFF);
289 0 : fts->is->commands[fts->is->ip].num_tries++;
290 0 : fts->retry_task = GNUNET_SCHEDULER_add_delayed (
291 : fts->backoff,
292 : &do_retry,
293 : fts);
294 0 : return;
295 : }
296 : }
297 0 : break;
298 : }
299 0 : GNUNET_break (0);
300 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
301 : "Fakebank returned HTTP status %u/%d\n",
302 : http_status,
303 : (int) ec);
304 0 : TALER_TESTING_interpreter_fail (is);
305 : }
306 :
307 :
308 : /**
309 : * Run the "fakebank transfer" CMD.
310 : *
311 : * @param cls closure.
312 : * @param cmd CMD being run.
313 : * @param is interpreter state.
314 : */
315 : static void
316 6 : admin_add_incoming_run (void *cls,
317 : const struct TALER_TESTING_Command *cmd,
318 : struct TALER_TESTING_Interpreter *is)
319 : {
320 6 : struct AdminAddIncomingState *fts = cls;
321 6 : bool have_public = false;
322 :
323 : (void) cmd;
324 : /* Use reserve public key as subject */
325 6 : if (NULL != fts->reserve_reference)
326 : {
327 : const struct TALER_TESTING_Command *ref;
328 : const struct TALER_ReservePrivateKeyP *reserve_priv;
329 : const struct TALER_ReservePublicKeyP *reserve_pub;
330 :
331 2 : ref = TALER_TESTING_interpreter_lookup_command
332 : (is, fts->reserve_reference);
333 2 : if (NULL == ref)
334 : {
335 0 : GNUNET_break (0);
336 0 : TALER_TESTING_interpreter_fail (is);
337 0 : return;
338 : }
339 2 : if (GNUNET_OK !=
340 2 : TALER_TESTING_get_trait_reserve_priv (ref,
341 : &reserve_priv))
342 : {
343 0 : if (GNUNET_OK != TALER_TESTING_get_trait_reserve_pub (ref,
344 : &reserve_pub))
345 : {
346 0 : GNUNET_break (0);
347 0 : TALER_TESTING_interpreter_fail (is);
348 0 : return;
349 : }
350 0 : have_public = true;
351 0 : fts->reserve_pub.eddsa_pub = reserve_pub->eddsa_pub;
352 0 : fts->reserve_priv_known = false;
353 : }
354 : else
355 : {
356 2 : fts->reserve_priv.eddsa_priv = reserve_priv->eddsa_priv;
357 2 : fts->reserve_priv_known = true;
358 : }
359 : }
360 : else
361 : {
362 : /* No referenced reserve, no instance to take priv
363 : * from, no explicit subject given: create new key! */
364 4 : GNUNET_CRYPTO_eddsa_key_create (&fts->reserve_priv.eddsa_priv);
365 4 : fts->reserve_priv_known = true;
366 : }
367 6 : if (! have_public)
368 6 : GNUNET_CRYPTO_eddsa_key_get_public (&fts->reserve_priv.eddsa_priv,
369 : &fts->reserve_pub.eddsa_pub);
370 6 : fts->reserve_history.type = TALER_EXCHANGE_RTT_CREDIT;
371 6 : fts->reserve_history.amount = fts->amount;
372 : fts->reserve_history.details.in_details.sender_url
373 6 : = (char *) fts->payto_debit_account; /* remember to NOT free this one... */
374 6 : fts->is = is;
375 : fts->aih
376 6 : = TALER_BANK_admin_add_incoming (
377 : TALER_TESTING_interpreter_get_context (is),
378 6 : &fts->auth,
379 6 : &fts->reserve_pub,
380 6 : &fts->amount,
381 : fts->payto_debit_account,
382 : &confirmation_cb,
383 : fts);
384 6 : if (NULL == fts->aih)
385 : {
386 0 : GNUNET_break (0);
387 0 : TALER_TESTING_interpreter_fail (is);
388 0 : return;
389 : }
390 : }
391 :
392 :
393 : /**
394 : * Free the state of a "/admin/add-incoming" CMD, and possibly
395 : * cancel a pending operation thereof.
396 : *
397 : * @param cls closure
398 : * @param cmd current CMD being cleaned up.
399 : */
400 : static void
401 6 : admin_add_incoming_cleanup (void *cls,
402 : const struct TALER_TESTING_Command *cmd)
403 : {
404 6 : struct AdminAddIncomingState *fts = cls;
405 :
406 6 : if (NULL != fts->aih)
407 : {
408 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
409 : "Command %s did not complete\n",
410 : cmd->label);
411 0 : TALER_BANK_admin_add_incoming_cancel (fts->aih);
412 0 : fts->aih = NULL;
413 : }
414 6 : if (NULL != fts->retry_task)
415 : {
416 0 : GNUNET_SCHEDULER_cancel (fts->retry_task);
417 0 : fts->retry_task = NULL;
418 : }
419 6 : GNUNET_free (fts);
420 6 : }
421 :
422 :
423 : /**
424 : * Offer internal data from a "/admin/add-incoming" CMD to other
425 : * commands.
426 : *
427 : * @param cls closure.
428 : * @param[out] ret result
429 : * @param trait name of the trait.
430 : * @param index index number of the object to offer.
431 : * @return #GNUNET_OK on success.
432 : */
433 : static enum GNUNET_GenericReturnValue
434 50 : admin_add_incoming_traits (void *cls,
435 : const void **ret,
436 : const char *trait,
437 : unsigned int index)
438 : {
439 50 : struct AdminAddIncomingState *fts = cls;
440 : static const char *void_uri = "payto://void/the-exchange";
441 :
442 50 : if (MHD_HTTP_OK !=
443 50 : fts->expected_http_status)
444 6 : return GNUNET_NO; /* requests that failed generate no history */
445 44 : if (fts->reserve_priv_known)
446 : {
447 : struct TALER_TESTING_Trait traits[] = {
448 44 : TALER_TESTING_make_trait_bank_row (&fts->serial_id),
449 44 : TALER_TESTING_make_trait_debit_payto_uri (&fts->payto_debit_account),
450 44 : TALER_TESTING_make_trait_payto_uri (&fts->payto_debit_account),
451 : /* Used as a marker, content does not matter */
452 44 : TALER_TESTING_make_trait_credit_payto_uri (&void_uri),
453 44 : TALER_TESTING_make_trait_exchange_bank_account_url (
454 : &fts->exchange_credit_url),
455 44 : TALER_TESTING_make_trait_amount (&fts->amount),
456 44 : TALER_TESTING_make_trait_timestamp (0,
457 44 : &fts->timestamp),
458 44 : TALER_TESTING_make_trait_reserve_priv (&fts->reserve_priv),
459 44 : TALER_TESTING_make_trait_reserve_pub (&fts->reserve_pub),
460 44 : TALER_TESTING_make_trait_reserve_history (0,
461 44 : &fts->reserve_history),
462 44 : TALER_TESTING_trait_end ()
463 : };
464 :
465 44 : return TALER_TESTING_get_trait (traits,
466 : ret,
467 : trait,
468 : index);
469 : }
470 : else
471 : {
472 : struct TALER_TESTING_Trait traits[] = {
473 0 : TALER_TESTING_make_trait_bank_row (&fts->serial_id),
474 0 : TALER_TESTING_make_trait_debit_payto_uri (&fts->payto_debit_account),
475 : /* Used as a marker, content does not matter */
476 0 : TALER_TESTING_make_trait_credit_payto_uri (&void_uri),
477 0 : TALER_TESTING_make_trait_exchange_bank_account_url (
478 : &fts->exchange_credit_url),
479 0 : TALER_TESTING_make_trait_amount (&fts->amount),
480 0 : TALER_TESTING_make_trait_timestamp (0, &fts->timestamp),
481 0 : TALER_TESTING_make_trait_reserve_pub (&fts->reserve_pub),
482 0 : TALER_TESTING_make_trait_reserve_history (0,
483 0 : &fts->reserve_history),
484 0 : TALER_TESTING_trait_end ()
485 : };
486 :
487 0 : return TALER_TESTING_get_trait (traits,
488 : ret,
489 : trait,
490 : index);
491 : }
492 : }
493 :
494 :
495 : /**
496 : * Create internal state for "/admin/add-incoming" CMD.
497 : *
498 : * @param amount the amount to transfer.
499 : * @param payto_debit_account which account sends money
500 : * @param auth authentication data
501 : * @return the internal state
502 : */
503 : static struct AdminAddIncomingState *
504 6 : make_fts (const char *amount,
505 : const struct TALER_BANK_AuthenticationData *auth,
506 : const char *payto_debit_account)
507 : {
508 : struct AdminAddIncomingState *fts;
509 :
510 6 : fts = GNUNET_new (struct AdminAddIncomingState);
511 6 : fts->exchange_credit_url = auth->wire_gateway_url;
512 6 : fts->payto_debit_account = payto_debit_account;
513 6 : fts->auth = *auth;
514 6 : fts->expected_http_status = MHD_HTTP_OK;
515 6 : if (GNUNET_OK !=
516 6 : TALER_string_to_amount (amount,
517 : &fts->amount))
518 : {
519 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
520 : "Failed to parse amount `%s'\n",
521 : amount);
522 0 : GNUNET_assert (0);
523 : }
524 6 : return fts;
525 : }
526 :
527 :
528 : /**
529 : * Helper function to create admin/add-incoming command.
530 : *
531 : * @param label command label.
532 : * @param fts internal state to use
533 : * @return the command.
534 : */
535 : static struct TALER_TESTING_Command
536 6 : make_command (const char *label,
537 : struct AdminAddIncomingState *fts)
538 : {
539 6 : struct TALER_TESTING_Command cmd = {
540 : .cls = fts,
541 : .label = label,
542 : .run = &admin_add_incoming_run,
543 : .cleanup = &admin_add_incoming_cleanup,
544 : .traits = &admin_add_incoming_traits
545 : };
546 :
547 6 : return cmd;
548 : }
549 :
550 :
551 : struct TALER_TESTING_Command
552 4 : TALER_TESTING_cmd_admin_add_incoming (
553 : const char *label,
554 : const char *amount,
555 : const struct TALER_BANK_AuthenticationData *auth,
556 : const char *payto_debit_account)
557 : {
558 4 : return make_command (label,
559 : make_fts (amount,
560 : auth,
561 : payto_debit_account));
562 : }
563 :
564 :
565 : struct TALER_TESTING_Command
566 2 : TALER_TESTING_cmd_admin_add_incoming_with_ref (
567 : const char *label,
568 : const char *amount,
569 : const struct TALER_BANK_AuthenticationData *auth,
570 : const char *payto_debit_account,
571 : const char *ref,
572 : unsigned int http_status)
573 : {
574 : struct AdminAddIncomingState *fts;
575 :
576 2 : fts = make_fts (amount,
577 : auth,
578 : payto_debit_account);
579 2 : fts->reserve_reference = ref;
580 2 : fts->expected_http_status = http_status;
581 2 : return make_command (label,
582 : fts);
583 : }
584 :
585 :
586 : /**
587 : * Modify a fakebank transfer command to enable retries when the
588 : * reserve is not yet full or we get other transient errors from the
589 : * fakebank.
590 : *
591 : * @param cmd a fakebank transfer command
592 : * @return the command with retries enabled
593 : */
594 : struct TALER_TESTING_Command
595 0 : TALER_TESTING_cmd_admin_add_incoming_retry (struct TALER_TESTING_Command cmd)
596 : {
597 : struct AdminAddIncomingState *fts;
598 :
599 0 : GNUNET_assert (&admin_add_incoming_run == cmd.run);
600 0 : fts = cmd.cls;
601 0 : fts->do_retry = NUM_RETRIES;
602 0 : return cmd;
603 : }
604 :
605 :
606 : /* end of testing_api_cmd_bank_admin_add_incoming.c */
|