testing_api_cmd_reserve_purse.c (10874B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2022, 2024 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_reserve_purse.c 21 * @brief command for testing /reserves/$PID/purse 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 /** 31 * State for a "purse create with merge" CMD. 32 */ 33 struct ReservePurseState 34 { 35 36 /** 37 * Merge time (local time when the command was 38 * executed). 39 */ 40 struct GNUNET_TIME_Timestamp merge_timestamp; 41 42 /** 43 * Account (reserve) private key. 44 */ 45 union TALER_AccountPrivateKeyP account_priv; 46 47 /** 48 * Account (reserve) public key. 49 */ 50 union TALER_AccountPublicKeyP account_pub; 51 52 /** 53 * Reserve signature generated for the request 54 * (client-side). 55 */ 56 struct TALER_ReserveSignatureP reserve_sig; 57 58 /** 59 * Private key of the purse. 60 */ 61 struct TALER_PurseContractPrivateKeyP purse_priv; 62 63 /** 64 * Public key of the purse. 65 */ 66 struct TALER_PurseContractPublicKeyP purse_pub; 67 68 /** 69 * Private key with the merge capability. 70 */ 71 struct TALER_PurseMergePrivateKeyP merge_priv; 72 73 /** 74 * Public key of the merge capability. 75 */ 76 struct TALER_PurseMergePublicKeyP merge_pub; 77 78 /** 79 * Private key to decrypt the contract. 80 */ 81 struct TALER_ContractDiffiePrivateP contract_priv; 82 83 /** 84 * Handle while operation is running. 85 */ 86 struct TALER_EXCHANGE_PostReservesPurseHandle *dh; 87 88 /** 89 * When will the purse expire? 90 */ 91 struct GNUNET_TIME_Relative expiration_rel; 92 93 /** 94 * When will the purse expire? 95 */ 96 struct GNUNET_TIME_Timestamp purse_expiration; 97 98 /** 99 * Hash of the payto://-URI for the reserve we are 100 * merging into. 101 */ 102 struct TALER_NormalizedPaytoHashP h_payto; 103 104 /** 105 * Set to the KYC requirement row *if* the exchange replied with 106 * a request for KYC. 107 */ 108 uint64_t requirement_row; 109 110 /** 111 * Contract terms for the purse. 112 */ 113 json_t *contract_terms; 114 115 /** 116 * Reference to the reserve, or NULL (!). 117 */ 118 const char *reserve_ref; 119 120 /** 121 * Interpreter state. 122 */ 123 struct TALER_TESTING_Interpreter *is; 124 125 /** 126 * Expected HTTP response code. 127 */ 128 unsigned int expected_response_code; 129 130 /** 131 * True to pay the purse fee. 132 */ 133 bool pay_purse_fee; 134 }; 135 136 137 /** 138 * Callback to analyze the /reserves/$PID/purse response, just used to check if 139 * the response code is acceptable. 140 * 141 * @param cls closure. 142 * @param dr purse response details 143 */ 144 static void 145 purse_cb (void *cls, 146 const struct TALER_EXCHANGE_PostReservesPurseResponse *dr) 147 { 148 struct ReservePurseState *ds = cls; 149 150 ds->dh = NULL; 151 ds->reserve_sig = *dr->reserve_sig; 152 if (ds->expected_response_code != dr->hr.http_status) 153 { 154 TALER_TESTING_unexpected_status (ds->is, 155 dr->hr.http_status, 156 ds->expected_response_code); 157 return; 158 } 159 switch (dr->hr.http_status) 160 { 161 case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: 162 /* KYC required */ 163 ds->requirement_row = 164 dr->details.unavailable_for_legal_reasons.requirement_row; 165 GNUNET_break (0 == 166 GNUNET_memcmp ( 167 &ds->h_payto, 168 &dr->details.unavailable_for_legal_reasons.h_payto)); 169 break; 170 } 171 TALER_TESTING_interpreter_next (ds->is); 172 } 173 174 175 /** 176 * Run the command. 177 * 178 * @param cls closure. 179 * @param cmd the command to execute. 180 * @param is the interpreter state. 181 */ 182 static void 183 purse_run (void *cls, 184 const struct TALER_TESTING_Command *cmd, 185 struct TALER_TESTING_Interpreter *is) 186 { 187 struct ReservePurseState *ds = cls; 188 const struct TALER_ReservePrivateKeyP *reserve_priv; 189 const struct TALER_TESTING_Command *ref; 190 191 (void) cmd; 192 ds->is = is; 193 ref = TALER_TESTING_interpreter_lookup_command (ds->is, 194 ds->reserve_ref); 195 GNUNET_assert (NULL != ref); 196 if (GNUNET_OK != 197 TALER_TESTING_get_trait_reserve_priv (ref, 198 &reserve_priv)) 199 { 200 GNUNET_break (0); 201 TALER_TESTING_interpreter_fail (ds->is); 202 return; 203 } 204 ds->account_priv.reserve_priv = *reserve_priv; 205 GNUNET_CRYPTO_eddsa_key_create ( 206 &ds->purse_priv.eddsa_priv); 207 GNUNET_CRYPTO_eddsa_key_get_public ( 208 &ds->purse_priv.eddsa_priv, 209 &ds->purse_pub.eddsa_pub); 210 GNUNET_CRYPTO_eddsa_key_get_public ( 211 &ds->account_priv.reserve_priv.eddsa_priv, 212 &ds->account_pub.reserve_pub.eddsa_pub); 213 GNUNET_CRYPTO_eddsa_key_create ( 214 &ds->merge_priv.eddsa_priv); 215 GNUNET_CRYPTO_eddsa_key_get_public ( 216 &ds->merge_priv.eddsa_priv, 217 &ds->merge_pub.eddsa_pub); 218 GNUNET_CRYPTO_ecdhe_key_create ( 219 &ds->contract_priv.ecdhe_priv); 220 ds->purse_expiration 221 = GNUNET_TIME_absolute_to_timestamp ( 222 GNUNET_TIME_relative_to_absolute ( 223 ds->expiration_rel)); 224 225 { 226 struct TALER_NormalizedPayto payto_uri; 227 const char *exchange_url; 228 const struct TALER_TESTING_Command *exchange_cmd; 229 230 exchange_cmd = TALER_TESTING_interpreter_get_command (is, 231 "exchange"); 232 if (NULL == exchange_cmd) 233 { 234 GNUNET_break (0); 235 TALER_TESTING_interpreter_fail (is); 236 return; 237 } 238 GNUNET_assert ( 239 GNUNET_OK == 240 TALER_TESTING_get_trait_exchange_url ( 241 exchange_cmd, 242 &exchange_url)); 243 payto_uri 244 = TALER_reserve_make_payto ( 245 exchange_url, 246 &ds->account_pub.reserve_pub); 247 TALER_normalized_payto_hash (payto_uri, 248 &ds->h_payto); 249 GNUNET_free (payto_uri.normalized_payto); 250 } 251 252 GNUNET_assert (0 == 253 json_object_set_new ( 254 ds->contract_terms, 255 "pay_deadline", 256 GNUNET_JSON_from_timestamp (ds->purse_expiration))); 257 ds->merge_timestamp = GNUNET_TIME_timestamp_get (); 258 ds->dh = TALER_EXCHANGE_post_reserves_purse_create ( 259 TALER_TESTING_interpreter_get_context (is), 260 TALER_TESTING_get_exchange_url (is), 261 TALER_TESTING_get_keys (is), 262 &ds->account_priv.reserve_priv, 263 &ds->purse_priv, 264 &ds->merge_priv, 265 &ds->contract_priv, 266 ds->contract_terms, 267 ds->pay_purse_fee, 268 ds->merge_timestamp); 269 if (NULL == ds->dh) 270 { 271 GNUNET_break (0); 272 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 273 "Could not purse reserve\n"); 274 TALER_TESTING_interpreter_fail (is); 275 return; 276 } 277 GNUNET_assert (GNUNET_OK == 278 TALER_EXCHANGE_post_reserves_purse_set_options ( 279 ds->dh, 280 TALER_EXCHANGE_post_reserves_purse_option_upload_contract ()) 281 ); 282 GNUNET_assert (TALER_EC_NONE == 283 TALER_EXCHANGE_post_reserves_purse_start (ds->dh, 284 &purse_cb, 285 ds)); 286 } 287 288 289 /** 290 * Free the state of a "purse" CMD, and possibly cancel a 291 * pending operation thereof. 292 * 293 * @param cls closure, must be a `struct ReservePurseState`. 294 * @param cmd the command which is being cleaned up. 295 */ 296 static void 297 purse_cleanup (void *cls, 298 const struct TALER_TESTING_Command *cmd) 299 { 300 struct ReservePurseState *ds = cls; 301 302 if (NULL != ds->dh) 303 { 304 TALER_TESTING_command_incomplete (ds->is, 305 cmd->label); 306 TALER_EXCHANGE_post_reserves_purse_cancel (ds->dh); 307 ds->dh = NULL; 308 } 309 json_decref (ds->contract_terms); 310 GNUNET_free (ds); 311 } 312 313 314 /** 315 * Offer internal data from a "purse" CMD, to other commands. 316 * 317 * @param cls closure. 318 * @param[out] ret result. 319 * @param trait name of the trait. 320 * @param index index number of the object to offer. 321 * @return #GNUNET_OK on success. 322 */ 323 static enum GNUNET_GenericReturnValue 324 purse_traits (void *cls, 325 const void **ret, 326 const char *trait, 327 unsigned int index) 328 { 329 struct ReservePurseState *ds = cls; 330 struct TALER_TESTING_Trait traits[] = { 331 TALER_TESTING_make_trait_timestamp ( 332 0, 333 &ds->merge_timestamp), 334 TALER_TESTING_make_trait_contract_terms ( 335 ds->contract_terms), 336 TALER_TESTING_make_trait_purse_priv ( 337 &ds->purse_priv), 338 TALER_TESTING_make_trait_purse_pub ( 339 &ds->purse_pub), 340 TALER_TESTING_make_trait_merge_priv ( 341 &ds->merge_priv), 342 TALER_TESTING_make_trait_merge_pub ( 343 &ds->merge_pub), 344 TALER_TESTING_make_trait_contract_priv ( 345 &ds->contract_priv), 346 TALER_TESTING_make_trait_account_priv ( 347 &ds->account_priv), 348 TALER_TESTING_make_trait_account_pub ( 349 &ds->account_pub), 350 TALER_TESTING_make_trait_reserve_priv ( 351 &ds->account_priv.reserve_priv), 352 TALER_TESTING_make_trait_reserve_pub ( 353 &ds->account_pub.reserve_pub), 354 TALER_TESTING_make_trait_reserve_sig ( 355 &ds->reserve_sig), 356 TALER_TESTING_make_trait_legi_requirement_row ( 357 &ds->requirement_row), 358 TALER_TESTING_make_trait_h_normalized_payto ( 359 &ds->h_payto), 360 TALER_TESTING_trait_end () 361 }; 362 363 return TALER_TESTING_get_trait (traits, 364 ret, 365 trait, 366 index); 367 } 368 369 370 struct TALER_TESTING_Command 371 TALER_TESTING_cmd_purse_create_with_reserve ( 372 const char *label, 373 unsigned int expected_http_status, 374 const char *contract_terms, 375 bool upload_contract, 376 bool pay_purse_fee, 377 struct GNUNET_TIME_Relative expiration, 378 const char *reserve_ref) 379 { 380 struct ReservePurseState *ds; 381 json_error_t err; 382 383 ds = GNUNET_new (struct ReservePurseState); 384 ds->expiration_rel = expiration; 385 ds->contract_terms = json_loads (contract_terms, 386 0 /* flags */, 387 &err); 388 GNUNET_assert (NULL != ds->contract_terms); 389 ds->pay_purse_fee = pay_purse_fee; 390 ds->reserve_ref = reserve_ref; 391 ds->expected_response_code = expected_http_status; 392 393 { 394 struct TALER_TESTING_Command cmd = { 395 .cls = ds, 396 .label = label, 397 .run = &purse_run, 398 .cleanup = &purse_cleanup, 399 .traits = &purse_traits 400 }; 401 402 return cmd; 403 } 404 } 405 406 407 /* end of testing_api_cmd_reserve_purse.c */