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