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