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