testing_api_cmd_recoup.c (11975B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2014-2023 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as 7 published by the Free Software Foundation; either version 3, or 8 (at your 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 13 GNU 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_recoup.c 21 * @brief Implement the /recoup test command. 22 * @author Marcello Stanisci 23 */ 24 #include "taler/taler_json_lib.h" 25 #include <gnunet/gnunet_curl_lib.h> 26 struct RecoupState; 27 #define TALER_EXCHANGE_POST_RECOUP_WITHDRAW_RESULT_CLOSURE struct RecoupState 28 #include "taler/exchange/post-recoup-withdraw.h" 29 #include "taler/taler_testing_lib.h" 30 31 32 /** 33 * State for a "pay back" CMD. 34 */ 35 struct RecoupState 36 { 37 /** 38 * Expected HTTP status code. 39 */ 40 unsigned int expected_response_code; 41 42 /** 43 * Command that offers a reserve private key, 44 * plus a coin to be paid back. 45 */ 46 const char *coin_reference; 47 48 /** 49 * The interpreter state. 50 */ 51 struct TALER_TESTING_Interpreter *is; 52 53 /** 54 * Handle to the ongoing operation. 55 */ 56 struct TALER_EXCHANGE_PostRecoupWithdrawHandle *ph; 57 58 /** 59 * If the recoup filled a reserve, this is set to the reserve's public key. 60 */ 61 struct TALER_ReservePublicKeyP reserve_pub; 62 63 /** 64 * Entry in the coin's history generated by this operation. 65 */ 66 struct TALER_EXCHANGE_CoinHistoryEntry che; 67 68 /** 69 * Public key of the refunded coin. 70 */ 71 struct TALER_CoinSpendPublicKeyP coin; 72 73 /** 74 * Reserve history entry, set if this recoup actually filled up a reserve. 75 * Otherwise `reserve_history.type` will be zero. 76 */ 77 struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history; 78 79 }; 80 81 82 /** 83 * Check the result of the recoup request: checks whether 84 * the HTTP response code is good, and that the coin that 85 * was paid back belonged to the right reserve. 86 * 87 * @param ps closure 88 * @param rr response details 89 */ 90 static void 91 recoup_cb (struct RecoupState *ps, 92 const struct TALER_EXCHANGE_PostRecoupWithdrawResponse *rr) 93 { 94 const struct TALER_EXCHANGE_HttpResponse *hr = &rr->hr; 95 struct TALER_TESTING_Interpreter *is = ps->is; 96 const struct TALER_TESTING_Command *reserve_cmd; 97 char *cref; 98 unsigned int idx; 99 100 ps->ph = NULL; 101 if (ps->expected_response_code != hr->http_status) 102 { 103 TALER_TESTING_unexpected_status (is, 104 hr->http_status, 105 ps->expected_response_code); 106 return; 107 } 108 109 if (GNUNET_OK != 110 TALER_TESTING_parse_coin_reference ( 111 ps->coin_reference, 112 &cref, 113 &idx)) 114 { 115 TALER_TESTING_interpreter_fail (is); 116 return; 117 } 118 (void) idx; /* do NOT use! We ignore 'idx', must be 0 for melt! */ 119 120 reserve_cmd = TALER_TESTING_interpreter_lookup_command (is, 121 cref); 122 GNUNET_free (cref); 123 124 if (NULL == reserve_cmd) 125 { 126 GNUNET_break (0); 127 TALER_TESTING_interpreter_fail (is); 128 return; 129 } 130 131 switch (hr->http_status) 132 { 133 case MHD_HTTP_OK: 134 /* check old_coin_pub or reserve_pub, respectively */ 135 { 136 const struct TALER_ReservePrivateKeyP *reserve_priv; 137 138 if (GNUNET_OK != 139 TALER_TESTING_get_trait_reserve_priv (reserve_cmd, 140 &reserve_priv)) 141 { 142 GNUNET_break (0); 143 TALER_TESTING_interpreter_fail (is); 144 return; 145 } 146 GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, 147 &ps->reserve_pub.eddsa_pub); 148 if (0 != GNUNET_memcmp (&rr->details.ok.reserve_pub, 149 &ps->reserve_pub)) 150 { 151 GNUNET_break (0); 152 TALER_TESTING_interpreter_fail (is); 153 return; 154 } 155 if (GNUNET_OK == 156 TALER_amount_is_valid (&ps->reserve_history.amount)) 157 ps->reserve_history.type = TALER_EXCHANGE_RTT_RECOUP; 158 /* ps->reserve_history.details.recoup_details.coin_pub; // initialized earlier */ 159 ps->che.details.recoup.reserve_pub = ps->reserve_pub; 160 } 161 break; 162 case MHD_HTTP_NOT_FOUND: 163 break; 164 case MHD_HTTP_CONFLICT: 165 break; 166 default: 167 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 168 "Unmanaged HTTP status code %u/%d.\n", 169 hr->http_status, 170 (int) hr->ec); 171 break; 172 } 173 TALER_TESTING_interpreter_next (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 recoup_run (void *cls, 186 const struct TALER_TESTING_Command *cmd, 187 struct TALER_TESTING_Interpreter *is) 188 { 189 struct RecoupState *ps = cls; 190 const struct TALER_TESTING_Command *coin_cmd; 191 const struct TALER_CoinSpendPrivateKeyP *coin_priv; 192 const struct TALER_EXCHANGE_DenomPublicKey *denom_pub; 193 const struct TALER_DenominationSignature *coin_sig; 194 const struct TALER_WithdrawMasterSeedP *seed; 195 const struct TALER_BlindingMasterSeedP *blinding_seed; 196 const struct TALER_HashBlindedPlanchetsP *h_planchets; 197 struct TALER_PlanchetMasterSecretP secret; 198 char *cref; 199 unsigned int idx; 200 const struct TALER_ExchangeBlindingValues *ewv; 201 struct TALER_DenominationHashP h_denom_pub; 202 203 ps->is = is; 204 if (GNUNET_OK != 205 TALER_TESTING_parse_coin_reference ( 206 ps->coin_reference, 207 &cref, 208 &idx)) 209 { 210 TALER_TESTING_interpreter_fail (is); 211 return; 212 } 213 214 coin_cmd = TALER_TESTING_interpreter_lookup_command (is, 215 cref); 216 GNUNET_free (cref); 217 218 if (NULL == coin_cmd) 219 { 220 GNUNET_break (0); 221 TALER_TESTING_interpreter_fail (is); 222 return; 223 } 224 if (GNUNET_OK != 225 TALER_TESTING_get_trait_coin_priv (coin_cmd, 226 idx, 227 &coin_priv)) 228 { 229 GNUNET_break (0); 230 TALER_TESTING_interpreter_fail (is); 231 return; 232 } 233 GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, 234 &ps->coin.eddsa_pub); 235 if (GNUNET_OK != 236 TALER_TESTING_get_trait_exchange_blinding_values (coin_cmd, 237 idx, 238 &ewv)) 239 { 240 GNUNET_break (0); 241 TALER_TESTING_interpreter_fail (is); 242 return; 243 } 244 if (GNUNET_OK != 245 TALER_TESTING_get_trait_withdraw_seed (coin_cmd, 246 &seed)) 247 { 248 GNUNET_break (0); 249 TALER_TESTING_interpreter_fail (is); 250 return; 251 } 252 if (GNUNET_OK != 253 TALER_TESTING_get_trait_blinding_seed (coin_cmd, 254 &blinding_seed)) 255 { 256 GNUNET_break (0); 257 TALER_TESTING_interpreter_fail (is); 258 return; 259 } 260 GNUNET_CRYPTO_eddsa_key_get_public ( 261 &coin_priv->eddsa_priv, 262 &ps->reserve_history.details.recoup_details.coin_pub.eddsa_pub); 263 264 if (GNUNET_OK != 265 TALER_TESTING_get_trait_denom_pub (coin_cmd, 266 idx, 267 &denom_pub)) 268 { 269 GNUNET_break (0); 270 TALER_TESTING_interpreter_fail (is); 271 return; 272 } 273 if (GNUNET_OK != 274 TALER_TESTING_get_trait_denom_sig (coin_cmd, 275 idx, 276 &coin_sig)) 277 { 278 GNUNET_break (0); 279 TALER_TESTING_interpreter_fail (is); 280 return; 281 } 282 if (GNUNET_OK != 283 TALER_TESTING_get_trait_withdraw_commitment (coin_cmd, 284 &h_planchets)) 285 { 286 GNUNET_break (0); 287 TALER_TESTING_interpreter_fail (is); 288 return; 289 } 290 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 291 "Trying to recoup denomination '%s'\n", 292 TALER_B2S (&denom_pub->h_key)); 293 ps->che.type = TALER_EXCHANGE_CTT_RECOUP; 294 ps->che.amount = ps->reserve_history.amount; 295 TALER_withdraw_expand_secrets (1, 296 seed, 297 &secret); 298 TALER_planchet_blinding_secret_create (&secret, 299 ewv, 300 &ps->che.details.recoup.coin_bks); 301 TALER_denom_pub_hash (&denom_pub->key, 302 &h_denom_pub); 303 TALER_wallet_recoup_sign (&h_denom_pub, 304 &ps->che.details.recoup.coin_bks, 305 coin_priv, 306 &ps->che.details.recoup.coin_sig); 307 ps->ph = TALER_EXCHANGE_post_recoup_withdraw_create ( 308 TALER_TESTING_interpreter_get_context (is), 309 TALER_TESTING_get_exchange_url (is), 310 TALER_TESTING_get_keys (is), 311 denom_pub, 312 coin_sig, 313 ewv, 314 blinding_seed, 315 idx, 316 &secret, 317 h_planchets); 318 GNUNET_assert (NULL != ps->ph); 319 GNUNET_assert (TALER_EC_NONE == 320 TALER_EXCHANGE_post_recoup_withdraw_start (ps->ph, 321 &recoup_cb, 322 ps)); 323 } 324 325 326 /** 327 * Cleanup the "recoup" CMD state, and possibly cancel 328 * a pending operation thereof. 329 * 330 * @param cls closure. 331 * @param cmd the command which is being cleaned up. 332 */ 333 static void 334 recoup_cleanup (void *cls, 335 const struct TALER_TESTING_Command *cmd) 336 { 337 struct RecoupState *ps = cls; 338 if (NULL != ps->ph) 339 { 340 TALER_EXCHANGE_post_recoup_withdraw_cancel (ps->ph); 341 ps->ph = NULL; 342 } 343 GNUNET_free (ps); 344 } 345 346 347 /** 348 * Offer internal data from a "recoup" CMD state to other 349 * commands. 350 * 351 * @param cls closure 352 * @param[out] ret result (could be anything) 353 * @param trait name of the trait 354 * @param index index number of the object to offer. 355 * @return #GNUNET_OK on success 356 */ 357 static enum GNUNET_GenericReturnValue 358 recoup_traits (void *cls, 359 const void **ret, 360 const char *trait, 361 unsigned int index) 362 { 363 struct RecoupState *ps = cls; 364 365 if (ps->reserve_history.type != TALER_EXCHANGE_RTT_RECOUP) 366 return GNUNET_SYSERR; /* no traits */ 367 { 368 struct TALER_TESTING_Trait traits[] = { 369 TALER_TESTING_make_trait_reserve_pub (&ps->reserve_pub), 370 TALER_TESTING_make_trait_reserve_history (0, 371 &ps->reserve_history), 372 TALER_TESTING_make_trait_coin_history (0, 373 &ps->che), 374 TALER_TESTING_make_trait_coin_pub (0, 375 &ps->coin), 376 TALER_TESTING_trait_end () 377 }; 378 379 return TALER_TESTING_get_trait (traits, 380 ret, 381 trait, 382 index); 383 } 384 } 385 386 387 struct TALER_TESTING_Command 388 TALER_TESTING_cmd_recoup (const char *label, 389 unsigned int expected_response_code, 390 const char *coin_reference, 391 const char *amount) 392 { 393 struct RecoupState *ps; 394 395 ps = GNUNET_new (struct RecoupState); 396 ps->expected_response_code = expected_response_code; 397 ps->coin_reference = coin_reference; 398 if (GNUNET_OK != 399 TALER_string_to_amount (amount, 400 &ps->reserve_history.amount)) 401 { 402 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 403 "Failed to parse amount `%s' at %s\n", 404 amount, 405 label); 406 GNUNET_assert (0); 407 } 408 { 409 struct TALER_TESTING_Command cmd = { 410 .cls = ps, 411 .label = label, 412 .run = &recoup_run, 413 .cleanup = &recoup_cleanup, 414 .traits = &recoup_traits 415 }; 416 417 return cmd; 418 } 419 }