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