testing_api_cmd_recoup.c (17247B)
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.c 18 * @brief Implement the /recoup-withdraw 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-withdraw" CMD. 31 */ 32 struct RecoupState 33 { 34 /** 35 * Expected HTTP status code. 36 */ 37 unsigned int expected_response_code; 38 39 /** 40 * Label of the withdraw command the coins came from. 41 */ 42 char *withdraw_reference; 43 44 /** 45 * Indices (in the withdraw command) of the coins to recoup. 46 */ 47 unsigned int *indices; 48 49 /** 50 * Number of entries in @e indices, @e ches, @e rhes and @e coin_pubs. 51 */ 52 size_t num_indices; 53 54 /** 55 * Amount we expect to be recouped per coin, invalid if 56 * not specified. 57 */ 58 struct TALER_Amount amount; 59 60 /** 61 * Interpreter state. 62 */ 63 struct TALER_TESTING_Interpreter *is; 64 65 /** 66 * Handle to the request. 67 */ 68 struct TALER_EXCHANGE_PostRecoupWithdrawHandle *ph; 69 70 /** 71 * Reserve that is credited. 72 */ 73 struct TALER_ReservePublicKeyP reserve_pub; 74 75 /** 76 * Coin history entries we expect, one per recouped coin. 77 */ 78 struct TALER_EXCHANGE_CoinHistoryEntry *ches; 79 80 /** 81 * Reserve history entries we expect, one per recouped coin. 82 */ 83 struct TALER_EXCHANGE_ReserveHistoryEntry *rhes; 84 85 /** 86 * Public keys of the recouped coins. 87 */ 88 struct TALER_CoinSpendPublicKeyP *coin_pubs; 89 }; 90 91 92 /** 93 * Check the response. 94 * 95 * @param cls closure, our `struct RecoupState` 96 * @param rr response details 97 */ 98 static void 99 recoup_cb (void *cls, 100 const struct TALER_EXCHANGE_PostRecoupWithdrawResponse *rr) 101 { 102 struct RecoupState *ps = cls; 103 const struct TALER_EXCHANGE_HttpResponse *hr = &rr->hr; 104 struct TALER_TESTING_Interpreter *is = ps->is; 105 106 ps->ph = NULL; 107 if (ps->expected_response_code != hr->http_status) 108 { 109 TALER_TESTING_unexpected_status (is, 110 hr->http_status, 111 ps->expected_response_code); 112 return; 113 } 114 switch (hr->http_status) 115 { 116 case MHD_HTTP_OK: 117 if (0 != GNUNET_memcmp (&rr->details.ok.reserve_pub, 118 &ps->reserve_pub)) 119 { 120 GNUNET_break (0); 121 TALER_TESTING_interpreter_fail (is); 122 return; 123 } 124 if (rr->details.ok.num_recoups != ps->num_indices) 125 { 126 GNUNET_break (0); 127 TALER_TESTING_interpreter_fail (is); 128 return; 129 } 130 for (size_t i = 0; i < ps->num_indices; i++) 131 { 132 const struct TALER_RecoupedCoin *rc = &rr->details.ok.recoups[i]; 133 134 if (0 != GNUNET_memcmp (&rc->coin_pub, 135 &ps->coin_pubs[i])) 136 { 137 GNUNET_break (0); 138 TALER_TESTING_interpreter_fail (is); 139 return; 140 } 141 if ( (GNUNET_OK == 142 TALER_amount_is_valid (&ps->amount)) && 143 (0 != TALER_amount_cmp (&rc->amount, 144 &ps->amount)) ) 145 { 146 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 147 "Recouped %s for coin %u, expected %s\n", 148 TALER_amount2s (&rc->amount), 149 ps->indices[i], 150 TALER_amount2s (&ps->amount)); 151 GNUNET_break (0); 152 TALER_TESTING_interpreter_fail (is); 153 return; 154 } 155 ps->ches[i].amount = rc->amount; 156 ps->rhes[i].amount = rc->amount; 157 ps->rhes[i].type = TALER_EXCHANGE_RTT_RECOUP; 158 } 159 break; 160 default: 161 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 162 "Recoup-withdraw failed as expected with HTTP status %u/%d\n", 163 hr->http_status, 164 (int) hr->ec); 165 break; 166 } 167 TALER_TESTING_interpreter_next (is); 168 } 169 170 171 /** 172 * Run the command. 173 * 174 * @param cls closure. 175 * @param cmd the command to execute. 176 * @param is the interpreter state. 177 */ 178 static void 179 recoup_run (void *cls, 180 const struct TALER_TESTING_Command *cmd, 181 struct TALER_TESTING_Interpreter *is) 182 { 183 struct RecoupState *ps = cls; 184 const struct TALER_TESTING_Command *coin_cmd; 185 const struct TALER_ReservePublicKeyP *reserve_pub; 186 const struct TALER_WithdrawMasterSeedP *seed; 187 const struct TALER_HashBlindedPlanchetsP *h_planchets; 188 struct TALER_BlindingMasterSeedP blinding_seed; 189 bool have_cs = false; 190 size_t num_coins = 0; 191 192 (void) cmd; 193 ps->is = is; 194 coin_cmd = TALER_TESTING_interpreter_lookup_command (is, 195 ps->withdraw_reference); 196 if (NULL == coin_cmd) 197 { 198 GNUNET_break (0); 199 TALER_TESTING_interpreter_fail (is); 200 return; 201 } 202 if ( (GNUNET_OK != 203 TALER_TESTING_get_trait_reserve_pub (coin_cmd, 204 &reserve_pub)) || 205 (GNUNET_OK != 206 TALER_TESTING_get_trait_withdraw_seed (coin_cmd, 207 &seed)) || 208 (GNUNET_OK != 209 TALER_TESTING_get_trait_withdraw_commitment (coin_cmd, 210 &h_planchets)) ) 211 { 212 GNUNET_break (0); 213 TALER_TESTING_interpreter_fail (is); 214 return; 215 } 216 ps->reserve_pub = *reserve_pub; 217 /* count the coins of the withdraw operation */ 218 while (num_coins < TALER_MAX_COINS) 219 { 220 const struct TALER_EXCHANGE_DenomPublicKey *pk; 221 222 if (GNUNET_OK != 223 TALER_TESTING_get_trait_denom_pub (coin_cmd, 224 (unsigned int) num_coins, 225 &pk)) 226 break; 227 num_coins++; 228 } 229 if (0 == num_coins) 230 { 231 GNUNET_break (0); 232 TALER_TESTING_interpreter_fail (is); 233 return; 234 } 235 for (size_t i = 0; i < ps->num_indices; i++) 236 if (ps->indices[i] >= num_coins) 237 { 238 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 239 "Coin index %u out of range for command %s (%u coins)\n", 240 ps->indices[i], 241 ps->withdraw_reference, 242 (unsigned int) num_coins); 243 GNUNET_break (0); 244 TALER_TESTING_interpreter_fail (is); 245 return; 246 } 247 /* the CS blinding seed is derived from the withdraw seed */ 248 TALER_cs_withdraw_seed_to_blinding_seed (seed, 249 &blinding_seed); 250 { 251 struct TALER_PlanchetMasterSecretP secrets[num_coins]; 252 struct TALER_EXCHANGE_RecoupCoin coins[num_coins]; 253 size_t k = 0; 254 255 TALER_withdraw_expand_secrets (num_coins, 256 seed, 257 secrets); 258 memset (coins, 259 0, 260 sizeof (coins)); 261 for (size_t i = 0; i < num_coins; i++) 262 { 263 struct TALER_EXCHANGE_RecoupCoin *c = &coins[i]; 264 const struct TALER_AgeCommitmentHashP *hac = NULL; 265 bool recoup = false; 266 267 if ( (GNUNET_OK != 268 TALER_TESTING_get_trait_denom_pub (coin_cmd, 269 (unsigned int) i, 270 &c->pk)) || 271 (GNUNET_OK != 272 TALER_TESTING_get_trait_exchange_blinding_values ( 273 coin_cmd, 274 (unsigned int) i, 275 &c->exchange_vals)) ) 276 { 277 GNUNET_break (0); 278 TALER_TESTING_interpreter_fail (is); 279 return; 280 } 281 /* only denominations with age restriction have a commitment */ 282 if ( (0 != c->pk->key.age_mask.bits) && 283 (GNUNET_OK == 284 TALER_TESTING_get_trait_h_age_commitment (coin_cmd, 285 (unsigned int) i, 286 &hac)) ) 287 c->h_age_commitment = hac; /* may still be NULL */ 288 /* commands with per-coin secrets (batch and age withdraw) expose 289 them; the single withdraw derives them from the seed */ 290 if (GNUNET_OK != 291 TALER_TESTING_get_trait_planchet_secrets (coin_cmd, 292 (unsigned int) i, 293 &c->ps)) 294 c->ps = &secrets[i]; 295 if (GNUNET_CRYPTO_BSA_CS == c->pk->key.bsign_pub_key->cipher) 296 have_cs = true; 297 for (size_t j = 0; j < ps->num_indices; j++) 298 if (ps->indices[j] == i) 299 recoup = true; 300 if (! recoup) 301 continue; 302 if (GNUNET_OK != 303 TALER_TESTING_get_trait_denom_sig (coin_cmd, 304 (unsigned int) i, 305 &c->denom_sig)) 306 { 307 GNUNET_break (0); 308 TALER_TESTING_interpreter_fail (is); 309 return; 310 } 311 /* what the histories should show for this coin */ 312 { 313 struct TALER_EXCHANGE_CoinHistoryEntry *che = &ps->ches[k]; 314 struct TALER_EXCHANGE_ReserveHistoryEntry *rhe = &ps->rhes[k]; 315 struct TALER_CoinSpendPrivateKeyP coin_priv; 316 const struct TALER_ExchangeBlindingValues *vals = c->exchange_vals; 317 318 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 319 "Trying to recoup coin %u of denomination '%s'\n", 320 (unsigned int) i, 321 TALER_B2S (&c->pk->h_key)); 322 TALER_planchet_setup_coin_priv (c->ps, 323 vals, 324 &coin_priv); 325 GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv.eddsa_priv, 326 &ps->coin_pubs[k].eddsa_pub); 327 che->type = TALER_EXCHANGE_CTT_RECOUP; 328 che->amount = ps->amount; 329 che->details.recoup.reserve_pub = ps->reserve_pub; 330 che->details.recoup.planchets_h = *h_planchets; 331 che->details.recoup.coin_index = (uint32_t) i; 332 TALER_planchet_blinding_secret_create (c->ps, 333 vals, 334 &che->details.recoup.coin_bks); 335 /* exercise the documented contract: the request needs no 336 exchange values for RSA denominations */ 337 if (GNUNET_CRYPTO_BSA_RSA == c->pk->key.bsign_pub_key->cipher) 338 c->exchange_vals = NULL; 339 TALER_wallet_recoup_sign (&c->pk->h_key, 340 &che->details.recoup.coin_bks, 341 &coin_priv, 342 &che->details.recoup.coin_sig); 343 rhe->amount = ps->amount; 344 rhe->details.recoup_details.coin_pub = ps->coin_pubs[k]; 345 rhe->details.recoup_details.planchets_h = *h_planchets; 346 k++; 347 } 348 } 349 ps->ph = TALER_EXCHANGE_post_recoup_withdraw_create ( 350 TALER_TESTING_interpreter_get_context (is), 351 TALER_TESTING_get_exchange_url (is), 352 TALER_TESTING_get_keys (is), 353 &ps->reserve_pub, 354 h_planchets, 355 have_cs ? &blinding_seed : NULL, 356 num_coins, 357 coins); 358 } 359 if (NULL == ps->ph) 360 { 361 GNUNET_break (0); 362 TALER_TESTING_interpreter_fail (is); 363 return; 364 } 365 GNUNET_assert (TALER_EC_NONE == 366 TALER_EXCHANGE_post_recoup_withdraw_start (ps->ph, 367 &recoup_cb, 368 ps)); 369 } 370 371 372 /** 373 * Cleanup the state. 374 * 375 * @param cls closure, must be a `struct RecoupState`. 376 * @param cmd the command which is being cleaned up. 377 */ 378 static void 379 recoup_cleanup (void *cls, 380 const struct TALER_TESTING_Command *cmd) 381 { 382 struct RecoupState *ps = cls; 383 384 (void) cmd; 385 if (NULL != ps->ph) 386 { 387 TALER_EXCHANGE_post_recoup_withdraw_cancel (ps->ph); 388 ps->ph = NULL; 389 } 390 GNUNET_free (ps->withdraw_reference); 391 GNUNET_free (ps->indices); 392 GNUNET_free (ps->ches); 393 GNUNET_free (ps->rhes); 394 GNUNET_free (ps->coin_pubs); 395 GNUNET_free (ps); 396 } 397 398 399 /** 400 * Offer internal data to other commands: one coin history entry 401 * and one reserve history entry per recouped coin. 402 * 403 * @param cls closure 404 * @param[out] ret result (could be anything) 405 * @param trait name of the trait 406 * @param index index number of the object to extract. 407 * @return #GNUNET_OK on success 408 */ 409 static enum GNUNET_GenericReturnValue 410 recoup_traits (void *cls, 411 const void **ret, 412 const char *trait, 413 unsigned int index) 414 { 415 struct RecoupState *ps = cls; 416 417 if (index >= ps->num_indices) 418 return GNUNET_NO; 419 if (TALER_EXCHANGE_RTT_RECOUP != ps->rhes[index].type) 420 return GNUNET_SYSERR; /* request did not succeed: no traits */ 421 { 422 struct TALER_TESTING_Trait traits[] = { 423 TALER_TESTING_make_trait_reserve_pub (&ps->reserve_pub), 424 TALER_TESTING_make_trait_reserve_history (index, 425 &ps->rhes[index]), 426 TALER_TESTING_make_trait_coin_history (index, 427 &ps->ches[index]), 428 TALER_TESTING_make_trait_coin_pub (index, 429 &ps->coin_pubs[index]), 430 TALER_TESTING_trait_end () 431 }; 432 433 return TALER_TESTING_get_trait (traits, 434 ret, 435 trait, 436 index); 437 } 438 } 439 440 441 /** 442 * Create the command state shared by both constructors. 443 * 444 * @param label the command label 445 * @param expected_response_code expected HTTP status code 446 * @param withdraw_reference label of the withdraw command 447 * @param indices coins to recoup, becomes owned by the state 448 * @param num_indices length of @a indices 449 * @param amount expected amount per coin, NULL for none 450 * @return the command 451 */ 452 static struct TALER_TESTING_Command 453 make_recoup_cmd (const char *label, 454 unsigned int expected_response_code, 455 const char *withdraw_reference, 456 unsigned int *indices, 457 size_t num_indices, 458 const char *amount) 459 { 460 struct RecoupState *ps; 461 462 ps = GNUNET_new (struct RecoupState); 463 ps->expected_response_code = expected_response_code; 464 ps->withdraw_reference = GNUNET_strdup (withdraw_reference); 465 ps->indices = indices; 466 ps->num_indices = num_indices; 467 ps->ches = GNUNET_new_array (num_indices, 468 struct TALER_EXCHANGE_CoinHistoryEntry); 469 ps->rhes = GNUNET_new_array (num_indices, 470 struct TALER_EXCHANGE_ReserveHistoryEntry); 471 ps->coin_pubs = GNUNET_new_array (num_indices, 472 struct TALER_CoinSpendPublicKeyP); 473 if ( (NULL != amount) && 474 (GNUNET_OK != 475 TALER_string_to_amount (amount, 476 &ps->amount)) ) 477 { 478 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 479 "Failed to parse amount `%s' at %s\n", 480 amount, 481 label); 482 GNUNET_assert (0); 483 } 484 { 485 struct TALER_TESTING_Command cmd = { 486 .cls = ps, 487 .label = label, 488 .run = &recoup_run, 489 .cleanup = &recoup_cleanup, 490 .traits = &recoup_traits 491 }; 492 493 return cmd; 494 } 495 } 496 497 498 struct TALER_TESTING_Command 499 TALER_TESTING_cmd_recoup (const char *label, 500 unsigned int expected_response_code, 501 const char *coin_reference, 502 const char *amount) 503 { 504 char *cref; 505 unsigned int idx; 506 unsigned int *indices; 507 struct TALER_TESTING_Command cmd; 508 509 GNUNET_assert (GNUNET_OK == 510 TALER_TESTING_parse_coin_reference (coin_reference, 511 &cref, 512 &idx)); 513 indices = GNUNET_new_array (1, 514 unsigned int); 515 indices[0] = idx; 516 cmd = make_recoup_cmd (label, 517 expected_response_code, 518 cref, 519 indices, 520 1, 521 amount); 522 GNUNET_free (cref); 523 return cmd; 524 } 525 526 527 struct TALER_TESTING_Command 528 TALER_TESTING_cmd_recoup_batch (const char *label, 529 unsigned int expected_response_code, 530 const char *withdraw_reference, 531 const char *indices, 532 const char *amount) 533 { 534 unsigned int *idx; 535 size_t num; 536 537 idx = TALER_TESTING_parse_index_list (indices, 538 &num); 539 GNUNET_assert (NULL != idx); 540 return make_recoup_cmd (label, 541 expected_response_code, 542 withdraw_reference, 543 idx, 544 num, 545 amount); 546 } 547 548 549 /* end of testing_api_cmd_recoup.c */