testing_api_cmd_purse_create_deposit.c (12988B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2022 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_create_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 * index of the specific coin in the traits of @e command_ref. 43 */ 44 unsigned int coin_index; 45 46 /** 47 * Public key of the deposited coin. 48 */ 49 struct TALER_CoinSpendPublicKeyP coin_pub; 50 51 /** 52 * Amount to deposit (with fee). 53 */ 54 struct TALER_Amount deposit_with_fee; 55 56 /** 57 * Entry in the coin's history generated by this operation. 58 */ 59 struct TALER_EXCHANGE_CoinHistoryEntry che; 60 61 }; 62 63 64 /** 65 * State for a "purse create deposit" CMD. 66 */ 67 struct PurseCreateDepositState 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 * JSON string describing what a proposal is about. 82 */ 83 json_t *contract_terms; 84 85 /** 86 * Purse expiration time. 87 */ 88 struct GNUNET_TIME_Timestamp purse_expiration; 89 90 /** 91 * Relative purse expiration time. 92 */ 93 struct GNUNET_TIME_Relative rel_expiration; 94 95 /** 96 * Set (by the interpreter) to a fresh private key. This 97 * key will be used to create the purse. 98 */ 99 struct TALER_PurseContractPrivateKeyP purse_priv; 100 101 /** 102 * Set (by the interpreter) to a fresh private key. This 103 * key will be used to merge the purse. 104 */ 105 struct TALER_PurseMergePrivateKeyP merge_priv; 106 107 /** 108 * Set (by the interpreter) to a fresh private key. This 109 * key will be used to decrypt the contract. 110 */ 111 struct TALER_ContractDiffiePrivateP contract_priv; 112 113 /** 114 * Signing key used by the exchange to sign the 115 * deposit confirmation. 116 */ 117 struct TALER_ExchangePublicKeyP exchange_pub; 118 119 /** 120 * Signature from the exchange on the 121 * deposit confirmation. 122 */ 123 struct TALER_ExchangeSignatureP exchange_sig; 124 125 /** 126 * Set (by the interpreter) to a public key corresponding 127 * to @e purse_priv. 128 */ 129 struct TALER_PurseContractPublicKeyP purse_pub; 130 131 /** 132 * PurseCreateDeposit handle while operation is running. 133 */ 134 struct TALER_EXCHANGE_PurseCreateDepositHandle *dh; 135 136 /** 137 * Interpreter state. 138 */ 139 struct TALER_TESTING_Interpreter *is; 140 141 /** 142 * Expected HTTP response code. 143 */ 144 unsigned int expected_response_code; 145 146 /** 147 * Length of the @e coin_references array. 148 */ 149 unsigned int num_coin_references; 150 151 /** 152 * Should we upload the contract? 153 */ 154 bool upload_contract; 155 156 }; 157 158 159 /** 160 * Callback to analyze the /purses/$PID/create response, just used to check if 161 * the response code is acceptable. 162 * 163 * @param cls closure. 164 * @param dr deposit response details 165 */ 166 static void 167 deposit_cb (void *cls, 168 const struct TALER_EXCHANGE_PurseCreateDepositResponse *dr) 169 { 170 struct PurseCreateDepositState *ds = cls; 171 172 ds->dh = NULL; 173 if (ds->expected_response_code != dr->hr.http_status) 174 { 175 TALER_TESTING_unexpected_status (ds->is, 176 dr->hr.http_status, 177 ds->expected_response_code); 178 return; 179 } 180 if (MHD_HTTP_OK == dr->hr.http_status) 181 { 182 ds->exchange_pub = dr->details.ok.exchange_pub; 183 ds->exchange_sig = dr->details.ok.exchange_sig; 184 } 185 TALER_TESTING_interpreter_next (ds->is); 186 } 187 188 189 /** 190 * Run the command. 191 * 192 * @param cls closure. 193 * @param cmd the command to execute. 194 * @param is the interpreter state. 195 */ 196 static void 197 deposit_run (void *cls, 198 const struct TALER_TESTING_Command *cmd, 199 struct TALER_TESTING_Interpreter *is) 200 { 201 struct PurseCreateDepositState *ds = cls; 202 struct TALER_EXCHANGE_PurseDeposit deposits[ds->num_coin_references]; 203 204 (void) cmd; 205 ds->is = is; 206 GNUNET_CRYPTO_eddsa_key_create (&ds->purse_priv.eddsa_priv); 207 GNUNET_CRYPTO_eddsa_key_create (&ds->merge_priv.eddsa_priv); 208 GNUNET_CRYPTO_ecdhe_key_create (&ds->contract_priv.ecdhe_priv); 209 GNUNET_CRYPTO_eddsa_key_get_public (&ds->purse_priv.eddsa_priv, 210 &ds->purse_pub.eddsa_pub); 211 212 for (unsigned int i = 0; i<ds->num_coin_references; i++) 213 { 214 struct Coin *cr = &ds->coin_references[i]; 215 struct TALER_EXCHANGE_PurseDeposit *pd = &deposits[i]; 216 const struct TALER_TESTING_Command *coin_cmd; 217 const struct TALER_CoinSpendPrivateKeyP *coin_priv; 218 const struct TALER_AgeCommitmentProof *age_commitment_proof = NULL; 219 const struct TALER_EXCHANGE_DenomPublicKey *denom_pub; 220 const struct TALER_DenominationSignature *denom_pub_sig; 221 222 coin_cmd = TALER_TESTING_interpreter_lookup_command (is, 223 cr->command_ref); 224 if (NULL == coin_cmd) 225 { 226 GNUNET_break (0); 227 TALER_TESTING_interpreter_fail (is); 228 return; 229 } 230 231 if ( (GNUNET_OK != 232 TALER_TESTING_get_trait_coin_priv (coin_cmd, 233 cr->coin_index, 234 &coin_priv)) || 235 (GNUNET_OK != 236 TALER_TESTING_get_trait_age_commitment_proof (coin_cmd, 237 cr->coin_index, 238 &age_commitment_proof)) 239 || 240 (GNUNET_OK != 241 TALER_TESTING_get_trait_denom_pub (coin_cmd, 242 cr->coin_index, 243 &denom_pub)) || 244 (GNUNET_OK != 245 TALER_TESTING_get_trait_denom_sig (coin_cmd, 246 cr->coin_index, 247 &denom_pub_sig)) ) 248 { 249 GNUNET_break (0); 250 TALER_TESTING_interpreter_fail (is); 251 return; 252 } 253 pd->age_commitment_proof = age_commitment_proof; 254 pd->denom_sig = *denom_pub_sig; 255 pd->coin_priv = *coin_priv; 256 pd->amount = cr->deposit_with_fee; 257 pd->h_denom_pub = denom_pub->h_key; 258 GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, 259 &cr->coin_pub.eddsa_pub); 260 cr->che.type = TALER_EXCHANGE_CTT_PURSE_DEPOSIT; 261 cr->che.amount = cr->deposit_with_fee; 262 GNUNET_CRYPTO_eddsa_key_get_public ( 263 &ds->purse_priv.eddsa_priv, 264 &cr->che.details.purse_deposit.purse_pub.eddsa_pub); 265 cr->che.details.purse_deposit.exchange_base_url 266 = TALER_TESTING_get_exchange_url (is); 267 TALER_age_commitment_hash ( 268 &age_commitment_proof->commitment, 269 &cr->che.details.purse_deposit.phac); 270 } 271 272 ds->purse_expiration = 273 GNUNET_TIME_absolute_to_timestamp ( 274 GNUNET_TIME_relative_to_absolute (ds->rel_expiration)); 275 GNUNET_assert (0 == 276 json_object_set_new ( 277 ds->contract_terms, 278 "pay_deadline", 279 GNUNET_JSON_from_timestamp (ds->purse_expiration))); 280 ds->dh = TALER_EXCHANGE_purse_create_with_deposit ( 281 TALER_TESTING_interpreter_get_context (is), 282 TALER_TESTING_get_exchange_url (is), 283 TALER_TESTING_get_keys (is), 284 &ds->purse_priv, 285 &ds->merge_priv, 286 &ds->contract_priv, 287 ds->contract_terms, 288 ds->num_coin_references, 289 deposits, 290 ds->upload_contract, 291 &deposit_cb, 292 ds); 293 if (NULL == ds->dh) 294 { 295 GNUNET_break (0); 296 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 297 "Could not create purse with deposit\n"); 298 TALER_TESTING_interpreter_fail (is); 299 return; 300 } 301 } 302 303 304 /** 305 * Free the state of a "deposit" CMD, and possibly cancel a 306 * pending operation thereof. 307 * 308 * @param cls closure, must be a `struct PurseCreateDepositState`. 309 * @param cmd the command which is being cleaned up. 310 */ 311 static void 312 deposit_cleanup (void *cls, 313 const struct TALER_TESTING_Command *cmd) 314 { 315 struct PurseCreateDepositState *ds = cls; 316 317 if (NULL != ds->dh) 318 { 319 TALER_TESTING_command_incomplete (ds->is, 320 cmd->label); 321 TALER_EXCHANGE_purse_create_with_deposit_cancel (ds->dh); 322 ds->dh = NULL; 323 } 324 for (unsigned int i = 0; i<ds->num_coin_references; i++) 325 GNUNET_free (ds->coin_references[i].command_ref); 326 json_decref (ds->contract_terms); 327 GNUNET_free (ds->coin_references); 328 GNUNET_free (ds); 329 } 330 331 332 /** 333 * Offer internal data from a "deposit" CMD, to other commands. 334 * 335 * @param cls closure. 336 * @param[out] ret result. 337 * @param trait name of the trait. 338 * @param index index number of the object to offer. 339 * @return #GNUNET_OK on success. 340 */ 341 static enum GNUNET_GenericReturnValue 342 deposit_traits (void *cls, 343 const void **ret, 344 const char *trait, 345 unsigned int index) 346 { 347 struct PurseCreateDepositState *ds = cls; 348 if (index >= ds->num_coin_references) 349 return GNUNET_NO; 350 351 { 352 const struct Coin *co = &ds->coin_references[index]; 353 struct TALER_TESTING_Trait traits[] = { 354 TALER_TESTING_make_trait_merge_priv (&ds->merge_priv), 355 TALER_TESTING_make_trait_contract_priv (&ds->contract_priv), 356 TALER_TESTING_make_trait_coin_history (index, 357 &co->che), 358 TALER_TESTING_make_trait_coin_pub (index, 359 &co->coin_pub), 360 TALER_TESTING_make_trait_purse_priv (&ds->purse_priv), 361 TALER_TESTING_make_trait_purse_pub (&ds->purse_pub), 362 TALER_TESTING_make_trait_contract_terms (ds->contract_terms), 363 TALER_TESTING_make_trait_deposit_amount (0, 364 &ds->target_amount), 365 TALER_TESTING_make_trait_timestamp (index, 366 &ds->purse_expiration), 367 TALER_TESTING_trait_end () 368 }; 369 370 return TALER_TESTING_get_trait (traits, 371 ret, 372 trait, 373 index); 374 } 375 } 376 377 378 struct TALER_TESTING_Command 379 TALER_TESTING_cmd_purse_create_with_deposit ( 380 const char *label, 381 unsigned int expected_http_status, 382 const char *contract_terms, 383 bool upload_contract, 384 struct GNUNET_TIME_Relative purse_expiration, 385 ...) 386 { 387 struct PurseCreateDepositState *ds; 388 389 ds = GNUNET_new (struct PurseCreateDepositState); 390 ds->rel_expiration = purse_expiration; 391 ds->upload_contract = upload_contract; 392 ds->expected_response_code = expected_http_status; 393 ds->contract_terms = json_loads (contract_terms, 394 JSON_REJECT_DUPLICATES, 395 NULL); 396 if (NULL == ds->contract_terms) 397 { 398 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 399 "Failed to parse contract terms `%s' for CMD `%s'\n", 400 contract_terms, 401 label); 402 GNUNET_assert (0); 403 } 404 { 405 va_list ap; 406 unsigned int i; 407 const char *ref; 408 const char *val; 409 410 va_start (ap, purse_expiration); 411 while (NULL != (va_arg (ap, const char *))) 412 ds->num_coin_references++; 413 va_end (ap); 414 GNUNET_assert (0 == (ds->num_coin_references % 2)); 415 ds->num_coin_references /= 2; 416 ds->coin_references = GNUNET_new_array (ds->num_coin_references, 417 struct Coin); 418 i = 0; 419 va_start (ap, purse_expiration); 420 while (NULL != (ref = va_arg (ap, const char *))) 421 { 422 struct Coin *c = &ds->coin_references[i++]; 423 424 GNUNET_assert (NULL != (val = va_arg (ap, const char *))); 425 GNUNET_assert (GNUNET_OK == 426 TALER_TESTING_parse_coin_reference ( 427 ref, 428 &c->command_ref, 429 &c->coin_index)); 430 GNUNET_assert (GNUNET_OK == 431 TALER_string_to_amount (val, 432 &c->deposit_with_fee)); 433 } 434 va_end (ap); 435 } 436 { 437 struct TALER_TESTING_Command cmd = { 438 .cls = ds, 439 .label = label, 440 .run = &deposit_run, 441 .cleanup = &deposit_cleanup, 442 .traits = &deposit_traits 443 }; 444 445 return cmd; 446 } 447 } 448 449 450 /* end of testing_api_cmd_purse_create_deposit.c */