testing_api_cmd_purse_create_deposit.c (13264B)
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/taler_json_lib.h" 25 #include <gnunet/gnunet_curl_lib.h> 26 struct PurseCreateDepositState; 27 #define TALER_EXCHANGE_POST_PURSES_CREATE_RESULT_CLOSURE \ 28 struct PurseCreateDepositState 29 #include "taler/exchange/post-purses-PURSE_PUB-create.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 * index of the specific coin in the traits of @e command_ref. 45 */ 46 unsigned int coin_index; 47 48 /** 49 * Public key of the deposited coin. 50 */ 51 struct TALER_CoinSpendPublicKeyP coin_pub; 52 53 /** 54 * Amount to deposit (with fee). 55 */ 56 struct TALER_Amount deposit_with_fee; 57 58 /** 59 * Entry in the coin's history generated by this operation. 60 */ 61 struct TALER_EXCHANGE_CoinHistoryEntry che; 62 63 }; 64 65 66 /** 67 * State for a "purse create deposit" CMD. 68 */ 69 struct PurseCreateDepositState 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 * JSON string describing what a proposal is about. 84 */ 85 json_t *contract_terms; 86 87 /** 88 * Purse expiration time. 89 */ 90 struct GNUNET_TIME_Timestamp purse_expiration; 91 92 /** 93 * Relative purse expiration time. 94 */ 95 struct GNUNET_TIME_Relative rel_expiration; 96 97 /** 98 * Set (by the interpreter) to a fresh private key. This 99 * key will be used to create the purse. 100 */ 101 struct TALER_PurseContractPrivateKeyP purse_priv; 102 103 /** 104 * Set (by the interpreter) to a fresh private key. This 105 * key will be used to merge the purse. 106 */ 107 struct TALER_PurseMergePrivateKeyP merge_priv; 108 109 /** 110 * Set (by the interpreter) to a fresh private key. This 111 * key will be used to decrypt the contract. 112 */ 113 struct TALER_ContractDiffiePrivateP contract_priv; 114 115 /** 116 * Signing key used by the exchange to sign the 117 * deposit confirmation. 118 */ 119 struct TALER_ExchangePublicKeyP exchange_pub; 120 121 /** 122 * Signature from the exchange on the 123 * deposit confirmation. 124 */ 125 struct TALER_ExchangeSignatureP exchange_sig; 126 127 /** 128 * Set (by the interpreter) to a public key corresponding 129 * to @e purse_priv. 130 */ 131 struct TALER_PurseContractPublicKeyP purse_pub; 132 133 /** 134 * PurseCreateDeposit handle while operation is running. 135 */ 136 struct TALER_EXCHANGE_PostPursesCreateHandle *dh; 137 138 /** 139 * Interpreter state. 140 */ 141 struct TALER_TESTING_Interpreter *is; 142 143 /** 144 * Expected HTTP response code. 145 */ 146 unsigned int expected_response_code; 147 148 /** 149 * Length of the @e coin_references array. 150 */ 151 unsigned int num_coin_references; 152 153 /** 154 * Should we upload the contract? 155 */ 156 bool upload_contract; 157 158 }; 159 160 161 /** 162 * Callback to analyze the /purses/$PID/create response, just used to check if 163 * the response code is acceptable. 164 * 165 * @param ds closure. 166 * @param dr deposit response details 167 */ 168 static void 169 deposit_cb (struct PurseCreateDepositState *ds, 170 const struct TALER_EXCHANGE_PostPursesCreateResponse *dr) 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 */