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