testing_api_cmd_reserve_purse.c (10523B)
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_PurseCreateMergeHandle *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_PurseCreateMergeResponse *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_purse_create_with_merge ( 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 true /* upload contract */, 270 ds->pay_purse_fee, 271 ds->merge_timestamp, 272 &purse_cb, 273 ds); 274 if (NULL == ds->dh) 275 { 276 GNUNET_break (0); 277 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 278 "Could not purse reserve\n"); 279 TALER_TESTING_interpreter_fail (is); 280 return; 281 } 282 } 283 284 285 /** 286 * Free the state of a "purse" CMD, and possibly cancel a 287 * pending operation thereof. 288 * 289 * @param cls closure, must be a `struct ReservePurseState`. 290 * @param cmd the command which is being cleaned up. 291 */ 292 static void 293 purse_cleanup (void *cls, 294 const struct TALER_TESTING_Command *cmd) 295 { 296 struct ReservePurseState *ds = cls; 297 298 if (NULL != ds->dh) 299 { 300 TALER_TESTING_command_incomplete (ds->is, 301 cmd->label); 302 TALER_EXCHANGE_purse_create_with_merge_cancel (ds->dh); 303 ds->dh = NULL; 304 } 305 json_decref (ds->contract_terms); 306 GNUNET_free (ds); 307 } 308 309 310 /** 311 * Offer internal data from a "purse" CMD, to other commands. 312 * 313 * @param cls closure. 314 * @param[out] ret result. 315 * @param trait name of the trait. 316 * @param index index number of the object to offer. 317 * @return #GNUNET_OK on success. 318 */ 319 static enum GNUNET_GenericReturnValue 320 purse_traits (void *cls, 321 const void **ret, 322 const char *trait, 323 unsigned int index) 324 { 325 struct ReservePurseState *ds = cls; 326 struct TALER_TESTING_Trait traits[] = { 327 TALER_TESTING_make_trait_timestamp ( 328 0, 329 &ds->merge_timestamp), 330 TALER_TESTING_make_trait_contract_terms ( 331 ds->contract_terms), 332 TALER_TESTING_make_trait_purse_priv ( 333 &ds->purse_priv), 334 TALER_TESTING_make_trait_purse_pub ( 335 &ds->purse_pub), 336 TALER_TESTING_make_trait_merge_priv ( 337 &ds->merge_priv), 338 TALER_TESTING_make_trait_merge_pub ( 339 &ds->merge_pub), 340 TALER_TESTING_make_trait_contract_priv ( 341 &ds->contract_priv), 342 TALER_TESTING_make_trait_account_priv ( 343 &ds->account_priv), 344 TALER_TESTING_make_trait_account_pub ( 345 &ds->account_pub), 346 TALER_TESTING_make_trait_reserve_priv ( 347 &ds->account_priv.reserve_priv), 348 TALER_TESTING_make_trait_reserve_pub ( 349 &ds->account_pub.reserve_pub), 350 TALER_TESTING_make_trait_reserve_sig ( 351 &ds->reserve_sig), 352 TALER_TESTING_make_trait_legi_requirement_row ( 353 &ds->requirement_row), 354 TALER_TESTING_make_trait_h_normalized_payto ( 355 &ds->h_payto), 356 TALER_TESTING_trait_end () 357 }; 358 359 return TALER_TESTING_get_trait (traits, 360 ret, 361 trait, 362 index); 363 } 364 365 366 struct TALER_TESTING_Command 367 TALER_TESTING_cmd_purse_create_with_reserve ( 368 const char *label, 369 unsigned int expected_http_status, 370 const char *contract_terms, 371 bool upload_contract, 372 bool pay_purse_fee, 373 struct GNUNET_TIME_Relative expiration, 374 const char *reserve_ref) 375 { 376 struct ReservePurseState *ds; 377 json_error_t err; 378 379 ds = GNUNET_new (struct ReservePurseState); 380 ds->expiration_rel = expiration; 381 ds->contract_terms = json_loads (contract_terms, 382 0 /* flags */, 383 &err); 384 GNUNET_assert (NULL != ds->contract_terms); 385 ds->pay_purse_fee = pay_purse_fee; 386 ds->reserve_ref = reserve_ref; 387 ds->expected_response_code = expected_http_status; 388 389 { 390 struct TALER_TESTING_Command cmd = { 391 .cls = ds, 392 .label = label, 393 .run = &purse_run, 394 .cleanup = &purse_cleanup, 395 .traits = &purse_traits 396 }; 397 398 return cmd; 399 } 400 } 401 402 403 /* end of testing_api_cmd_reserve_purse.c */