testing_api_cmd_purse_deposit.c (14735B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2022, 2023 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it 6 under the terms of the GNU General Public License as published by 7 the Free Software Foundation; either version 3, or (at your 8 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 GNU 13 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_purse_deposit.c 21 * @brief command for testing /purses/$PID/create 22 * @author Christian Grothoff 23 */ 24 #include "taler/platform.h" 25 #include "taler/taler_json_lib.h" 26 #include <gnunet/gnunet_curl_lib.h> 27 #include "taler/taler_testing_lib.h" 28 #include "taler/taler_signatures.h" 29 #include "taler/backoff.h" 30 31 /** 32 * Information we keep per deposited coin. 33 */ 34 struct Coin 35 { 36 /** 37 * Reference to the respective command. 38 */ 39 char *command_ref; 40 41 /** 42 * Entry in the coin's history generated by this operation. 43 */ 44 struct TALER_EXCHANGE_CoinHistoryEntry che; 45 46 /** 47 * Public key of the deposited coin. 48 */ 49 struct TALER_CoinSpendPublicKeyP coin_pub; 50 51 /** 52 * index of the specific coin in the traits of @e command_ref. 53 */ 54 unsigned int coin_index; 55 56 /** 57 * Amount to deposit (with fee). 58 */ 59 struct TALER_Amount deposit_with_fee; 60 61 }; 62 63 64 /** 65 * State for a "purse deposit" CMD. 66 */ 67 struct PurseDepositState 68 { 69 70 /** 71 * Total purse target amount without fees. 72 */ 73 struct TALER_Amount target_amount; 74 75 /** 76 * Reference to any command that is able to provide a coin. 77 */ 78 struct Coin *coin_references; 79 80 /** 81 * The purse's public key. 82 */ 83 struct TALER_PurseContractPublicKeyP purse_pub; 84 85 /** 86 * The reserve we are being deposited into. 87 * Set as a trait once we know the reserve. 88 */ 89 struct TALER_ReservePublicKeyP reserve_pub; 90 91 /** 92 * PurseDeposit handle while operation is running. 93 */ 94 struct TALER_EXCHANGE_PurseDepositHandle *dh; 95 96 /** 97 * Interpreter state. 98 */ 99 struct TALER_TESTING_Interpreter *is; 100 101 /** 102 * Reference to the command that established the purse. 103 */ 104 const char *purse_ref; 105 106 /** 107 * Reserve history entry that corresponds to this operation. 108 * Will be of type #TALER_EXCHANGE_RTT_MERGE. 109 * Only valid if @e purse_complete is true. 110 */ 111 struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history; 112 /** 113 * Expected HTTP response code. 114 */ 115 unsigned int expected_response_code; 116 117 /** 118 * Length of the @e coin_references array. 119 */ 120 unsigned int num_coin_references; 121 122 /** 123 * Minimum age to apply to all deposits. 124 */ 125 uint8_t min_age; 126 127 /** 128 * Set to true if this deposit filled the purse. 129 */ 130 bool purse_complete; 131 }; 132 133 134 /** 135 * Callback to analyze the /purses/$PID/deposit response, just used to check if 136 * the response code is acceptable. 137 * 138 * @param cls closure. 139 * @param dr deposit response details 140 */ 141 static void 142 deposit_cb (void *cls, 143 const struct TALER_EXCHANGE_PurseDepositResponse *dr) 144 { 145 struct PurseDepositState *ds = cls; 146 147 ds->dh = NULL; 148 if (ds->expected_response_code != dr->hr.http_status) 149 { 150 TALER_TESTING_unexpected_status (ds->is, 151 dr->hr.http_status, 152 ds->expected_response_code); 153 return; 154 } 155 if (MHD_HTTP_OK == dr->hr.http_status) 156 { 157 if (-1 != 158 TALER_amount_cmp (&dr->details.ok.total_deposited, 159 &dr->details.ok.purse_value_after_fees)) 160 { 161 const struct TALER_TESTING_Command *purse_cmd; 162 const struct TALER_ReserveSignatureP *reserve_sig; 163 const struct TALER_ReservePublicKeyP *reserve_pub; 164 const struct GNUNET_TIME_Timestamp *merge_timestamp; 165 const struct TALER_PurseMergePublicKeyP *merge_pub; 166 167 purse_cmd = TALER_TESTING_interpreter_lookup_command (ds->is, 168 ds->purse_ref); 169 GNUNET_assert (NULL != purse_cmd); 170 if (GNUNET_OK != 171 TALER_TESTING_get_trait_reserve_sig (purse_cmd, 172 &reserve_sig)) 173 { 174 GNUNET_break (0); 175 TALER_TESTING_interpreter_fail (ds->is); 176 return; 177 } 178 if (GNUNET_OK != 179 TALER_TESTING_get_trait_reserve_pub (purse_cmd, 180 &reserve_pub)) 181 { 182 GNUNET_break (0); 183 TALER_TESTING_interpreter_fail (ds->is); 184 return; 185 } 186 if (GNUNET_OK != 187 TALER_TESTING_get_trait_merge_pub (purse_cmd, 188 &merge_pub)) 189 { 190 GNUNET_break (0); 191 TALER_TESTING_interpreter_fail (ds->is); 192 return; 193 } 194 ds->reserve_pub = *reserve_pub; 195 if (GNUNET_OK != 196 TALER_TESTING_get_trait_timestamp (purse_cmd, 197 0, 198 &merge_timestamp)) 199 { 200 GNUNET_break (0); 201 TALER_TESTING_interpreter_fail (ds->is); 202 return; 203 } 204 205 /* Deposits complete, create trait! */ 206 ds->reserve_history.type = TALER_EXCHANGE_RTT_MERGE; 207 { 208 struct TALER_EXCHANGE_Keys *keys; 209 const struct TALER_EXCHANGE_GlobalFee *gf; 210 211 keys = TALER_TESTING_get_keys (ds->is); 212 GNUNET_assert (NULL != keys); 213 gf = TALER_EXCHANGE_get_global_fee (keys, 214 *merge_timestamp); 215 GNUNET_assert (NULL != gf); 216 217 /* Note: change when flags below changes! */ 218 ds->reserve_history.amount 219 = dr->details.ok.purse_value_after_fees; 220 if (true) 221 { 222 ds->reserve_history.details.merge_details.purse_fee = gf->fees.purse; 223 } 224 else 225 { 226 TALER_amount_set_zero ( 227 ds->reserve_history.amount.currency, 228 &ds->reserve_history.details.merge_details.purse_fee); 229 } 230 } 231 ds->reserve_history.details.merge_details.h_contract_terms 232 = dr->details.ok.h_contract_terms; 233 ds->reserve_history.details.merge_details.merge_pub 234 = *merge_pub; 235 ds->reserve_history.details.merge_details.purse_pub 236 = ds->purse_pub; 237 ds->reserve_history.details.merge_details.reserve_sig 238 = *reserve_sig; 239 ds->reserve_history.details.merge_details.merge_timestamp 240 = *merge_timestamp; 241 ds->reserve_history.details.merge_details.purse_expiration 242 = dr->details.ok.purse_expiration; 243 ds->reserve_history.details.merge_details.min_age 244 = ds->min_age; 245 ds->reserve_history.details.merge_details.flags 246 = TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE; 247 ds->purse_complete = true; 248 } 249 } 250 TALER_TESTING_interpreter_next (ds->is); 251 } 252 253 254 /** 255 * Run the command. 256 * 257 * @param cls closure. 258 * @param cmd the command to execute. 259 * @param is the interpreter state. 260 */ 261 static void 262 deposit_run (void *cls, 263 const struct TALER_TESTING_Command *cmd, 264 struct TALER_TESTING_Interpreter *is) 265 { 266 struct PurseDepositState *ds = cls; 267 struct TALER_EXCHANGE_PurseDeposit deposits[ds->num_coin_references]; 268 const struct TALER_PurseContractPublicKeyP *purse_pub; 269 const struct TALER_TESTING_Command *purse_cmd; 270 271 (void) cmd; 272 ds->is = is; 273 purse_cmd = TALER_TESTING_interpreter_lookup_command (is, 274 ds->purse_ref); 275 GNUNET_assert (NULL != purse_cmd); 276 if (GNUNET_OK != 277 TALER_TESTING_get_trait_purse_pub (purse_cmd, 278 &purse_pub)) 279 { 280 GNUNET_break (0); 281 TALER_TESTING_interpreter_fail (is); 282 return; 283 } 284 ds->purse_pub = *purse_pub; 285 for (unsigned int i = 0; i<ds->num_coin_references; i++) 286 { 287 struct Coin *cr = &ds->coin_references[i]; 288 struct TALER_EXCHANGE_PurseDeposit *pd = &deposits[i]; 289 const struct TALER_TESTING_Command *coin_cmd; 290 const struct TALER_CoinSpendPrivateKeyP *coin_priv; 291 const struct TALER_AgeCommitmentProof *age_commitment_proof = NULL; 292 const struct TALER_EXCHANGE_DenomPublicKey *denom_pub; 293 const struct TALER_DenominationSignature *denom_pub_sig; 294 295 coin_cmd = TALER_TESTING_interpreter_lookup_command (is, 296 cr->command_ref); 297 GNUNET_assert (NULL != coin_cmd); 298 if ( (GNUNET_OK != 299 TALER_TESTING_get_trait_coin_priv (coin_cmd, 300 cr->coin_index, 301 &coin_priv)) || 302 (GNUNET_OK != 303 TALER_TESTING_get_trait_age_commitment_proof (coin_cmd, 304 cr->coin_index, 305 &age_commitment_proof)) 306 || 307 (GNUNET_OK != 308 TALER_TESTING_get_trait_denom_pub (coin_cmd, 309 cr->coin_index, 310 &denom_pub)) || 311 (GNUNET_OK != 312 TALER_TESTING_get_trait_denom_sig (coin_cmd, 313 cr->coin_index, 314 &denom_pub_sig)) ) 315 { 316 GNUNET_break (0); 317 TALER_TESTING_interpreter_fail (is); 318 return; 319 } 320 GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, 321 &cr->coin_pub.eddsa_pub); 322 cr->che.type = TALER_EXCHANGE_CTT_PURSE_DEPOSIT; 323 cr->che.amount = cr->deposit_with_fee; 324 cr->che.details.purse_deposit.purse_pub = *purse_pub; 325 cr->che.details.purse_deposit.exchange_base_url 326 = TALER_TESTING_get_exchange_url (is); 327 TALER_age_commitment_hash ( 328 &age_commitment_proof->commitment, 329 &cr->che.details.purse_deposit.phac); 330 pd->age_commitment_proof = age_commitment_proof; 331 pd->denom_sig = *denom_pub_sig; 332 pd->coin_priv = *coin_priv; 333 pd->amount = cr->deposit_with_fee; 334 pd->h_denom_pub = denom_pub->h_key; 335 } 336 337 ds->dh = TALER_EXCHANGE_purse_deposit ( 338 TALER_TESTING_interpreter_get_context (is), 339 TALER_TESTING_get_exchange_url (is), 340 TALER_TESTING_get_keys (is), 341 NULL, /* FIXME #7271: WADs support: purse exchange URL */ 342 &ds->purse_pub, 343 ds->min_age, 344 ds->num_coin_references, 345 deposits, 346 &deposit_cb, 347 ds); 348 if (NULL == ds->dh) 349 { 350 GNUNET_break (0); 351 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 352 "Could not deposit into purse\n"); 353 TALER_TESTING_interpreter_fail (is); 354 return; 355 } 356 } 357 358 359 /** 360 * Free the state of a "deposit" CMD, and possibly cancel a 361 * pending operation thereof. 362 * 363 * @param cls closure, must be a `struct PurseDepositState`. 364 * @param cmd the command which is being cleaned up. 365 */ 366 static void 367 deposit_cleanup (void *cls, 368 const struct TALER_TESTING_Command *cmd) 369 { 370 struct PurseDepositState *ds = cls; 371 372 if (NULL != ds->dh) 373 { 374 TALER_TESTING_command_incomplete (ds->is, 375 cmd->label); 376 TALER_EXCHANGE_purse_deposit_cancel (ds->dh); 377 ds->dh = NULL; 378 } 379 for (unsigned int i = 0; i<ds->num_coin_references; i++) 380 GNUNET_free (ds->coin_references[i].command_ref); 381 GNUNET_free (ds->coin_references); 382 GNUNET_free (ds); 383 } 384 385 386 /** 387 * Offer internal data from a "deposit" CMD, to other commands. 388 * 389 * @param cls closure. 390 * @param[out] ret result. 391 * @param trait name of the trait. 392 * @param index index number of the object to offer. 393 * @return #GNUNET_OK on success. 394 */ 395 static enum GNUNET_GenericReturnValue 396 deposit_traits (void *cls, 397 const void **ret, 398 const char *trait, 399 unsigned int index) 400 { 401 struct PurseDepositState *ds = cls; 402 403 if (index >= ds->num_coin_references) 404 return GNUNET_NO; 405 { 406 const struct Coin *co = &ds->coin_references[index]; 407 struct TALER_TESTING_Trait traits[] = { 408 /* history entry MUST be first due to response code logic below! */ 409 TALER_TESTING_make_trait_reserve_history (0, 410 &ds->reserve_history), 411 TALER_TESTING_make_trait_coin_history (index, 412 &co->che), 413 TALER_TESTING_make_trait_coin_pub (index, 414 &co->coin_pub), 415 TALER_TESTING_make_trait_reserve_pub (&ds->reserve_pub), 416 TALER_TESTING_make_trait_purse_pub (&ds->purse_pub), 417 TALER_TESTING_trait_end () 418 }; 419 420 return TALER_TESTING_get_trait (ds->purse_complete 421 ? &traits[0] /* we have reserve history */ 422 : &traits[1], /* skip reserve history */ 423 ret, 424 trait, 425 index); 426 } 427 } 428 429 430 struct TALER_TESTING_Command 431 TALER_TESTING_cmd_purse_deposit_coins ( 432 const char *label, 433 unsigned int expected_http_status, 434 uint8_t min_age, 435 const char *purse_ref, 436 ...) 437 { 438 struct PurseDepositState *ds; 439 440 ds = GNUNET_new (struct PurseDepositState); 441 ds->expected_response_code = expected_http_status; 442 ds->min_age = min_age; 443 ds->purse_ref = purse_ref; 444 { 445 va_list ap; 446 unsigned int i; 447 const char *ref; 448 const char *val; 449 450 va_start (ap, purse_ref); 451 while (NULL != (va_arg (ap, const char *))) 452 ds->num_coin_references++; 453 va_end (ap); 454 GNUNET_assert (0 == (ds->num_coin_references % 2)); 455 ds->num_coin_references /= 2; 456 ds->coin_references = GNUNET_new_array (ds->num_coin_references, 457 struct Coin); 458 i = 0; 459 va_start (ap, purse_ref); 460 while (NULL != (ref = va_arg (ap, const char *))) 461 { 462 struct Coin *c = &ds->coin_references[i++]; 463 464 GNUNET_assert (NULL != (val = va_arg (ap, 465 const char *))); 466 GNUNET_assert (GNUNET_OK == 467 TALER_TESTING_parse_coin_reference ( 468 ref, 469 &c->command_ref, 470 &c->coin_index)); 471 GNUNET_assert (GNUNET_OK == 472 TALER_string_to_amount (val, 473 &c->deposit_with_fee)); 474 } 475 va_end (ap); 476 } 477 { 478 struct TALER_TESTING_Command cmd = { 479 .cls = ds, 480 .label = label, 481 .run = &deposit_run, 482 .cleanup = &deposit_cleanup, 483 .traits = &deposit_traits 484 }; 485 486 return cmd; 487 } 488 } 489 490 491 /* end of testing_api_cmd_purse_deposit.c */