testing_api_cmd_recoup.c (11671B)
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_HashBlindedPlanchetsP *h_planchets; 196 struct TALER_PlanchetMasterSecretP secret; 197 char *cref; 198 unsigned int idx; 199 const struct TALER_ExchangeBlindingValues *ewv; 200 struct TALER_DenominationHashP h_denom_pub; 201 202 ps->is = is; 203 if (GNUNET_OK != 204 TALER_TESTING_parse_coin_reference ( 205 ps->coin_reference, 206 &cref, 207 &idx)) 208 { 209 TALER_TESTING_interpreter_fail (is); 210 return; 211 } 212 213 coin_cmd = TALER_TESTING_interpreter_lookup_command (is, 214 cref); 215 GNUNET_free (cref); 216 217 if (NULL == coin_cmd) 218 { 219 GNUNET_break (0); 220 TALER_TESTING_interpreter_fail (is); 221 return; 222 } 223 if (GNUNET_OK != 224 TALER_TESTING_get_trait_coin_priv (coin_cmd, 225 idx, 226 &coin_priv)) 227 { 228 GNUNET_break (0); 229 TALER_TESTING_interpreter_fail (is); 230 return; 231 } 232 GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, 233 &ps->coin.eddsa_pub); 234 if (GNUNET_OK != 235 TALER_TESTING_get_trait_exchange_blinding_values (coin_cmd, 236 idx, 237 &ewv)) 238 { 239 GNUNET_break (0); 240 TALER_TESTING_interpreter_fail (is); 241 return; 242 } 243 if (GNUNET_OK != 244 TALER_TESTING_get_trait_withdraw_seed (coin_cmd, 245 &seed)) 246 { 247 GNUNET_break (0); 248 TALER_TESTING_interpreter_fail (is); 249 return; 250 } 251 GNUNET_CRYPTO_eddsa_key_get_public ( 252 &coin_priv->eddsa_priv, 253 &ps->reserve_history.details.recoup_details.coin_pub.eddsa_pub); 254 255 if (GNUNET_OK != 256 TALER_TESTING_get_trait_denom_pub (coin_cmd, 257 idx, 258 &denom_pub)) 259 { 260 GNUNET_break (0); 261 TALER_TESTING_interpreter_fail (is); 262 return; 263 } 264 if (GNUNET_OK != 265 TALER_TESTING_get_trait_denom_sig (coin_cmd, 266 idx, 267 &coin_sig)) 268 { 269 GNUNET_break (0); 270 TALER_TESTING_interpreter_fail (is); 271 return; 272 } 273 if (GNUNET_OK != 274 TALER_TESTING_get_trait_withdraw_commitment (coin_cmd, 275 &h_planchets)) 276 { 277 GNUNET_break (0); 278 TALER_TESTING_interpreter_fail (is); 279 return; 280 } 281 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 282 "Trying to recoup denomination '%s'\n", 283 TALER_B2S (&denom_pub->h_key)); 284 ps->che.type = TALER_EXCHANGE_CTT_RECOUP; 285 ps->che.amount = ps->reserve_history.amount; 286 TALER_withdraw_expand_secrets (1, 287 seed, 288 &secret); 289 TALER_planchet_blinding_secret_create (&secret, 290 ewv, 291 &ps->che.details.recoup.coin_bks); 292 TALER_denom_pub_hash (&denom_pub->key, 293 &h_denom_pub); 294 TALER_wallet_recoup_sign (&h_denom_pub, 295 &ps->che.details.recoup.coin_bks, 296 coin_priv, 297 &ps->che.details.recoup.coin_sig); 298 ps->ph = TALER_EXCHANGE_post_recoup_withdraw_create ( 299 TALER_TESTING_interpreter_get_context (is), 300 TALER_TESTING_get_exchange_url (is), 301 TALER_TESTING_get_keys (is), 302 denom_pub, 303 coin_sig, 304 ewv, 305 &secret, 306 h_planchets); 307 GNUNET_assert (NULL != ps->ph); 308 GNUNET_assert (TALER_EC_NONE == 309 TALER_EXCHANGE_post_recoup_withdraw_start (ps->ph, 310 &recoup_cb, 311 ps)); 312 } 313 314 315 /** 316 * Cleanup the "recoup" CMD state, and possibly cancel 317 * a pending operation thereof. 318 * 319 * @param cls closure. 320 * @param cmd the command which is being cleaned up. 321 */ 322 static void 323 recoup_cleanup (void *cls, 324 const struct TALER_TESTING_Command *cmd) 325 { 326 struct RecoupState *ps = cls; 327 if (NULL != ps->ph) 328 { 329 TALER_EXCHANGE_post_recoup_withdraw_cancel (ps->ph); 330 ps->ph = NULL; 331 } 332 GNUNET_free (ps); 333 } 334 335 336 /** 337 * Offer internal data from a "recoup" CMD state to other 338 * commands. 339 * 340 * @param cls closure 341 * @param[out] ret result (could be anything) 342 * @param trait name of the trait 343 * @param index index number of the object to offer. 344 * @return #GNUNET_OK on success 345 */ 346 static enum GNUNET_GenericReturnValue 347 recoup_traits (void *cls, 348 const void **ret, 349 const char *trait, 350 unsigned int index) 351 { 352 struct RecoupState *ps = cls; 353 354 if (ps->reserve_history.type != TALER_EXCHANGE_RTT_RECOUP) 355 return GNUNET_SYSERR; /* no traits */ 356 { 357 struct TALER_TESTING_Trait traits[] = { 358 TALER_TESTING_make_trait_reserve_pub (&ps->reserve_pub), 359 TALER_TESTING_make_trait_reserve_history (0, 360 &ps->reserve_history), 361 TALER_TESTING_make_trait_coin_history (0, 362 &ps->che), 363 TALER_TESTING_make_trait_coin_pub (0, 364 &ps->coin), 365 TALER_TESTING_trait_end () 366 }; 367 368 return TALER_TESTING_get_trait (traits, 369 ret, 370 trait, 371 index); 372 } 373 } 374 375 376 struct TALER_TESTING_Command 377 TALER_TESTING_cmd_recoup (const char *label, 378 unsigned int expected_response_code, 379 const char *coin_reference, 380 const char *amount) 381 { 382 struct RecoupState *ps; 383 384 ps = GNUNET_new (struct RecoupState); 385 ps->expected_response_code = expected_response_code; 386 ps->coin_reference = coin_reference; 387 if (GNUNET_OK != 388 TALER_string_to_amount (amount, 389 &ps->reserve_history.amount)) 390 { 391 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 392 "Failed to parse amount `%s' at %s\n", 393 amount, 394 label); 395 GNUNET_assert (0); 396 } 397 { 398 struct TALER_TESTING_Command cmd = { 399 .cls = ps, 400 .label = label, 401 .run = &recoup_run, 402 .cleanup = &recoup_cleanup, 403 .traits = &recoup_traits 404 }; 405 406 return cmd; 407 } 408 }