testing_api_cmd_recoup_refresh.c (17979B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2018-2026 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU General Public License for more details. 12 13 You should have received a copy of the GNU General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file testing/testing_api_cmd_recoup_refresh.c 18 * @brief Implement the /recoup-refresh test command. 19 * @author Marcello Stanisci 20 * @author Christian Grothoff 21 * @author Özgür Kesim 22 */ 23 #include "platform.h" 24 #include "taler/taler_json_lib.h" 25 #include <gnunet/gnunet_curl_lib.h> 26 #include "taler/taler_testing_lib.h" 27 28 29 /** 30 * State for a "recoup-refresh" CMD. 31 */ 32 struct RecoupRefreshState 33 { 34 /** 35 * Expected HTTP status code. 36 */ 37 unsigned int expected_response_code; 38 39 /** 40 * Label of the reveal-melt command the fresh coins came from. 41 */ 42 char *reveal_reference; 43 44 /** 45 * Label of the melt command. 46 */ 47 const char *melt_reference; 48 49 /** 50 * Indices (in the reveal command) of the coins to recoup. 51 */ 52 unsigned int *indices; 53 54 /** 55 * Number of entries in @e indices, @e ches_new, @e ches_old 56 * and @e coin_pubs. 57 */ 58 size_t num_indices; 59 60 /** 61 * Amount we expect to be recouped per coin, invalid if 62 * not specified. 63 */ 64 struct TALER_Amount amount; 65 66 /** 67 * Interpreter state. 68 */ 69 struct TALER_TESTING_Interpreter *is; 70 71 /** 72 * Handle to the request. 73 */ 74 struct TALER_EXCHANGE_PostRecoupRefreshHandle *ph; 75 76 /** 77 * Old coin that is credited. 78 */ 79 struct TALER_CoinSpendPublicKeyP old_coin_pub; 80 81 /** 82 * Coin history entries we expect for the recouped coins. 83 */ 84 struct TALER_EXCHANGE_CoinHistoryEntry *ches_new; 85 86 /** 87 * Coin history entries we expect for the old coin, one per 88 * recouped coin. 89 */ 90 struct TALER_EXCHANGE_CoinHistoryEntry *ches_old; 91 92 /** 93 * Public keys of the recouped coins. 94 */ 95 struct TALER_CoinSpendPublicKeyP *coin_pubs; 96 }; 97 98 99 /** 100 * Check the response. 101 * 102 * @param cls closure, our `struct RecoupRefreshState` 103 * @param rrr response details 104 */ 105 static void 106 recoup_refresh_cb (void *cls, 107 const struct TALER_EXCHANGE_PostRecoupRefreshResponse *rrr) 108 { 109 struct RecoupRefreshState *rrs = cls; 110 const struct TALER_EXCHANGE_HttpResponse *hr = &rrr->hr; 111 struct TALER_TESTING_Interpreter *is = rrs->is; 112 113 rrs->ph = NULL; 114 if (rrs->expected_response_code != hr->http_status) 115 { 116 TALER_TESTING_unexpected_status (is, 117 hr->http_status, 118 rrs->expected_response_code); 119 return; 120 } 121 switch (hr->http_status) 122 { 123 case MHD_HTTP_OK: 124 if (0 != GNUNET_memcmp (&rrr->details.ok.old_coin_pub, 125 &rrs->old_coin_pub)) 126 { 127 GNUNET_break (0); 128 TALER_TESTING_interpreter_fail (is); 129 return; 130 } 131 if (rrr->details.ok.num_recoups != rrs->num_indices) 132 { 133 GNUNET_break (0); 134 TALER_TESTING_interpreter_fail (is); 135 return; 136 } 137 for (size_t i = 0; i < rrs->num_indices; i++) 138 { 139 const struct TALER_RecoupedCoin *rc = &rrr->details.ok.recoups[i]; 140 141 if (0 != GNUNET_memcmp (&rc->coin_pub, 142 &rrs->coin_pubs[i])) 143 { 144 GNUNET_break (0); 145 TALER_TESTING_interpreter_fail (is); 146 return; 147 } 148 if ( (GNUNET_OK == 149 TALER_amount_is_valid (&rrs->amount)) && 150 (0 != TALER_amount_cmp (&rc->amount, 151 &rrs->amount)) ) 152 { 153 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 154 "Recouped %s for coin %u, expected %s\n", 155 TALER_amount2s (&rc->amount), 156 rrs->indices[i], 157 TALER_amount2s (&rrs->amount)); 158 GNUNET_break (0); 159 TALER_TESTING_interpreter_fail (is); 160 return; 161 } 162 rrs->ches_new[i].amount = rc->amount; 163 rrs->ches_old[i].amount = rc->amount; 164 } 165 break; 166 default: 167 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 168 "Recoup-refresh failed as expected with HTTP status %u/%d\n", 169 hr->http_status, 170 (int) hr->ec); 171 break; 172 } 173 TALER_TESTING_interpreter_next (is); 174 } 175 176 177 /** 178 * Run the command. 179 * 180 * @param cls closure. 181 * @param cmd the command to execute. 182 * @param is the interpreter state. 183 */ 184 static void 185 recoup_refresh_run (void *cls, 186 const struct TALER_TESTING_Command *cmd, 187 struct TALER_TESTING_Interpreter *is) 188 { 189 struct RecoupRefreshState *rrs = cls; 190 const struct TALER_TESTING_Command *reveal_cmd; 191 const struct TALER_TESTING_Command *melt_cmd; 192 const struct TALER_CoinSpendPublicKeyP *old_coin_pub; 193 const struct TALER_RefreshCommitmentP *rc; 194 const struct TALER_BlindingMasterSeedP *blinding_seed = NULL; 195 const unsigned int *num_coins_p; 196 size_t num_coins; 197 198 (void) cmd; 199 rrs->is = is; 200 reveal_cmd = TALER_TESTING_interpreter_lookup_command (is, 201 rrs->reveal_reference); 202 melt_cmd = TALER_TESTING_interpreter_lookup_command (is, 203 rrs->melt_reference); 204 if ( (NULL == reveal_cmd) || 205 (NULL == melt_cmd) ) 206 { 207 GNUNET_break (0); 208 TALER_TESTING_interpreter_fail (is); 209 return; 210 } 211 if ( (GNUNET_OK != 212 TALER_TESTING_get_trait_coin_pub (melt_cmd, 213 0, 214 &old_coin_pub)) || 215 (GNUNET_OK != 216 TALER_TESTING_get_trait_refresh_commitment (melt_cmd, 217 &rc)) || 218 (GNUNET_OK != 219 TALER_TESTING_get_trait_array_length (reveal_cmd, 220 &num_coins_p)) ) 221 { 222 GNUNET_break (0); 223 TALER_TESTING_interpreter_fail (is); 224 return; 225 } 226 /* NULL if the melt used no CS denomination */ 227 if (GNUNET_OK != 228 TALER_TESTING_get_trait_blinding_seed (melt_cmd, 229 &blinding_seed)) 230 blinding_seed = NULL; 231 rrs->old_coin_pub = *old_coin_pub; 232 num_coins = *num_coins_p; 233 if (0 == num_coins) 234 { 235 GNUNET_break (0); 236 TALER_TESTING_interpreter_fail (is); 237 return; 238 } 239 for (size_t i = 0; i < rrs->num_indices; i++) 240 if (rrs->indices[i] >= num_coins) 241 { 242 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 243 "Coin index %u out of range for command %s (%u coins)\n", 244 rrs->indices[i], 245 rrs->reveal_reference, 246 (unsigned int) num_coins); 247 GNUNET_break (0); 248 TALER_TESTING_interpreter_fail (is); 249 return; 250 } 251 { 252 struct TALER_EXCHANGE_RecoupCoin coins[num_coins]; 253 size_t k = 0; 254 255 memset (coins, 256 0, 257 sizeof (coins)); 258 for (size_t i = 0; i < num_coins; i++) 259 { 260 struct TALER_EXCHANGE_RecoupCoin *c = &coins[i]; 261 const struct TALER_AgeCommitmentHashP *hac = NULL; 262 bool recoup = false; 263 264 if ( (GNUNET_OK != 265 TALER_TESTING_get_trait_denom_pub (reveal_cmd, 266 (unsigned int) i, 267 &c->pk)) || 268 (GNUNET_OK != 269 TALER_TESTING_get_trait_planchet_secrets (reveal_cmd, 270 (unsigned int) i, 271 &c->ps)) || 272 (GNUNET_OK != 273 TALER_TESTING_get_trait_exchange_blinding_values ( 274 melt_cmd, 275 (unsigned int) i, 276 &c->exchange_vals)) ) 277 { 278 GNUNET_break (0); 279 TALER_TESTING_interpreter_fail (is); 280 return; 281 } 282 /* only denominations with age restriction have a commitment */ 283 if ( (0 != c->pk->key.age_mask.bits) && 284 (GNUNET_OK == 285 TALER_TESTING_get_trait_h_age_commitment (reveal_cmd, 286 (unsigned int) i, 287 &hac)) ) 288 c->h_age_commitment = hac; /* may still be NULL */ 289 for (size_t j = 0; j < rrs->num_indices; j++) 290 if (rrs->indices[j] == i) 291 recoup = true; 292 if (! recoup) 293 continue; 294 if (GNUNET_OK != 295 TALER_TESTING_get_trait_denom_sig (reveal_cmd, 296 (unsigned int) i, 297 &c->denom_sig)) 298 { 299 GNUNET_break (0); 300 TALER_TESTING_interpreter_fail (is); 301 return; 302 } 303 /* what the histories should show for this coin and the old coin */ 304 { 305 struct TALER_EXCHANGE_CoinHistoryEntry *che_new = &rrs->ches_new[k]; 306 struct TALER_EXCHANGE_CoinHistoryEntry *che_old = &rrs->ches_old[k]; 307 struct TALER_CoinSpendPrivateKeyP coin_priv; 308 const struct TALER_ExchangeBlindingValues *vals = c->exchange_vals; 309 310 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 311 "Trying to recoup-refresh coin %u of denomination '%s'\n", 312 (unsigned int) i, 313 TALER_B2S (&c->pk->h_key)); 314 TALER_planchet_setup_coin_priv (c->ps, 315 vals, 316 &coin_priv); 317 GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv.eddsa_priv, 318 &rrs->coin_pubs[k].eddsa_pub); 319 che_new->type = TALER_EXCHANGE_CTT_RECOUP_REFRESH; 320 che_new->amount = rrs->amount; 321 che_new->details.recoup_refresh.old_coin_pub = rrs->old_coin_pub; 322 che_new->details.recoup_refresh.rc = *rc; 323 che_new->details.recoup_refresh.coin_index = (uint32_t) i; 324 TALER_planchet_blinding_secret_create ( 325 c->ps, 326 vals, 327 &che_new->details.recoup_refresh.coin_bks); 328 /* exercise the documented contract: the request needs no 329 exchange values for RSA denominations */ 330 if (GNUNET_CRYPTO_BSA_RSA == c->pk->key.bsign_pub_key->cipher) 331 c->exchange_vals = NULL; 332 TALER_wallet_recoup_refresh_sign ( 333 &c->pk->h_key, 334 &che_new->details.recoup_refresh.coin_bks, 335 &coin_priv, 336 &che_new->details.recoup_refresh.coin_sig); 337 che_old->type = TALER_EXCHANGE_CTT_OLD_COIN_RECOUP; 338 che_old->amount = rrs->amount; 339 che_old->details.old_coin_recoup.new_coin_pub = rrs->coin_pubs[k]; 340 che_old->details.old_coin_recoup.rc = *rc; 341 k++; 342 } 343 } 344 rrs->ph = TALER_EXCHANGE_post_recoup_refresh_create ( 345 TALER_TESTING_interpreter_get_context (is), 346 TALER_TESTING_get_exchange_url (is), 347 TALER_TESTING_get_keys (is), 348 &rrs->old_coin_pub, 349 rc, 350 blinding_seed, 351 num_coins, 352 coins); 353 } 354 if (NULL == rrs->ph) 355 { 356 GNUNET_break (0); 357 TALER_TESTING_interpreter_fail (is); 358 return; 359 } 360 GNUNET_assert (TALER_EC_NONE == 361 TALER_EXCHANGE_post_recoup_refresh_start (rrs->ph, 362 &recoup_refresh_cb, 363 rrs)); 364 } 365 366 367 /** 368 * Cleanup the state. 369 * 370 * @param cls closure, must be a `struct RecoupRefreshState`. 371 * @param cmd the command which is being cleaned up. 372 */ 373 static void 374 recoup_refresh_cleanup (void *cls, 375 const struct TALER_TESTING_Command *cmd) 376 { 377 struct RecoupRefreshState *rrs = cls; 378 379 (void) cmd; 380 if (NULL != rrs->ph) 381 { 382 TALER_EXCHANGE_post_recoup_refresh_cancel (rrs->ph); 383 rrs->ph = NULL; 384 } 385 GNUNET_free (rrs->reveal_reference); 386 GNUNET_free (rrs->indices); 387 GNUNET_free (rrs->ches_new); 388 GNUNET_free (rrs->ches_old); 389 GNUNET_free (rrs->coin_pubs); 390 GNUNET_free (rrs); 391 } 392 393 394 /** 395 * Offer internal data to other commands: for index k below the 396 * number of recouped coins, the recouped coin and its history 397 * entry; for the following indices, the old coin and its history 398 * entry for the respective recoup. 399 * 400 * @param cls closure 401 * @param[out] ret result (could be anything) 402 * @param trait name of the trait 403 * @param index index number of the object to extract. 404 * @return #GNUNET_OK on success 405 */ 406 static enum GNUNET_GenericReturnValue 407 recoup_refresh_traits (void *cls, 408 const void **ret, 409 const char *trait, 410 unsigned int index) 411 { 412 struct RecoupRefreshState *rrs = cls; 413 size_t k; 414 bool old; 415 416 if (index >= 2 * rrs->num_indices) 417 return GNUNET_NO; 418 old = (index >= rrs->num_indices); 419 k = old ? index - rrs->num_indices : index; 420 if (GNUNET_OK != 421 TALER_amount_is_valid (&rrs->ches_new[k].amount)) 422 return GNUNET_SYSERR; /* request did not succeed: no traits */ 423 { 424 struct TALER_TESTING_Trait traits[] = { 425 TALER_TESTING_make_trait_coin_history (index, 426 old 427 ? &rrs->ches_old[k] 428 : &rrs->ches_new[k]), 429 TALER_TESTING_make_trait_coin_pub (index, 430 old 431 ? &rrs->old_coin_pub 432 : &rrs->coin_pubs[k]), 433 TALER_TESTING_trait_end () 434 }; 435 436 return TALER_TESTING_get_trait (traits, 437 ret, 438 trait, 439 index); 440 } 441 } 442 443 444 /** 445 * Create the command state shared by both constructors. 446 * 447 * @param label the command label 448 * @param expected_response_code expected HTTP status code 449 * @param reveal_reference label of the reveal-melt command 450 * @param indices coins to recoup, becomes owned by the state 451 * @param num_indices length of @a indices 452 * @param melt_reference label of the melt command 453 * @param amount expected amount per coin, NULL for none 454 * @return the command 455 */ 456 static struct TALER_TESTING_Command 457 make_recoup_refresh_cmd (const char *label, 458 unsigned int expected_response_code, 459 const char *reveal_reference, 460 unsigned int *indices, 461 size_t num_indices, 462 const char *melt_reference, 463 const char *amount) 464 { 465 struct RecoupRefreshState *rrs; 466 467 rrs = GNUNET_new (struct RecoupRefreshState); 468 rrs->expected_response_code = expected_response_code; 469 rrs->reveal_reference = GNUNET_strdup (reveal_reference); 470 rrs->melt_reference = melt_reference; 471 rrs->indices = indices; 472 rrs->num_indices = num_indices; 473 rrs->ches_new = GNUNET_new_array (num_indices, 474 struct TALER_EXCHANGE_CoinHistoryEntry); 475 rrs->ches_old = GNUNET_new_array (num_indices, 476 struct TALER_EXCHANGE_CoinHistoryEntry); 477 rrs->coin_pubs = GNUNET_new_array (num_indices, 478 struct TALER_CoinSpendPublicKeyP); 479 if ( (NULL != amount) && 480 (GNUNET_OK != 481 TALER_string_to_amount (amount, 482 &rrs->amount)) ) 483 { 484 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 485 "Failed to parse amount `%s' at %s\n", 486 amount, 487 label); 488 GNUNET_assert (0); 489 } 490 { 491 struct TALER_TESTING_Command cmd = { 492 .cls = rrs, 493 .label = label, 494 .run = &recoup_refresh_run, 495 .cleanup = &recoup_refresh_cleanup, 496 .traits = &recoup_refresh_traits 497 }; 498 499 return cmd; 500 } 501 } 502 503 504 struct TALER_TESTING_Command 505 TALER_TESTING_cmd_recoup_refresh (const char *label, 506 unsigned int expected_response_code, 507 const char *coin_reference, 508 const char *melt_reference, 509 const char *amount) 510 { 511 char *cref; 512 unsigned int idx; 513 unsigned int *indices; 514 struct TALER_TESTING_Command cmd; 515 516 GNUNET_assert (GNUNET_OK == 517 TALER_TESTING_parse_coin_reference (coin_reference, 518 &cref, 519 &idx)); 520 indices = GNUNET_new_array (1, 521 unsigned int); 522 indices[0] = idx; 523 cmd = make_recoup_refresh_cmd (label, 524 expected_response_code, 525 cref, 526 indices, 527 1, 528 melt_reference, 529 amount); 530 GNUNET_free (cref); 531 return cmd; 532 } 533 534 535 struct TALER_TESTING_Command 536 TALER_TESTING_cmd_recoup_refresh_batch (const char *label, 537 unsigned int expected_response_code, 538 const char *reveal_reference, 539 const char *indices, 540 const char *melt_reference, 541 const char *amount) 542 { 543 unsigned int *idx; 544 size_t num; 545 546 idx = TALER_TESTING_parse_index_list (indices, 547 &num); 548 GNUNET_assert (NULL != idx); 549 return make_recoup_refresh_cmd (label, 550 expected_response_code, 551 reveal_reference, 552 idx, 553 num, 554 melt_reference, 555 amount); 556 } 557 558 559 /* end of testing_api_cmd_recoup_refresh.c */