testing_api_cmd_reserve_history.c (18520B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2014-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_reserve_history.c 21 * @brief Implement the /reserve/history test command. 22 * @author Marcello Stanisci 23 */ 24 #include "taler/taler_json_lib.h" 25 #include <gnunet/gnunet_curl_lib.h> 26 struct HistoryState; 27 #define TALER_EXCHANGE_GET_RESERVES_HISTORY_RESULT_CLOSURE struct HistoryState 28 #include "taler/exchange/get-reserves-RESERVE_PUB-history.h" 29 #include "taler/taler_testing_lib.h" 30 31 32 /** 33 * State for a "history" CMD. 34 */ 35 struct HistoryState 36 { 37 38 /** 39 * Public key of the reserve being analyzed. 40 */ 41 struct TALER_ReservePublicKeyP reserve_pub; 42 43 /** 44 * Label to the command which created the reserve to check, 45 * needed to resort the reserve key. 46 */ 47 const char *reserve_reference; 48 49 /** 50 * Handle to the "reserve history" operation. 51 */ 52 struct TALER_EXCHANGE_GetReservesHistoryHandle *rsh; 53 54 /** 55 * Expected reserve balance. 56 */ 57 const char *expected_balance; 58 59 /** 60 * Private key of the reserve being analyzed. 61 */ 62 const struct TALER_ReservePrivateKeyP *reserve_priv; 63 64 /** 65 * Interpreter state. 66 */ 67 struct TALER_TESTING_Interpreter *is; 68 69 /** 70 * Expected HTTP response code. 71 */ 72 unsigned int expected_response_code; 73 74 }; 75 76 77 /** 78 * Closure for analysis_cb(). 79 */ 80 struct AnalysisContext 81 { 82 /** 83 * Reserve public key we are looking at. 84 */ 85 const struct TALER_ReservePublicKeyP *reserve_pub; 86 87 /** 88 * Length of the @e history array. 89 */ 90 unsigned int history_length; 91 92 /** 93 * Array of history items to match. 94 */ 95 const struct TALER_EXCHANGE_ReserveHistoryEntry *history; 96 97 /** 98 * Array of @e history_length of matched entries. 99 */ 100 bool *found; 101 102 /** 103 * Set to true if an entry could not be found. 104 */ 105 bool failure; 106 }; 107 108 109 /** 110 * Compare @a h1 and @a h2. 111 * 112 * @param h1 a history entry 113 * @param h2 a history entry 114 * @return 0 if @a h1 and @a h2 are equal 115 */ 116 static int 117 history_entry_cmp ( 118 const struct TALER_EXCHANGE_ReserveHistoryEntry *h1, 119 const struct TALER_EXCHANGE_ReserveHistoryEntry *h2) 120 { 121 if (h1->type != h2->type) 122 return 1; 123 switch (h1->type) 124 { 125 case TALER_EXCHANGE_RTT_CREDIT: 126 if ( (GNUNET_OK == 127 TALER_amount_cmp_currency (&h1->amount, 128 &h2->amount)) && 129 (0 == 130 TALER_amount_cmp (&h1->amount, 131 &h2->amount)) && 132 (0 == 133 TALER_full_payto_cmp (h1->details.in_details.sender_url, 134 h2->details.in_details.sender_url)) && 135 (h1->details.in_details.wire_reference == 136 h2->details.in_details.wire_reference) && 137 (GNUNET_TIME_timestamp_cmp (h1->details.in_details.timestamp, 138 ==, 139 h2->details.in_details.timestamp)) ) 140 return 0; 141 return 1; 142 case TALER_EXCHANGE_RTT_WITHDRAWAL: 143 if ( (GNUNET_OK == 144 TALER_amount_cmp_currency (&h1->amount, 145 &h2->amount)) && 146 (0 == 147 TALER_amount_cmp (&h1->amount, 148 &h2->amount)) && 149 (0 == 150 TALER_amount_cmp (&h1->details.withdraw.fee, 151 &h2->details.withdraw.fee)) && 152 (h1->details.withdraw.age_restricted == 153 h2->details.withdraw.age_restricted) && 154 ((! h1->details.withdraw.age_restricted) || 155 (h1->details.withdraw.max_age == h2->details.withdraw.max_age) )) 156 return 0; 157 return 1; 158 case TALER_EXCHANGE_RTT_RECOUP: 159 /* exchange_sig, exchange_pub and timestamp are NOT available 160 from the original recoup response, hence here NOT check(able/ed) */ 161 if ( (GNUNET_OK == 162 TALER_amount_cmp_currency (&h1->amount, 163 &h2->amount)) && 164 (0 == 165 TALER_amount_cmp (&h1->amount, 166 &h2->amount)) && 167 (0 == 168 GNUNET_memcmp (&h1->details.recoup_details.coin_pub, 169 &h2->details.recoup_details.coin_pub)) ) 170 return 0; 171 return 1; 172 case TALER_EXCHANGE_RTT_CLOSING: 173 /* testing_api_cmd_exec_closer doesn't set the 174 receiver_account_details, exchange_sig, exchange_pub or wtid or timestamp 175 so we cannot test for it here. but if the amount matches, 176 that should be good enough. */ 177 if ( (GNUNET_OK == 178 TALER_amount_cmp_currency (&h1->amount, 179 &h2->amount)) && 180 (0 == 181 TALER_amount_cmp (&h1->amount, 182 &h2->amount)) && 183 (0 == 184 TALER_amount_cmp (&h1->details.close_details.fee, 185 &h2->details.close_details.fee)) ) 186 return 0; 187 return 1; 188 case TALER_EXCHANGE_RTT_MERGE: 189 if ( (GNUNET_OK == 190 TALER_amount_cmp_currency (&h1->amount, 191 &h2->amount)) && 192 (0 == 193 TALER_amount_cmp (&h1->amount, 194 &h2->amount)) && 195 (0 == 196 TALER_amount_cmp (&h1->details.merge_details.purse_fee, 197 &h2->details.merge_details.purse_fee)) && 198 (GNUNET_TIME_timestamp_cmp (h1->details.merge_details.merge_timestamp, 199 ==, 200 h2->details.merge_details.merge_timestamp)) 201 && 202 (GNUNET_TIME_timestamp_cmp (h1->details.merge_details.purse_expiration, 203 ==, 204 h2->details.merge_details.purse_expiration) 205 ) 206 && 207 (0 == 208 GNUNET_memcmp (&h1->details.merge_details.merge_pub, 209 &h2->details.merge_details.merge_pub)) && 210 (0 == 211 GNUNET_memcmp (&h1->details.merge_details.h_contract_terms, 212 &h2->details.merge_details.h_contract_terms)) && 213 (0 == 214 GNUNET_memcmp (&h1->details.merge_details.purse_pub, 215 &h2->details.merge_details.purse_pub)) && 216 (0 == 217 GNUNET_memcmp (&h1->details.merge_details.reserve_sig, 218 &h2->details.merge_details.reserve_sig)) && 219 (h1->details.merge_details.min_age == 220 h2->details.merge_details.min_age) && 221 (h1->details.merge_details.flags == 222 h2->details.merge_details.flags) ) 223 return 0; 224 return 1; 225 case TALER_EXCHANGE_RTT_OPEN: 226 if ( (GNUNET_OK == 227 TALER_amount_cmp_currency (&h1->amount, 228 &h2->amount)) && 229 (0 == 230 TALER_amount_cmp (&h1->amount, 231 &h2->amount)) && 232 (GNUNET_TIME_timestamp_cmp ( 233 h1->details.open_request.request_timestamp, 234 ==, 235 h2->details.open_request.request_timestamp)) && 236 (GNUNET_TIME_timestamp_cmp ( 237 h1->details.open_request.reserve_expiration, 238 ==, 239 h2->details.open_request.reserve_expiration)) && 240 (h1->details.open_request.purse_limit == 241 h2->details.open_request.purse_limit) && 242 (0 == 243 TALER_amount_cmp (&h1->details.open_request.reserve_payment, 244 &h2->details.open_request.reserve_payment)) && 245 (0 == 246 GNUNET_memcmp (&h1->details.open_request.reserve_sig, 247 &h2->details.open_request.reserve_sig)) ) 248 return 0; 249 return 1; 250 case TALER_EXCHANGE_RTT_CLOSE: 251 if ( (GNUNET_OK == 252 TALER_amount_cmp_currency (&h1->amount, 253 &h2->amount)) && 254 (0 == 255 TALER_amount_cmp (&h1->amount, 256 &h2->amount)) && 257 (GNUNET_TIME_timestamp_cmp ( 258 h1->details.close_request.request_timestamp, 259 ==, 260 h2->details.close_request.request_timestamp)) && 261 (0 == 262 GNUNET_memcmp (&h1->details.close_request.target_account_h_payto, 263 &h2->details.close_request.target_account_h_payto)) && 264 (0 == 265 GNUNET_memcmp (&h1->details.close_request.reserve_sig, 266 &h2->details.close_request.reserve_sig)) ) 267 return 0; 268 return 1; 269 } 270 GNUNET_assert (0); 271 return 1; 272 } 273 274 275 /** 276 * Check if @a cmd changed the reserve, if so, find the 277 * entry in our history and set the respective index in found 278 * to true. If the entry is not found, set failure. 279 * 280 * @param cls our `struct AnalysisContext *` 281 * @param cmd command to analyze for impact on history 282 */ 283 static void 284 analyze_command (void *cls, 285 const struct TALER_TESTING_Command *cmd) 286 { 287 struct AnalysisContext *ac = cls; 288 const struct TALER_ReservePublicKeyP *reserve_pub = ac->reserve_pub; 289 const struct TALER_EXCHANGE_ReserveHistoryEntry *history = ac->history; 290 unsigned int history_length = ac->history_length; 291 bool *found = ac->found; 292 293 if (TALER_TESTING_cmd_is_batch (cmd)) 294 { 295 struct TALER_TESTING_Command *cur; 296 struct TALER_TESTING_Command *bcmd; 297 298 cur = TALER_TESTING_cmd_batch_get_current (cmd); 299 if (GNUNET_OK != 300 TALER_TESTING_get_trait_batch_cmds (cmd, 301 &bcmd)) 302 { 303 GNUNET_break (0); 304 ac->failure = true; 305 return; 306 } 307 for (unsigned int i = 0; NULL != bcmd[i].label; i++) 308 { 309 struct TALER_TESTING_Command *step = &bcmd[i]; 310 311 analyze_command (ac, 312 step); 313 if (ac->failure) 314 { 315 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 316 "Entry for batch step `%s' missing in reserve history\n", 317 step->label); 318 return; 319 } 320 if (step == cur) 321 break; /* if *we* are in a batch, make sure not to analyze commands past 'now' */ 322 } 323 return; 324 } 325 326 { 327 const struct TALER_ReservePublicKeyP *rp; 328 bool matched; 329 330 if (GNUNET_OK != 331 TALER_TESTING_get_trait_reserve_pub (cmd, 332 &rp)) 333 return; /* command does nothing for reserves */ 334 if (0 != 335 GNUNET_memcmp (rp, 336 reserve_pub)) 337 return; /* command affects some _other_ reserve */ 338 for (unsigned int j = 0; true; j++) 339 { 340 const struct TALER_EXCHANGE_ReserveHistoryEntry *he; 341 342 if (GNUNET_OK != 343 TALER_TESTING_get_trait_reserve_history (cmd, 344 j, 345 &he)) 346 { 347 /* NOTE: only for debugging... */ 348 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 349 "Command `%s' has the reserve_pub, but lacks reserve history trait for index #%u\n", 350 cmd->label, 351 j); 352 return; /* command does nothing for reserves */ 353 } 354 matched = false; 355 for (unsigned int i = 0; i < history_length; i++) 356 { 357 if (found[i]) 358 continue; /* already found, skip */ 359 if (0 == 360 history_entry_cmp (he, 361 &history[i])) 362 { 363 found[i] = true; 364 matched = true; 365 break; 366 } 367 } 368 if (! matched) 369 { 370 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 371 "Reserve history entry not found for command `%s'\n", 372 cmd->label); 373 ac->failure = true; 374 return; 375 } 376 } 377 } 378 } 379 380 381 /** 382 * Check that the reserve balance and HTTP response code are 383 * both acceptable. 384 * 385 * @param ss closure. 386 * @param rs HTTP response details 387 */ 388 static void 389 reserve_history_cb (struct HistoryState *ss, 390 const struct TALER_EXCHANGE_GetReservesHistoryResponse *rs) 391 { 392 struct TALER_TESTING_Interpreter *is = ss->is; 393 struct TALER_Amount eb; 394 395 ss->rsh = NULL; 396 if (ss->expected_response_code != rs->hr.http_status) 397 { 398 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 399 "Unexpected HTTP response code: %d in %s:%u\n", 400 rs->hr.http_status, 401 __FILE__, 402 __LINE__); 403 json_dumpf (rs->hr.reply, 404 stderr, 405 0); 406 TALER_TESTING_interpreter_fail (ss->is); 407 return; 408 } 409 if (MHD_HTTP_OK != rs->hr.http_status) 410 { 411 TALER_TESTING_interpreter_next (is); 412 return; 413 } 414 GNUNET_assert (GNUNET_OK == 415 TALER_string_to_amount (ss->expected_balance, 416 &eb)); 417 418 if (0 != TALER_amount_cmp (&eb, 419 &rs->details.ok.balance)) 420 { 421 GNUNET_break (0); 422 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 423 "Unexpected amount in reserve: %s\n", 424 TALER_amount_to_string (&rs->details.ok.balance)); 425 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 426 "Expected balance of: %s\n", 427 TALER_amount_to_string (&eb)); 428 TALER_TESTING_interpreter_fail (ss->is); 429 return; 430 } 431 { 432 bool found[rs->details.ok.history_len]; 433 struct AnalysisContext ac = { 434 .reserve_pub = &ss->reserve_pub, 435 .history = rs->details.ok.history, 436 .history_length = rs->details.ok.history_len, 437 .found = found 438 }; 439 440 memset (found, 441 0, 442 sizeof (found)); 443 TALER_TESTING_iterate (is, 444 true, 445 &analyze_command, 446 &ac); 447 if (ac.failure) 448 { 449 json_dumpf (rs->hr.reply, 450 stderr, 451 JSON_INDENT (2)); 452 TALER_TESTING_interpreter_fail (ss->is); 453 return; 454 } 455 for (unsigned int i = 0; i<rs->details.ok.history_len; i++) 456 { 457 if (found[i]) 458 continue; 459 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 460 "History entry at index %u of type %d not justified by command history\n", 461 i, 462 rs->details.ok.history[i].type); 463 json_dumpf (rs->hr.reply, 464 stderr, 465 JSON_INDENT (2)); 466 TALER_TESTING_interpreter_fail (ss->is); 467 return; 468 } 469 } 470 TALER_TESTING_interpreter_next (is); 471 } 472 473 474 /** 475 * Run the command. 476 * 477 * @param cls closure. 478 * @param cmd the command being executed. 479 * @param is the interpreter state. 480 */ 481 static void 482 history_run (void *cls, 483 const struct TALER_TESTING_Command *cmd, 484 struct TALER_TESTING_Interpreter *is) 485 { 486 struct HistoryState *ss = cls; 487 const struct TALER_TESTING_Command *create_reserve; 488 489 ss->is = is; 490 create_reserve 491 = TALER_TESTING_interpreter_lookup_command (is, 492 ss->reserve_reference); 493 if (NULL == create_reserve) 494 { 495 GNUNET_break (0); 496 TALER_TESTING_interpreter_fail (is); 497 return; 498 } 499 if (GNUNET_OK != 500 TALER_TESTING_get_trait_reserve_priv (create_reserve, 501 &ss->reserve_priv)) 502 { 503 GNUNET_break (0); 504 TALER_LOG_ERROR ("Failed to find reserve_priv for history query\n"); 505 TALER_TESTING_interpreter_fail (is); 506 return; 507 } 508 GNUNET_CRYPTO_eddsa_key_get_public (&ss->reserve_priv->eddsa_priv, 509 &ss->reserve_pub.eddsa_pub); 510 ss->rsh = TALER_EXCHANGE_get_reserves_history_create ( 511 TALER_TESTING_interpreter_get_context (is), 512 TALER_TESTING_get_exchange_url (is), 513 TALER_TESTING_get_keys (is), 514 ss->reserve_priv); 515 if (NULL == ss->rsh) 516 { 517 GNUNET_break (0); 518 TALER_TESTING_interpreter_fail (is); 519 return; 520 } 521 if (TALER_EC_NONE != 522 TALER_EXCHANGE_get_reserves_history_start (ss->rsh, 523 &reserve_history_cb, 524 ss)) 525 { 526 GNUNET_break (0); 527 TALER_EXCHANGE_get_reserves_history_cancel (ss->rsh); 528 ss->rsh = NULL; 529 TALER_TESTING_interpreter_fail (is); 530 return; 531 } 532 } 533 534 535 /** 536 * Offer internal data from a "history" CMD, to other commands. 537 * 538 * @param cls closure. 539 * @param[out] ret result. 540 * @param trait name of the trait. 541 * @param index index number of the object to offer. 542 * @return #GNUNET_OK on success. 543 */ 544 static enum GNUNET_GenericReturnValue 545 history_traits (void *cls, 546 const void **ret, 547 const char *trait, 548 unsigned int index) 549 { 550 struct HistoryState *hs = cls; 551 struct TALER_TESTING_Trait traits[] = { 552 TALER_TESTING_make_trait_reserve_pub (&hs->reserve_pub), 553 TALER_TESTING_trait_end () 554 }; 555 556 return TALER_TESTING_get_trait (traits, 557 ret, 558 trait, 559 index); 560 } 561 562 563 /** 564 * Cleanup the state from a "reserve history" CMD, and possibly 565 * cancel a pending operation thereof. 566 * 567 * @param cls closure. 568 * @param cmd the command which is being cleaned up. 569 */ 570 static void 571 history_cleanup (void *cls, 572 const struct TALER_TESTING_Command *cmd) 573 { 574 struct HistoryState *ss = cls; 575 576 if (NULL != ss->rsh) 577 { 578 TALER_TESTING_command_incomplete (ss->is, 579 cmd->label); 580 TALER_EXCHANGE_get_reserves_history_cancel (ss->rsh); 581 ss->rsh = NULL; 582 } 583 GNUNET_free (ss); 584 } 585 586 587 struct TALER_TESTING_Command 588 TALER_TESTING_cmd_reserve_history (const char *label, 589 const char *reserve_reference, 590 const char *expected_balance, 591 unsigned int expected_response_code) 592 { 593 struct HistoryState *ss; 594 595 GNUNET_assert (NULL != reserve_reference); 596 ss = GNUNET_new (struct HistoryState); 597 ss->reserve_reference = reserve_reference; 598 ss->expected_balance = expected_balance; 599 ss->expected_response_code = expected_response_code; 600 { 601 struct TALER_TESTING_Command cmd = { 602 .cls = ss, 603 .label = label, 604 .run = &history_run, 605 .cleanup = &history_cleanup, 606 .traits = &history_traits 607 }; 608 609 return cmd; 610 } 611 }