testing_api_cmd_purse_merge.c (11971B)
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_purse_merge.c 21 * @brief command for testing /purses/$PID/merge 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 deposit" CMD. 34 */ 35 struct PurseMergeState 36 { 37 38 /** 39 * Merge time. 40 */ 41 struct GNUNET_TIME_Timestamp merge_timestamp; 42 43 /** 44 * Reserve public key (to be merged into) 45 */ 46 struct TALER_ReservePublicKeyP reserve_pub; 47 48 /** 49 * Reserve private key (useful especially if 50 * @e reserve_ref is NULL). 51 */ 52 struct TALER_ReservePrivateKeyP reserve_priv; 53 54 /** 55 * Handle while operation is running. 56 */ 57 struct TALER_EXCHANGE_AccountMergeHandle *dh; 58 59 /** 60 * Reference to the merge capability. 61 */ 62 const char *merge_ref; 63 64 /** 65 * Reference to the reserve, or NULL (!). 66 */ 67 const char *reserve_ref; 68 69 /** 70 * Interpreter state. 71 */ 72 struct TALER_TESTING_Interpreter *is; 73 74 /** 75 * Hash of the payto://-URI for the reserve we are 76 * merging into. 77 */ 78 struct TALER_NormalizedPaytoHashP h_payto; 79 80 /** 81 * Set to the KYC requirement row *if* the exchange replied with 82 * a request for KYC. 83 */ 84 uint64_t requirement_row; 85 86 /** 87 * Reserve history entry that corresponds to this operation. 88 * Will be of type #TALER_EXCHANGE_RTT_MERGE. 89 */ 90 struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history; 91 92 /** 93 * Public key of the purse. 94 */ 95 struct TALER_PurseContractPublicKeyP purse_pub; 96 97 /** 98 * Public key of the merge capability. 99 */ 100 struct TALER_PurseMergePublicKeyP merge_pub; 101 102 /** 103 * Contract value. 104 */ 105 struct TALER_Amount value_after_fees; 106 107 /** 108 * Hash of the contract. 109 */ 110 struct TALER_PrivateContractHashP h_contract_terms; 111 112 /** 113 * When does the purse expire. 114 */ 115 struct GNUNET_TIME_Timestamp purse_expiration; 116 117 /** 118 * Minimum age of deposits into the purse. 119 */ 120 uint32_t min_age; 121 122 /** 123 * Expected HTTP response code. 124 */ 125 unsigned int expected_response_code; 126 127 }; 128 129 130 /** 131 * Callback to analyze the /purses/$PID/merge response, just used to check if 132 * the response code is acceptable. 133 * 134 * @param cls closure. 135 * @param dr merge response details 136 */ 137 static void 138 merge_cb (void *cls, 139 const struct TALER_EXCHANGE_AccountMergeResponse *dr) 140 { 141 struct PurseMergeState *ds = cls; 142 143 ds->dh = NULL; 144 switch (dr->hr.http_status) 145 { 146 case MHD_HTTP_OK: 147 ds->reserve_history.type = TALER_EXCHANGE_RTT_MERGE; 148 ds->reserve_history.amount = ds->value_after_fees; 149 GNUNET_assert (GNUNET_OK == 150 TALER_amount_set_zero ( 151 ds->value_after_fees.currency, 152 &ds->reserve_history.details.merge_details.purse_fee)); 153 ds->reserve_history.details.merge_details.h_contract_terms 154 = ds->h_contract_terms; 155 ds->reserve_history.details.merge_details.merge_pub 156 = ds->merge_pub; 157 ds->reserve_history.details.merge_details.purse_pub 158 = ds->purse_pub; 159 ds->reserve_history.details.merge_details.reserve_sig 160 = *dr->reserve_sig; 161 ds->reserve_history.details.merge_details.merge_timestamp 162 = ds->merge_timestamp; 163 ds->reserve_history.details.merge_details.purse_expiration 164 = ds->purse_expiration; 165 ds->reserve_history.details.merge_details.min_age 166 = ds->min_age; 167 ds->reserve_history.details.merge_details.flags 168 = TALER_WAMF_MODE_MERGE_FULLY_PAID_PURSE; 169 break; 170 case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: 171 /* KYC required */ 172 ds->requirement_row = 173 dr->details.unavailable_for_legal_reasons.requirement_row; 174 GNUNET_break (0 == 175 GNUNET_memcmp ( 176 &ds->h_payto, 177 &dr->details.unavailable_for_legal_reasons.h_payto)); 178 break; 179 } 180 181 182 if (ds->expected_response_code != dr->hr.http_status) 183 { 184 TALER_TESTING_unexpected_status (ds->is, 185 dr->hr.http_status, 186 ds->expected_response_code); 187 return; 188 } 189 TALER_TESTING_interpreter_next (ds->is); 190 } 191 192 193 /** 194 * Run the command. 195 * 196 * @param cls closure. 197 * @param cmd the command to execute. 198 * @param is the interpreter state. 199 */ 200 static void 201 merge_run (void *cls, 202 const struct TALER_TESTING_Command *cmd, 203 struct TALER_TESTING_Interpreter *is) 204 { 205 struct PurseMergeState *ds = cls; 206 const struct TALER_PurseMergePrivateKeyP *merge_priv; 207 const json_t *ct; 208 const struct TALER_TESTING_Command *ref; 209 210 (void) cmd; 211 ds->is = is; 212 ref = TALER_TESTING_interpreter_lookup_command (ds->is, 213 ds->merge_ref); 214 GNUNET_assert (NULL != ref); 215 if (GNUNET_OK != 216 TALER_TESTING_get_trait_merge_priv (ref, 217 &merge_priv)) 218 { 219 GNUNET_break (0); 220 TALER_TESTING_interpreter_fail (ds->is); 221 return; 222 } 223 { 224 const struct TALER_PurseContractPublicKeyP *purse_pub; 225 226 if (GNUNET_OK != 227 TALER_TESTING_get_trait_purse_pub (ref, 228 &purse_pub)) 229 { 230 GNUNET_break (0); 231 TALER_TESTING_interpreter_fail (ds->is); 232 return; 233 } 234 ds->purse_pub = *purse_pub; 235 } 236 237 if (GNUNET_OK != 238 TALER_TESTING_get_trait_contract_terms (ref, 239 &ct)) 240 { 241 GNUNET_break (0); 242 TALER_TESTING_interpreter_fail (ds->is); 243 return; 244 } 245 if (GNUNET_OK != 246 TALER_JSON_contract_hash (ct, 247 &ds->h_contract_terms)) 248 { 249 GNUNET_break (0); 250 TALER_TESTING_interpreter_fail (ds->is); 251 return; 252 } 253 { 254 struct GNUNET_JSON_Specification spec[] = { 255 GNUNET_JSON_spec_timestamp ("pay_deadline", 256 &ds->purse_expiration), 257 TALER_JSON_spec_amount_any ("amount", 258 &ds->value_after_fees), 259 GNUNET_JSON_spec_mark_optional ( 260 GNUNET_JSON_spec_uint32 ("minimum_age", 261 &ds->min_age), 262 NULL), 263 GNUNET_JSON_spec_end () 264 }; 265 266 if (GNUNET_OK != 267 GNUNET_JSON_parse (ct, 268 spec, 269 NULL, NULL)) 270 { 271 GNUNET_break (0); 272 TALER_TESTING_interpreter_fail (ds->is); 273 return; 274 } 275 } 276 277 if (NULL == ds->reserve_ref) 278 { 279 GNUNET_CRYPTO_eddsa_key_create (&ds->reserve_priv.eddsa_priv); 280 } 281 else 282 { 283 const struct TALER_ReservePrivateKeyP *rp; 284 285 ref = TALER_TESTING_interpreter_lookup_command (ds->is, 286 ds->reserve_ref); 287 GNUNET_assert (NULL != ref); 288 if (GNUNET_OK != 289 TALER_TESTING_get_trait_reserve_priv (ref, 290 &rp)) 291 { 292 GNUNET_break (0); 293 TALER_TESTING_interpreter_fail (ds->is); 294 return; 295 } 296 ds->reserve_priv = *rp; 297 } 298 GNUNET_CRYPTO_eddsa_key_get_public (&ds->reserve_priv.eddsa_priv, 299 &ds->reserve_pub.eddsa_pub); 300 { 301 struct TALER_NormalizedPayto payto_uri; 302 const char *exchange_url; 303 const struct TALER_TESTING_Command *exchange_cmd; 304 305 exchange_cmd = TALER_TESTING_interpreter_get_command (is, 306 "exchange"); 307 if (NULL == exchange_cmd) 308 { 309 GNUNET_break (0); 310 TALER_TESTING_interpreter_fail (is); 311 return; 312 } 313 GNUNET_assert (GNUNET_OK == 314 TALER_TESTING_get_trait_exchange_url (exchange_cmd, 315 &exchange_url)); 316 payto_uri = TALER_reserve_make_payto (exchange_url, 317 &ds->reserve_pub); 318 TALER_normalized_payto_hash (payto_uri, 319 &ds->h_payto); 320 GNUNET_free (payto_uri.normalized_payto); 321 } 322 GNUNET_CRYPTO_eddsa_key_get_public (&merge_priv->eddsa_priv, 323 &ds->merge_pub.eddsa_pub); 324 ds->merge_timestamp = GNUNET_TIME_timestamp_get (); 325 ds->dh = TALER_EXCHANGE_account_merge ( 326 TALER_TESTING_interpreter_get_context (is), 327 TALER_TESTING_get_exchange_url (is), 328 TALER_TESTING_get_keys (is), 329 NULL, /* no wad */ 330 &ds->reserve_priv, 331 &ds->purse_pub, 332 merge_priv, 333 &ds->h_contract_terms, 334 ds->min_age, 335 &ds->value_after_fees, 336 ds->purse_expiration, 337 ds->merge_timestamp, 338 &merge_cb, 339 ds); 340 if (NULL == ds->dh) 341 { 342 GNUNET_break (0); 343 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 344 "Could not merge purse\n"); 345 TALER_TESTING_interpreter_fail (is); 346 return; 347 } 348 } 349 350 351 /** 352 * Free the state of a "merge" CMD, and possibly cancel a 353 * pending operation thereof. 354 * 355 * @param cls closure, must be a `struct PurseMergeState`. 356 * @param cmd the command which is being cleaned up. 357 */ 358 static void 359 merge_cleanup (void *cls, 360 const struct TALER_TESTING_Command *cmd) 361 { 362 struct PurseMergeState *ds = cls; 363 364 if (NULL != ds->dh) 365 { 366 TALER_TESTING_command_incomplete (ds->is, 367 cmd->label); 368 TALER_EXCHANGE_account_merge_cancel (ds->dh); 369 ds->dh = NULL; 370 } 371 GNUNET_free (ds); 372 } 373 374 375 /** 376 * Offer internal data from a "merge" CMD, to other commands. 377 * 378 * @param cls closure. 379 * @param[out] ret result. 380 * @param trait name of the trait. 381 * @param index index number of the object to offer. 382 * @return #GNUNET_OK on success. 383 */ 384 static enum GNUNET_GenericReturnValue 385 merge_traits (void *cls, 386 const void **ret, 387 const char *trait, 388 unsigned int index) 389 { 390 struct PurseMergeState *ds = cls; 391 struct TALER_TESTING_Trait traits[] = { 392 /* history entry MUST be first due to response code logic below! */ 393 TALER_TESTING_make_trait_reserve_history (0, 394 &ds->reserve_history), 395 TALER_TESTING_make_trait_reserve_pub (&ds->reserve_pub), 396 TALER_TESTING_make_trait_timestamp (0, 397 &ds->merge_timestamp), 398 TALER_TESTING_make_trait_legi_requirement_row (&ds->requirement_row), 399 TALER_TESTING_make_trait_h_normalized_payto (&ds->h_payto), 400 TALER_TESTING_trait_end () 401 }; 402 403 return TALER_TESTING_get_trait ((ds->expected_response_code == MHD_HTTP_OK) 404 ? &traits[0] /* we have reserve history */ 405 : &traits[1], /* skip reserve history */ 406 ret, 407 trait, 408 index); 409 } 410 411 412 struct TALER_TESTING_Command 413 TALER_TESTING_cmd_purse_merge ( 414 const char *label, 415 unsigned int expected_http_status, 416 const char *merge_ref, 417 const char *reserve_ref) 418 { 419 struct PurseMergeState *ds; 420 421 ds = GNUNET_new (struct PurseMergeState); 422 ds->merge_ref = merge_ref; 423 ds->reserve_ref = reserve_ref; 424 ds->expected_response_code = expected_http_status; 425 426 { 427 struct TALER_TESTING_Command cmd = { 428 .cls = ds, 429 .label = label, 430 .run = &merge_run, 431 .cleanup = &merge_cleanup, 432 .traits = &merge_traits 433 }; 434 435 return cmd; 436 } 437 } 438 439 440 /* end of testing_api_cmd_purse_merge.c */