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