testing_api_cmd_purse_create_deposit.c (13153B)
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_PostPursesCreateHandle *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_PostPursesCreateResponse *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_post_purses_create_create ( 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 GNUNET_assert (NULL != ds->dh); 291 if (ds->upload_contract) 292 TALER_EXCHANGE_post_purses_create_set_options ( 293 ds->dh, 294 TALER_EXCHANGE_post_purses_create_option_upload_contract ()); 295 GNUNET_assert (TALER_EC_NONE == 296 TALER_EXCHANGE_post_purses_create_start (ds->dh, 297 &deposit_cb, 298 ds)); 299 } 300 301 302 /** 303 * Free the state of a "deposit" CMD, and possibly cancel a 304 * pending operation thereof. 305 * 306 * @param cls closure, must be a `struct PurseCreateDepositState`. 307 * @param cmd the command which is being cleaned up. 308 */ 309 static void 310 deposit_cleanup (void *cls, 311 const struct TALER_TESTING_Command *cmd) 312 { 313 struct PurseCreateDepositState *ds = cls; 314 315 if (NULL != ds->dh) 316 { 317 TALER_TESTING_command_incomplete (ds->is, 318 cmd->label); 319 TALER_EXCHANGE_post_purses_create_cancel (ds->dh); 320 ds->dh = NULL; 321 } 322 for (unsigned int i = 0; i<ds->num_coin_references; i++) 323 GNUNET_free (ds->coin_references[i].command_ref); 324 json_decref (ds->contract_terms); 325 GNUNET_free (ds->coin_references); 326 GNUNET_free (ds); 327 } 328 329 330 /** 331 * Offer internal data from a "deposit" CMD, to other commands. 332 * 333 * @param cls closure. 334 * @param[out] ret result. 335 * @param trait name of the trait. 336 * @param index index number of the object to offer. 337 * @return #GNUNET_OK on success. 338 */ 339 static enum GNUNET_GenericReturnValue 340 deposit_traits (void *cls, 341 const void **ret, 342 const char *trait, 343 unsigned int index) 344 { 345 struct PurseCreateDepositState *ds = cls; 346 if (index >= ds->num_coin_references) 347 return GNUNET_NO; 348 349 { 350 const struct Coin *co = &ds->coin_references[index]; 351 struct TALER_TESTING_Trait traits[] = { 352 TALER_TESTING_make_trait_merge_priv (&ds->merge_priv), 353 TALER_TESTING_make_trait_contract_priv (&ds->contract_priv), 354 TALER_TESTING_make_trait_coin_history (index, 355 &co->che), 356 TALER_TESTING_make_trait_coin_pub (index, 357 &co->coin_pub), 358 TALER_TESTING_make_trait_purse_priv (&ds->purse_priv), 359 TALER_TESTING_make_trait_purse_pub (&ds->purse_pub), 360 TALER_TESTING_make_trait_contract_terms (ds->contract_terms), 361 TALER_TESTING_make_trait_deposit_amount (0, 362 &ds->target_amount), 363 TALER_TESTING_make_trait_timestamp (index, 364 &ds->purse_expiration), 365 TALER_TESTING_trait_end () 366 }; 367 368 return TALER_TESTING_get_trait (traits, 369 ret, 370 trait, 371 index); 372 } 373 } 374 375 376 struct TALER_TESTING_Command 377 TALER_TESTING_cmd_purse_create_with_deposit ( 378 const char *label, 379 unsigned int expected_http_status, 380 const char *contract_terms, 381 bool upload_contract, 382 struct GNUNET_TIME_Relative purse_expiration, 383 ...) 384 { 385 struct PurseCreateDepositState *ds; 386 387 ds = GNUNET_new (struct PurseCreateDepositState); 388 ds->rel_expiration = purse_expiration; 389 ds->upload_contract = upload_contract; 390 ds->expected_response_code = expected_http_status; 391 ds->contract_terms = json_loads (contract_terms, 392 JSON_REJECT_DUPLICATES, 393 NULL); 394 if (NULL == ds->contract_terms) 395 { 396 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 397 "Failed to parse contract terms `%s' for CMD `%s'\n", 398 contract_terms, 399 label); 400 GNUNET_assert (0); 401 } 402 { 403 va_list ap; 404 unsigned int i; 405 const char *ref; 406 const char *val; 407 408 va_start (ap, purse_expiration); 409 while (NULL != (va_arg (ap, const char *))) 410 ds->num_coin_references++; 411 va_end (ap); 412 GNUNET_assert (0 == (ds->num_coin_references % 2)); 413 ds->num_coin_references /= 2; 414 ds->coin_references = GNUNET_new_array (ds->num_coin_references, 415 struct Coin); 416 i = 0; 417 va_start (ap, purse_expiration); 418 while (NULL != (ref = va_arg (ap, const char *))) 419 { 420 struct Coin *c = &ds->coin_references[i++]; 421 422 GNUNET_assert (NULL != (val = va_arg (ap, const char *))); 423 GNUNET_assert (GNUNET_OK == 424 TALER_TESTING_parse_coin_reference ( 425 ref, 426 &c->command_ref, 427 &c->coin_index)); 428 GNUNET_assert (GNUNET_OK == 429 TALER_string_to_amount (val, 430 &c->deposit_with_fee)); 431 } 432 va_end (ap); 433 } 434 { 435 struct TALER_TESTING_Command cmd = { 436 .cls = ds, 437 .label = label, 438 .run = &deposit_run, 439 .cleanup = &deposit_cleanup, 440 .traits = &deposit_traits 441 }; 442 443 return cmd; 444 } 445 } 446 447 448 /* end of testing_api_cmd_purse_create_deposit.c */