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