testing_api_cmd_purse_deposit.c (14922B)
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 #include "taler/taler_testing_lib.h" 27 #include "taler/taler_signatures.h" 28 29 /** 30 * Information we keep per deposited coin. 31 */ 32 struct Coin 33 { 34 /** 35 * Reference to the respective command. 36 */ 37 char *command_ref; 38 39 /** 40 * Entry in the coin's history generated by this operation. 41 */ 42 struct TALER_EXCHANGE_CoinHistoryEntry che; 43 44 /** 45 * Public key of the deposited coin. 46 */ 47 struct TALER_CoinSpendPublicKeyP coin_pub; 48 49 /** 50 * index of the specific coin in the traits of @e command_ref. 51 */ 52 unsigned int coin_index; 53 54 /** 55 * Amount to deposit (with fee). 56 */ 57 struct TALER_Amount deposit_with_fee; 58 59 }; 60 61 62 /** 63 * State for a "purse deposit" CMD. 64 */ 65 struct PurseDepositState 66 { 67 68 /** 69 * Total purse target amount without fees. 70 */ 71 struct TALER_Amount target_amount; 72 73 /** 74 * Reference to any command that is able to provide a coin. 75 */ 76 struct Coin *coin_references; 77 78 /** 79 * The purse's public key. 80 */ 81 struct TALER_PurseContractPublicKeyP purse_pub; 82 83 /** 84 * The reserve we are being deposited into. 85 * Set as a trait once we know the reserve. 86 */ 87 struct TALER_ReservePublicKeyP reserve_pub; 88 89 /** 90 * PurseDeposit handle while operation is running. 91 */ 92 struct TALER_EXCHANGE_PostPursesDepositHandle *dh; 93 94 /** 95 * Interpreter state. 96 */ 97 struct TALER_TESTING_Interpreter *is; 98 99 /** 100 * Reference to the command that established the purse. 101 */ 102 const char *purse_ref; 103 104 /** 105 * Reserve history entry that corresponds to this operation. 106 * Will be of type #TALER_EXCHANGE_RTT_MERGE. 107 * Only valid if @e purse_complete is true. 108 */ 109 struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history; 110 /** 111 * Expected HTTP response code. 112 */ 113 unsigned int expected_response_code; 114 115 /** 116 * Length of the @e coin_references array. 117 */ 118 unsigned int num_coin_references; 119 120 /** 121 * Minimum age to apply to all deposits. 122 */ 123 uint8_t min_age; 124 125 /** 126 * Set to true if this deposit filled the purse. 127 */ 128 bool purse_complete; 129 }; 130 131 132 /** 133 * Callback to analyze the /purses/$PID/deposit response, just used to check if 134 * the response code is acceptable. 135 * 136 * @param cls closure. 137 * @param dr deposit response details 138 */ 139 static void 140 deposit_cb (void *cls, 141 const struct TALER_EXCHANGE_PostPursesDepositResponse *dr) 142 { 143 struct PurseDepositState *ds = cls; 144 145 ds->dh = NULL; 146 if (ds->expected_response_code != dr->hr.http_status) 147 { 148 TALER_TESTING_unexpected_status (ds->is, 149 dr->hr.http_status, 150 ds->expected_response_code); 151 return; 152 } 153 if (MHD_HTTP_OK == dr->hr.http_status) 154 { 155 if (-1 != 156 TALER_amount_cmp (&dr->details.ok.total_deposited, 157 &dr->details.ok.purse_value_after_fees)) 158 { 159 const struct TALER_TESTING_Command *purse_cmd; 160 const struct TALER_ReserveSignatureP *reserve_sig; 161 const struct TALER_ReservePublicKeyP *reserve_pub; 162 const struct GNUNET_TIME_Timestamp *merge_timestamp; 163 const struct TALER_PurseMergePublicKeyP *merge_pub; 164 165 purse_cmd = TALER_TESTING_interpreter_lookup_command (ds->is, 166 ds->purse_ref); 167 GNUNET_assert (NULL != purse_cmd); 168 if (GNUNET_OK != 169 TALER_TESTING_get_trait_reserve_sig (purse_cmd, 170 &reserve_sig)) 171 { 172 GNUNET_break (0); 173 TALER_TESTING_interpreter_fail (ds->is); 174 return; 175 } 176 if (GNUNET_OK != 177 TALER_TESTING_get_trait_reserve_pub (purse_cmd, 178 &reserve_pub)) 179 { 180 GNUNET_break (0); 181 TALER_TESTING_interpreter_fail (ds->is); 182 return; 183 } 184 if (GNUNET_OK != 185 TALER_TESTING_get_trait_merge_pub (purse_cmd, 186 &merge_pub)) 187 { 188 GNUNET_break (0); 189 TALER_TESTING_interpreter_fail (ds->is); 190 return; 191 } 192 ds->reserve_pub = *reserve_pub; 193 if (GNUNET_OK != 194 TALER_TESTING_get_trait_timestamp (purse_cmd, 195 0, 196 &merge_timestamp)) 197 { 198 GNUNET_break (0); 199 TALER_TESTING_interpreter_fail (ds->is); 200 return; 201 } 202 203 /* Deposits complete, create trait! */ 204 ds->reserve_history.type = TALER_EXCHANGE_RTT_MERGE; 205 { 206 struct TALER_EXCHANGE_Keys *keys; 207 const struct TALER_EXCHANGE_GlobalFee *gf; 208 209 keys = TALER_TESTING_get_keys (ds->is); 210 GNUNET_assert (NULL != keys); 211 gf = TALER_EXCHANGE_get_global_fee (keys, 212 *merge_timestamp); 213 GNUNET_assert (NULL != gf); 214 215 /* Note: change when flags below changes! */ 216 ds->reserve_history.amount 217 = dr->details.ok.purse_value_after_fees; 218 if (true) 219 { 220 ds->reserve_history.details.merge_details.purse_fee = gf->fees.purse; 221 } 222 else 223 { 224 TALER_amount_set_zero ( 225 ds->reserve_history.amount.currency, 226 &ds->reserve_history.details.merge_details.purse_fee); 227 } 228 } 229 ds->reserve_history.details.merge_details.h_contract_terms 230 = dr->details.ok.h_contract_terms; 231 ds->reserve_history.details.merge_details.merge_pub 232 = *merge_pub; 233 ds->reserve_history.details.merge_details.purse_pub 234 = ds->purse_pub; 235 ds->reserve_history.details.merge_details.reserve_sig 236 = *reserve_sig; 237 ds->reserve_history.details.merge_details.merge_timestamp 238 = *merge_timestamp; 239 ds->reserve_history.details.merge_details.purse_expiration 240 = dr->details.ok.purse_expiration; 241 ds->reserve_history.details.merge_details.min_age 242 = ds->min_age; 243 ds->reserve_history.details.merge_details.flags 244 = TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE; 245 ds->purse_complete = true; 246 } 247 } 248 TALER_TESTING_interpreter_next (ds->is); 249 } 250 251 252 /** 253 * Run the command. 254 * 255 * @param cls closure. 256 * @param cmd the command to execute. 257 * @param is the interpreter state. 258 */ 259 static void 260 deposit_run (void *cls, 261 const struct TALER_TESTING_Command *cmd, 262 struct TALER_TESTING_Interpreter *is) 263 { 264 struct PurseDepositState *ds = cls; 265 struct TALER_EXCHANGE_PurseDeposit deposits[ds->num_coin_references]; 266 const struct TALER_PurseContractPublicKeyP *purse_pub; 267 const struct TALER_TESTING_Command *purse_cmd; 268 269 (void) cmd; 270 ds->is = is; 271 purse_cmd = TALER_TESTING_interpreter_lookup_command (is, 272 ds->purse_ref); 273 GNUNET_assert (NULL != purse_cmd); 274 if (GNUNET_OK != 275 TALER_TESTING_get_trait_purse_pub (purse_cmd, 276 &purse_pub)) 277 { 278 GNUNET_break (0); 279 TALER_TESTING_interpreter_fail (is); 280 return; 281 } 282 ds->purse_pub = *purse_pub; 283 for (unsigned int i = 0; i<ds->num_coin_references; i++) 284 { 285 struct Coin *cr = &ds->coin_references[i]; 286 struct TALER_EXCHANGE_PurseDeposit *pd = &deposits[i]; 287 const struct TALER_TESTING_Command *coin_cmd; 288 const struct TALER_CoinSpendPrivateKeyP *coin_priv; 289 const struct TALER_AgeCommitmentProof *age_commitment_proof = NULL; 290 const struct TALER_EXCHANGE_DenomPublicKey *denom_pub; 291 const struct TALER_DenominationSignature *denom_pub_sig; 292 293 coin_cmd = TALER_TESTING_interpreter_lookup_command (is, 294 cr->command_ref); 295 GNUNET_assert (NULL != coin_cmd); 296 if ( (GNUNET_OK != 297 TALER_TESTING_get_trait_coin_priv (coin_cmd, 298 cr->coin_index, 299 &coin_priv)) || 300 (GNUNET_OK != 301 TALER_TESTING_get_trait_age_commitment_proof (coin_cmd, 302 cr->coin_index, 303 &age_commitment_proof)) 304 || 305 (GNUNET_OK != 306 TALER_TESTING_get_trait_denom_pub (coin_cmd, 307 cr->coin_index, 308 &denom_pub)) || 309 (GNUNET_OK != 310 TALER_TESTING_get_trait_denom_sig (coin_cmd, 311 cr->coin_index, 312 &denom_pub_sig)) ) 313 { 314 GNUNET_break (0); 315 TALER_TESTING_interpreter_fail (is); 316 return; 317 } 318 GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, 319 &cr->coin_pub.eddsa_pub); 320 cr->che.type = TALER_EXCHANGE_CTT_PURSE_DEPOSIT; 321 cr->che.amount = cr->deposit_with_fee; 322 cr->che.details.purse_deposit.purse_pub = *purse_pub; 323 cr->che.details.purse_deposit.exchange_base_url 324 = TALER_TESTING_get_exchange_url (is); 325 TALER_age_commitment_hash ( 326 &age_commitment_proof->commitment, 327 &cr->che.details.purse_deposit.phac); 328 pd->age_commitment_proof = age_commitment_proof; 329 pd->denom_sig = *denom_pub_sig; 330 pd->coin_priv = *coin_priv; 331 pd->amount = cr->deposit_with_fee; 332 pd->h_denom_pub = denom_pub->h_key; 333 } 334 335 ds->dh = TALER_EXCHANGE_post_purses_deposit_create ( 336 TALER_TESTING_interpreter_get_context (is), 337 TALER_TESTING_get_exchange_url (is), 338 TALER_TESTING_get_keys (is), 339 NULL, /* FIXME #7271: WADs support: purse exchange URL */ 340 &ds->purse_pub, 341 ds->min_age, 342 ds->num_coin_references, 343 deposits); 344 if (NULL == ds->dh) 345 { 346 GNUNET_break (0); 347 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 348 "Could not deposit into purse\n"); 349 TALER_TESTING_interpreter_fail (is); 350 return; 351 } 352 GNUNET_assert (TALER_EC_NONE == 353 TALER_EXCHANGE_post_purses_deposit_start (ds->dh, 354 &deposit_cb, 355 ds)); 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_post_purses_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 */