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