testing_api_cmd_purse_deposit.c (15078B)
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/taler_json_lib.h" 25 #include <gnunet/gnunet_curl_lib.h> 26 struct PurseDepositState; 27 #define TALER_EXCHANGE_POST_PURSES_DEPOSIT_RESULT_CLOSURE struct \ 28 PurseDepositState 29 #include "taler/exchange/post-purses-PURSE_PUB-deposit.h" 30 #include "taler/taler_testing_lib.h" 31 #include "taler/taler_signatures.h" 32 33 /** 34 * Information we keep per deposited coin. 35 */ 36 struct Coin 37 { 38 /** 39 * Reference to the respective command. 40 */ 41 char *command_ref; 42 43 /** 44 * Entry in the coin's history generated by this operation. 45 */ 46 struct TALER_EXCHANGE_CoinHistoryEntry che; 47 48 /** 49 * Public key of the deposited coin. 50 */ 51 struct TALER_CoinSpendPublicKeyP coin_pub; 52 53 /** 54 * index of the specific coin in the traits of @e command_ref. 55 */ 56 unsigned int coin_index; 57 58 /** 59 * Amount to deposit (with fee). 60 */ 61 struct TALER_Amount deposit_with_fee; 62 63 }; 64 65 66 /** 67 * State for a "purse deposit" CMD. 68 */ 69 struct PurseDepositState 70 { 71 72 /** 73 * Total purse target amount without fees. 74 */ 75 struct TALER_Amount target_amount; 76 77 /** 78 * Reference to any command that is able to provide a coin. 79 */ 80 struct Coin *coin_references; 81 82 /** 83 * The purse's public key. 84 */ 85 struct TALER_PurseContractPublicKeyP purse_pub; 86 87 /** 88 * The reserve we are being deposited into. 89 * Set as a trait once we know the reserve. 90 */ 91 struct TALER_ReservePublicKeyP reserve_pub; 92 93 /** 94 * PurseDeposit handle while operation is running. 95 */ 96 struct TALER_EXCHANGE_PostPursesDepositHandle *dh; 97 98 /** 99 * Interpreter state. 100 */ 101 struct TALER_TESTING_Interpreter *is; 102 103 /** 104 * Reference to the command that established the purse. 105 */ 106 const char *purse_ref; 107 108 /** 109 * Reserve history entry that corresponds to this operation. 110 * Will be of type #TALER_EXCHANGE_RTT_MERGE. 111 * Only valid if @e purse_complete is true. 112 */ 113 struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history; 114 /** 115 * Expected HTTP response code. 116 */ 117 unsigned int expected_response_code; 118 119 /** 120 * Length of the @e coin_references array. 121 */ 122 unsigned int num_coin_references; 123 124 /** 125 * Minimum age to apply to all deposits. 126 */ 127 uint8_t min_age; 128 129 /** 130 * Set to true if this deposit filled the purse. 131 */ 132 bool purse_complete; 133 }; 134 135 136 /** 137 * Callback to analyze the /purses/$PID/deposit response, just used to check if 138 * the response code is acceptable. 139 * 140 * @param ds closure. 141 * @param dr deposit response details 142 */ 143 static void 144 deposit_cb (struct PurseDepositState *ds, 145 const struct TALER_EXCHANGE_PostPursesDepositResponse *dr) 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 */