testing_api_cmd_bank_history_credit.c (20514B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2018-2024 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as 7 published by the Free Software Foundation; either version 3, or 8 (at your 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 13 GNU 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_history_credit.c 21 * @brief command to check the /history/incoming API from the bank. 22 * @author Marcello Stanisci 23 */ 24 #include "taler/platform.h" 25 #include "taler/taler_json_lib.h" 26 #include <gnunet/gnunet_curl_lib.h> 27 #include "taler/taler_exchange_service.h" 28 #include "taler/taler_testing_lib.h" 29 #include "taler/taler_fakebank_lib.h" 30 #include "taler/taler_bank_service.h" 31 #include "taler/taler_fakebank_lib.h" 32 33 34 /** 35 * Item in the transaction history, as reconstructed from the 36 * command history. 37 */ 38 struct History 39 { 40 41 /** 42 * Wire details. 43 */ 44 struct TALER_BANK_CreditDetails credit_details; 45 46 /** 47 * Serial ID of the wire transfer. 48 */ 49 uint64_t row_id; 50 51 /** 52 * URL to free. 53 */ 54 char *url; 55 }; 56 57 58 /** 59 * State for a "history" CMD. 60 */ 61 struct HistoryState 62 { 63 /** 64 * Base URL of the account offering the "history" operation. 65 */ 66 char *account_url; 67 68 /** 69 * Reference to command defining the 70 * first row number we want in the result. 71 */ 72 const char *start_row_reference; 73 74 /** 75 * How many rows we want in the result, _at most_, 76 * and ascending/descending. 77 */ 78 long long num_results; 79 80 /** 81 * Handle to a pending "history" operation. 82 */ 83 struct TALER_BANK_CreditHistoryHandle *hh; 84 85 /** 86 * The interpreter. 87 */ 88 struct TALER_TESTING_Interpreter *is; 89 90 /** 91 * Authentication data for the operation. 92 */ 93 struct TALER_BANK_AuthenticationData auth; 94 95 /** 96 * Expected number of results (= rows). 97 */ 98 uint64_t results_obtained; 99 100 /** 101 * Set to true if the callback detects something 102 * unexpected. 103 */ 104 bool failed; 105 106 /** 107 * Expected history. 108 */ 109 struct History *h; 110 111 /** 112 * Length of @e h 113 */ 114 unsigned int total; 115 116 }; 117 118 119 /** 120 * Log which history we expected. Called when an error occurs. 121 * 122 * @param h what we expected. 123 * @param h_len number of entries in @a h. 124 * @param off position of the mismatch. 125 */ 126 static void 127 print_expected (struct History *h, 128 unsigned int h_len, 129 unsigned int off) 130 { 131 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 132 "Transaction history (credit) mismatch at position %u/%u\n", 133 off, 134 h_len); 135 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 136 "Expected history:\n"); 137 for (unsigned int i = 0; i<h_len; i++) 138 { 139 const struct TALER_BANK_CreditDetails *cd 140 = &h[i].credit_details; 141 142 switch (cd->type) 143 { 144 case TALER_BANK_CT_RESERVE: 145 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 146 "H(%u): %s (serial: %llu, RES: %s," 147 " counterpart: %s)\n", 148 i, 149 TALER_amount2s (&cd->amount), 150 (unsigned long long) h[i].row_id, 151 TALER_B2S (&cd->details.reserve.reserve_pub), 152 cd->debit_account_uri.full_payto); 153 break; 154 case TALER_BANK_CT_KYCAUTH: 155 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 156 "H(%u): %s (serial: %llu, KYC: %s," 157 " counterpart: %s)\n", 158 i, 159 TALER_amount2s (&cd->amount), 160 (unsigned long long) h[i].row_id, 161 TALER_B2S (&cd->details.kycauth.account_pub), 162 cd->debit_account_uri.full_payto); 163 break; 164 case TALER_BANK_CT_WAD: 165 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 166 "H(%u): %s (serial: %llu, WAD: %s-%s," 167 " counterpart: %s)\n", 168 i, 169 TALER_amount2s (&cd->amount), 170 (unsigned long long) h[i].row_id, 171 TALER_B2S (&cd->details.wad.wad_id), 172 cd->details.wad.origin_exchange_url, 173 cd->debit_account_uri.full_payto); 174 break; 175 } 176 } 177 } 178 179 180 /** 181 * Closure for command_cb(). 182 */ 183 struct IteratorContext 184 { 185 /** 186 * Array of history items to return. 187 */ 188 struct History *h; 189 190 /** 191 * Set to the row ID from where on we should actually process history items, 192 * or NULL if we should process all of them. 193 */ 194 const uint64_t *row_id_start; 195 196 /** 197 * History state we are working on. 198 */ 199 struct HistoryState *hs; 200 201 /** 202 * Current length of the @e h array. 203 */ 204 unsigned int total; 205 206 /** 207 * Current write position in @e h array. 208 */ 209 unsigned int pos; 210 211 /** 212 * Ok equals True whenever a starting row_id was provided AND was found 213 * among the CMDs, OR no starting row was given in the first place. 214 */ 215 bool ok; 216 217 }; 218 219 220 /** 221 * Helper function of build_history() that expands 222 * the history for each relevant command encountered. 223 * 224 * @param[in,out] cls our `struct IteratorContext` 225 * @param cmd a command to process 226 */ 227 static void 228 command_cb (void *cls, 229 const struct TALER_TESTING_Command *cmd) 230 { 231 struct IteratorContext *ic = cls; 232 struct HistoryState *hs = ic->hs; 233 const uint64_t *row_id; 234 const struct TALER_FullPayto *credit_account; 235 const struct TALER_FullPayto *debit_account; 236 const struct TALER_Amount *amount; 237 const struct TALER_ReservePublicKeyP *reserve_pub; 238 const char *exchange_credit_url; 239 240 /** 241 * The following command allows us to skip over those CMDs 242 * that do not offer a "row_id" trait. Such skipped CMDs are 243 * not interesting for building a history. 244 */ 245 if ( (GNUNET_OK != 246 TALER_TESTING_get_trait_bank_row (cmd, 247 &row_id)) || 248 (GNUNET_OK != 249 TALER_TESTING_get_trait_credit_payto_uri (cmd, 250 &credit_account)) || 251 (GNUNET_OK != 252 TALER_TESTING_get_trait_debit_payto_uri (cmd, 253 &debit_account)) || 254 (GNUNET_OK != 255 TALER_TESTING_get_trait_amount (cmd, 256 &amount)) || 257 (GNUNET_OK != 258 TALER_TESTING_get_trait_reserve_pub (cmd, 259 &reserve_pub)) || 260 (GNUNET_OK != 261 TALER_TESTING_get_trait_exchange_bank_account_url ( 262 cmd, 263 &exchange_credit_url)) ) 264 return; // Not an interesting event 265 // FIXME: support KYCAUTH transfer events! 266 // FIXME-#7271: support WAD transfer events! 267 268 /** 269 * Is the interesting event a match with regard to 270 * the row_id value? If yes, store this condition 271 * to the state and analyze the next CMDs. 272 */ 273 if ( (NULL != ic->row_id_start) && 274 (*(ic->row_id_start) == *row_id) && 275 (! ic->ok) ) 276 { 277 ic->ok = true; 278 return; 279 } 280 /** 281 * The interesting event didn't match the wanted 282 * row_id value, analyze the next CMDs. Note: this 283 * branch is relevant only when row_id WAS given. 284 */ 285 if (! ic->ok) 286 return; 287 if (0 != strcasecmp (hs->account_url, 288 exchange_credit_url)) 289 return; // Account mismatch 290 if (ic->total >= GNUNET_MAX (hs->num_results, 291 -hs->num_results) ) 292 { 293 TALER_LOG_DEBUG ("Hit history limit\n"); 294 return; 295 } 296 TALER_LOG_INFO ("Found history: %s->%s for account %s\n", 297 debit_account->full_payto, 298 credit_account->full_payto, 299 hs->account_url); 300 /* found matching record, make sure we have room */ 301 if (ic->pos == ic->total) 302 GNUNET_array_grow (ic->h, 303 ic->total, 304 ic->pos * 2); 305 ic->h[ic->pos].url 306 = GNUNET_strdup (debit_account->full_payto); 307 ic->h[ic->pos].row_id 308 = *row_id; 309 ic->h[ic->pos].credit_details.type 310 = TALER_BANK_CT_RESERVE; 311 ic->h[ic->pos].credit_details.debit_account_uri.full_payto 312 = ic->h[ic->pos].url; 313 ic->h[ic->pos].credit_details.amount 314 = *amount; 315 ic->h[ic->pos].credit_details.details.reserve.reserve_pub 316 = *reserve_pub; 317 ic->pos++; 318 } 319 320 321 /** 322 * This function constructs the list of history elements that 323 * interest the account number of the caller. It has two main 324 * loops: the first to figure out how many history elements have 325 * to be allocated, and the second to actually populate every 326 * element. 327 * 328 * @param hs history state 329 * @param[out] rh history array to initialize. 330 * @return number of entries in @a rh. 331 */ 332 static unsigned int 333 build_history (struct HistoryState *hs, 334 struct History **rh) 335 { 336 struct TALER_TESTING_Interpreter *is = hs->is; 337 struct IteratorContext ic = { 338 .hs = hs 339 }; 340 341 if (NULL != hs->start_row_reference) 342 { 343 const struct TALER_TESTING_Command *add_incoming_cmd; 344 345 TALER_LOG_INFO ("`%s': start row given via reference `%s'\n", 346 TALER_TESTING_interpreter_get_current_label (is), 347 hs->start_row_reference); 348 add_incoming_cmd 349 = TALER_TESTING_interpreter_lookup_command (is, 350 hs->start_row_reference); 351 GNUNET_assert (NULL != add_incoming_cmd); 352 GNUNET_assert (GNUNET_OK == 353 TALER_TESTING_get_trait_row (add_incoming_cmd, 354 &ic.row_id_start)); 355 } 356 357 ic.ok = false; 358 if (NULL == ic.row_id_start) 359 ic.ok = true; 360 GNUNET_array_grow (ic.h, 361 ic.total, 362 4); 363 GNUNET_assert (0 != hs->num_results); 364 TALER_TESTING_iterate (is, 365 hs->num_results > 0, 366 &command_cb, 367 &ic); 368 GNUNET_assert (ic.ok); 369 GNUNET_array_grow (ic.h, 370 ic.total, 371 ic.pos); 372 if (0 == ic.pos) 373 TALER_LOG_DEBUG ("Empty credit history computed\n"); 374 *rh = ic.h; 375 return ic.pos; 376 } 377 378 379 /** 380 * Check that the "/history/incoming" response matches the 381 * CMD whose offset in the list of CMDs is @a off. 382 * 383 * @param h expected history (array) 384 * @param total length of @a h 385 * @param off the offset (of the CMD list) where the command 386 * to check is. 387 * @param credit_details the expected transaction details. 388 * @return #GNUNET_OK if the transaction is what we expect. 389 */ 390 static enum GNUNET_GenericReturnValue 391 check_result (struct History *h, 392 unsigned int total, 393 unsigned int off, 394 const struct TALER_BANK_CreditDetails *credit_details) 395 { 396 if (off >= total) 397 { 398 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 399 "Test says history has at most %u" 400 " results, but got result #%u to check\n", 401 total, 402 off); 403 print_expected (h, 404 total, 405 off); 406 return GNUNET_SYSERR; 407 } 408 if ( (h[off].credit_details.type != 409 credit_details->type) || 410 (0 != TALER_amount_cmp (&h[off].credit_details.amount, 411 &credit_details->amount)) || 412 (0 != TALER_full_payto_normalize_and_cmp ( 413 h[off].credit_details.debit_account_uri, 414 credit_details->debit_account_uri)) ) 415 { 416 GNUNET_break (0); 417 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 418 "expected debit_account_uri: %s with %s\n", 419 h[off].credit_details.debit_account_uri.full_payto, 420 TALER_amount2s (&h[off].credit_details.amount)); 421 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 422 "actual debit_account_uri: %s with %s\n", 423 credit_details->debit_account_uri.full_payto, 424 TALER_amount2s (&credit_details->amount)); 425 print_expected (h, 426 total, 427 off); 428 return GNUNET_SYSERR; 429 } 430 switch (credit_details->type) 431 { 432 case TALER_BANK_CT_RESERVE: 433 if (0 != 434 GNUNET_memcmp (&h[off].credit_details.details.reserve.reserve_pub, 435 &credit_details->details.reserve.reserve_pub)) 436 { 437 GNUNET_break (0); 438 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 439 "expected debit_account_uri: %s with %s for %s\n", 440 h[off].credit_details.debit_account_uri.full_payto, 441 TALER_amount2s (&h[off].credit_details.amount), 442 TALER_B2S (&h[off].credit_details.details.reserve.reserve_pub) 443 ); 444 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 445 "actual debit_account_uri: %s with %s for %s\n", 446 credit_details->debit_account_uri.full_payto, 447 TALER_amount2s (&credit_details->amount), 448 TALER_B2S (&credit_details->details.reserve.reserve_pub)); 449 print_expected (h, 450 total, 451 off); 452 return GNUNET_SYSERR; 453 } 454 break; 455 case TALER_BANK_CT_KYCAUTH: 456 if (0 != GNUNET_memcmp (&h[off].credit_details.details.kycauth.account_pub, 457 &credit_details->details.kycauth.account_pub)) 458 { 459 GNUNET_break (0); 460 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 461 "expected debit_account_uri: %s with %s for %s\n", 462 h[off].credit_details.debit_account_uri.full_payto, 463 TALER_amount2s (&h[off].credit_details.amount), 464 TALER_B2S (&h[off].credit_details.details.kycauth.account_pub) 465 ); 466 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 467 "actual debit_account_uri: %s with %s for %s\n", 468 credit_details->debit_account_uri.full_payto, 469 TALER_amount2s (&credit_details->amount), 470 TALER_B2S (&credit_details->details.kycauth.account_pub)); 471 print_expected (h, 472 total, 473 off); 474 return GNUNET_SYSERR; 475 } 476 break; 477 case TALER_BANK_CT_WAD: 478 if ( (0 != GNUNET_memcmp (&h[off].credit_details.details.wad.wad_id, 479 &credit_details->details.wad.wad_id)) || 480 (0 != strcmp (h[off].credit_details.details.wad.origin_exchange_url, 481 credit_details->details.wad.origin_exchange_url)) ) 482 { 483 GNUNET_break (0); 484 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 485 "expected debit_account_uri: %s with %s for %s-%s\n", 486 h[off].credit_details.debit_account_uri.full_payto, 487 TALER_amount2s (&h[off].credit_details.amount), 488 h[off].credit_details.details.wad.origin_exchange_url, 489 TALER_B2S (&h[off].credit_details.details.wad.wad_id)); 490 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 491 "actual debit_account_uri: %s with %s for %s-%s\n", 492 credit_details->debit_account_uri.full_payto, 493 TALER_amount2s (&credit_details->amount), 494 credit_details->details.wad.origin_exchange_url, 495 TALER_B2S (&credit_details->details.wad.wad_id)); 496 print_expected (h, 497 total, 498 off); 499 return GNUNET_SYSERR; 500 } 501 break; 502 } 503 return GNUNET_OK; 504 } 505 506 507 /** 508 * This callback will (1) check that the HTTP response code 509 * is acceptable and (2) that the history is consistent. The 510 * consistency is checked by going through all the past CMDs, 511 * reconstructing then the expected history as of those, and 512 * finally check it against what the bank returned. 513 * 514 * @param cls closure. 515 * @param chr http response details 516 */ 517 static void 518 history_cb (void *cls, 519 const struct TALER_BANK_CreditHistoryResponse *chr) 520 { 521 struct HistoryState *hs = cls; 522 struct TALER_TESTING_Interpreter *is = hs->is; 523 524 hs->hh = NULL; 525 switch (chr->http_status) 526 { 527 case 0: 528 GNUNET_break (0); 529 goto error; 530 case MHD_HTTP_OK: 531 for (unsigned int i = 0; i<chr->details.ok.details_length; i++) 532 { 533 const struct TALER_BANK_CreditDetails *cd = 534 &chr->details.ok.details[i]; 535 536 /* check current element */ 537 if (GNUNET_OK != 538 check_result (hs->h, 539 hs->total, 540 hs->results_obtained, 541 cd)) 542 { 543 GNUNET_break (0); 544 json_dumpf (chr->response, 545 stderr, 546 JSON_COMPACT); 547 hs->failed = true; 548 hs->hh = NULL; 549 TALER_TESTING_interpreter_fail (is); 550 return; 551 } 552 hs->results_obtained++; 553 } 554 TALER_TESTING_interpreter_next (is); 555 return; 556 case MHD_HTTP_NO_CONTENT: 557 if (0 == hs->total) 558 { 559 /* not found is OK for empty history */ 560 TALER_TESTING_interpreter_next (is); 561 return; 562 } 563 GNUNET_break (0); 564 goto error; 565 case MHD_HTTP_NOT_FOUND: 566 if (0 == hs->total) 567 { 568 /* not found is OK for empty history */ 569 TALER_TESTING_interpreter_next (is); 570 return; 571 } 572 GNUNET_break (0); 573 goto error; 574 default: 575 hs->hh = NULL; 576 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 577 "Unwanted response code from /history/incoming: %u\n", 578 chr->http_status); 579 TALER_TESTING_interpreter_fail (is); 580 return; 581 } 582 error: 583 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 584 "Expected history of length %u, got %llu;" 585 " HTTP status code: %u/%d, failed: %d\n", 586 hs->total, 587 (unsigned long long) hs->results_obtained, 588 chr->http_status, 589 (int) chr->ec, 590 hs->failed ? 1 : 0); 591 print_expected (hs->h, 592 hs->total, 593 UINT_MAX); 594 TALER_TESTING_interpreter_fail (is); 595 } 596 597 598 /** 599 * Run the command. 600 * 601 * @param cls closure. 602 * @param cmd the command to execute. 603 * @param is the interpreter state. 604 */ 605 static void 606 history_run (void *cls, 607 const struct TALER_TESTING_Command *cmd, 608 struct TALER_TESTING_Interpreter *is) 609 { 610 struct HistoryState *hs = cls; 611 uint64_t row_id = (hs->num_results > 0) ? 0 : UINT64_MAX; 612 const uint64_t *row_ptr; 613 614 (void) cmd; 615 hs->is = is; 616 /* Get row_id from trait. */ 617 if (NULL != hs->start_row_reference) 618 { 619 const struct TALER_TESTING_Command *history_cmd; 620 621 history_cmd = TALER_TESTING_interpreter_lookup_command ( 622 is, 623 hs->start_row_reference); 624 if (NULL == history_cmd) 625 TALER_TESTING_FAIL (is); 626 627 if (GNUNET_OK != 628 TALER_TESTING_get_trait_row (history_cmd, 629 &row_ptr)) 630 TALER_TESTING_FAIL (is); 631 else 632 row_id = *row_ptr; 633 TALER_LOG_DEBUG ("row id (from trait) is %llu\n", 634 (unsigned long long) row_id); 635 } 636 hs->total = build_history (hs, 637 &hs->h); 638 hs->hh = TALER_BANK_credit_history ( 639 TALER_TESTING_interpreter_get_context (is), 640 &hs->auth, 641 row_id, 642 hs->num_results, 643 GNUNET_TIME_UNIT_ZERO, 644 &history_cb, 645 hs); 646 GNUNET_assert (NULL != hs->hh); 647 } 648 649 650 /** 651 * Free the state from a "history" CMD, and possibly cancel 652 * a pending operation thereof. 653 * 654 * @param cls closure. 655 * @param cmd the command which is being cleaned up. 656 */ 657 static void 658 history_cleanup (void *cls, 659 const struct TALER_TESTING_Command *cmd) 660 { 661 struct HistoryState *hs = cls; 662 663 (void) cmd; 664 if (NULL != hs->hh) 665 { 666 TALER_TESTING_command_incomplete (hs->is, 667 cmd->label); 668 TALER_BANK_credit_history_cancel (hs->hh); 669 } 670 GNUNET_free (hs->account_url); 671 for (unsigned int off = 0; off<hs->total; off++) 672 GNUNET_free (hs->h[off].url); 673 GNUNET_free (hs->h); 674 GNUNET_free (hs); 675 } 676 677 678 struct TALER_TESTING_Command 679 TALER_TESTING_cmd_bank_credits ( 680 const char *label, 681 const struct TALER_BANK_AuthenticationData *auth, 682 const char *start_row_reference, 683 long long num_results) 684 { 685 struct HistoryState *hs; 686 687 hs = GNUNET_new (struct HistoryState); 688 hs->account_url = GNUNET_strdup (auth->wire_gateway_url); 689 hs->start_row_reference = start_row_reference; 690 hs->num_results = num_results; 691 hs->auth = *auth; 692 { 693 struct TALER_TESTING_Command cmd = { 694 .label = label, 695 .cls = hs, 696 .run = &history_run, 697 .cleanup = &history_cleanup 698 }; 699 700 return cmd; 701 } 702 } 703 704 705 /* end of testing_api_cmd_credit_history.c */