testing_api_cmd_purse_deposit.c (14977B)
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_PostPursesDepositHandle *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_PostPursesDepositResponse *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_post_purses_deposit_create ( 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 if (NULL == ds->dh) 347 { 348 GNUNET_break (0); 349 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 350 "Could not deposit into purse\n"); 351 TALER_TESTING_interpreter_fail (is); 352 return; 353 } 354 GNUNET_assert (TALER_EC_NONE == 355 TALER_EXCHANGE_post_purses_deposit_start (ds->dh, 356 &deposit_cb, 357 ds)); 358 } 359 360 361 /** 362 * Free the state of a "deposit" CMD, and possibly cancel a 363 * pending operation thereof. 364 * 365 * @param cls closure, must be a `struct PurseDepositState`. 366 * @param cmd the command which is being cleaned up. 367 */ 368 static void 369 deposit_cleanup (void *cls, 370 const struct TALER_TESTING_Command *cmd) 371 { 372 struct PurseDepositState *ds = cls; 373 374 if (NULL != ds->dh) 375 { 376 TALER_TESTING_command_incomplete (ds->is, 377 cmd->label); 378 TALER_EXCHANGE_post_purses_deposit_cancel (ds->dh); 379 ds->dh = NULL; 380 } 381 for (unsigned int i = 0; i<ds->num_coin_references; i++) 382 GNUNET_free (ds->coin_references[i].command_ref); 383 GNUNET_free (ds->coin_references); 384 GNUNET_free (ds); 385 } 386 387 388 /** 389 * Offer internal data from a "deposit" CMD, to other commands. 390 * 391 * @param cls closure. 392 * @param[out] ret result. 393 * @param trait name of the trait. 394 * @param index index number of the object to offer. 395 * @return #GNUNET_OK on success. 396 */ 397 static enum GNUNET_GenericReturnValue 398 deposit_traits (void *cls, 399 const void **ret, 400 const char *trait, 401 unsigned int index) 402 { 403 struct PurseDepositState *ds = cls; 404 405 if (index >= ds->num_coin_references) 406 return GNUNET_NO; 407 { 408 const struct Coin *co = &ds->coin_references[index]; 409 struct TALER_TESTING_Trait traits[] = { 410 /* history entry MUST be first due to response code logic below! */ 411 TALER_TESTING_make_trait_reserve_history (0, 412 &ds->reserve_history), 413 TALER_TESTING_make_trait_coin_history (index, 414 &co->che), 415 TALER_TESTING_make_trait_coin_pub (index, 416 &co->coin_pub), 417 TALER_TESTING_make_trait_reserve_pub (&ds->reserve_pub), 418 TALER_TESTING_make_trait_purse_pub (&ds->purse_pub), 419 TALER_TESTING_trait_end () 420 }; 421 422 return TALER_TESTING_get_trait (ds->purse_complete 423 ? &traits[0] /* we have reserve history */ 424 : &traits[1], /* skip reserve history */ 425 ret, 426 trait, 427 index); 428 } 429 } 430 431 432 struct TALER_TESTING_Command 433 TALER_TESTING_cmd_purse_deposit_coins ( 434 const char *label, 435 unsigned int expected_http_status, 436 uint8_t min_age, 437 const char *purse_ref, 438 ...) 439 { 440 struct PurseDepositState *ds; 441 442 ds = GNUNET_new (struct PurseDepositState); 443 ds->expected_response_code = expected_http_status; 444 ds->min_age = min_age; 445 ds->purse_ref = purse_ref; 446 { 447 va_list ap; 448 unsigned int i; 449 const char *ref; 450 const char *val; 451 452 va_start (ap, purse_ref); 453 while (NULL != (va_arg (ap, const char *))) 454 ds->num_coin_references++; 455 va_end (ap); 456 GNUNET_assert (0 == (ds->num_coin_references % 2)); 457 ds->num_coin_references /= 2; 458 ds->coin_references = GNUNET_new_array (ds->num_coin_references, 459 struct Coin); 460 i = 0; 461 va_start (ap, purse_ref); 462 while (NULL != (ref = va_arg (ap, const char *))) 463 { 464 struct Coin *c = &ds->coin_references[i++]; 465 466 GNUNET_assert (NULL != (val = va_arg (ap, 467 const char *))); 468 GNUNET_assert (GNUNET_OK == 469 TALER_TESTING_parse_coin_reference ( 470 ref, 471 &c->command_ref, 472 &c->coin_index)); 473 GNUNET_assert (GNUNET_OK == 474 TALER_string_to_amount (val, 475 &c->deposit_with_fee)); 476 } 477 va_end (ap); 478 } 479 { 480 struct TALER_TESTING_Command cmd = { 481 .cls = ds, 482 .label = label, 483 .run = &deposit_run, 484 .cleanup = &deposit_cleanup, 485 .traits = &deposit_traits 486 }; 487 488 return cmd; 489 } 490 } 491 492 493 /* end of testing_api_cmd_purse_deposit.c */