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