testing_api_cmd_auditor_deposit_confirmation.c (13421B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2018-2023 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_auditor_deposit_confirmation.c 21 * @brief command for testing /deposit_confirmation. 22 * @author Christian Grothoff 23 */ 24 #include "taler/platform.h" 25 #include "taler/taler_json_lib.h" 26 #include <gnunet/gnunet_curl_lib.h> 27 #include "taler/taler_auditor_service.h" 28 #include "taler/taler_testing_lib.h" 29 #include "taler/taler_signatures.h" 30 #include "taler/backoff.h" 31 32 /** 33 * How long do we wait AT MOST when retrying? 34 */ 35 #define MAX_BACKOFF GNUNET_TIME_relative_multiply ( \ 36 GNUNET_TIME_UNIT_MILLISECONDS, 100) 37 38 /** 39 * How often do we retry before giving up? 40 */ 41 #define NUM_RETRIES 5 42 43 44 /** 45 * State for a "deposit confirmation" CMD. 46 */ 47 struct DepositConfirmationState 48 { 49 50 /** 51 * Reference to any command that is able to provide a deposit. 52 */ 53 const char *deposit_reference; 54 55 /** 56 * What is the deposited amount without the fee (i.e. the 57 * amount we expect in the deposit confirmation)? 58 */ 59 const char *amount_without_fee; 60 61 /** 62 * How many coins were there in the @e deposit_reference? 63 */ 64 unsigned int num_coins; 65 66 /** 67 * DepositConfirmation handle while operation is running. 68 */ 69 struct TALER_AUDITOR_DepositConfirmationHandle *dc; 70 71 /** 72 * Interpreter state. 73 */ 74 struct TALER_TESTING_Interpreter *is; 75 76 /** 77 * Task scheduled to try later. 78 */ 79 struct GNUNET_SCHEDULER_Task *retry_task; 80 81 /** 82 * How long do we wait until we retry? 83 */ 84 struct GNUNET_TIME_Relative backoff; 85 86 /** 87 * Expected HTTP response code. 88 */ 89 unsigned int expected_response_code; 90 91 /** 92 * How often should we retry on (transient) failures? 93 */ 94 unsigned int do_retry; 95 96 }; 97 98 99 /** 100 * Run the command. 101 * 102 * @param cls closure. 103 * @param cmd the command to execute. 104 * @param is the interpreter state. 105 */ 106 static void 107 deposit_confirmation_run (void *cls, 108 const struct TALER_TESTING_Command *cmd, 109 struct TALER_TESTING_Interpreter *is); 110 111 112 /** 113 * Task scheduled to re-try #deposit_confirmation_run. 114 * 115 * @param cls a `struct DepositConfirmationState` 116 */ 117 static void 118 do_retry (void *cls) 119 { 120 struct DepositConfirmationState *dcs = cls; 121 122 dcs->retry_task = NULL; 123 TALER_TESTING_touch_cmd (dcs->is); 124 deposit_confirmation_run (dcs, 125 NULL, 126 dcs->is); 127 } 128 129 130 /** 131 * Callback to analyze the /deposit-confirmation response, just used 132 * to check if the response code is acceptable. 133 * 134 * @param cls closure. 135 * @param dcr response details 136 */ 137 static void 138 deposit_confirmation_cb ( 139 void *cls, 140 const struct TALER_AUDITOR_DepositConfirmationResponse *dcr) 141 { 142 struct DepositConfirmationState *dcs = cls; 143 const struct TALER_AUDITOR_HttpResponse *hr = &dcr->hr; 144 145 dcs->dc = NULL; 146 if (dcs->expected_response_code != hr->http_status) 147 { 148 if (0 != dcs->do_retry) 149 { 150 dcs->do_retry--; 151 if ( (0 == hr->http_status) || 152 (TALER_EC_GENERIC_DB_SOFT_FAILURE == hr->ec) || 153 (MHD_HTTP_INTERNAL_SERVER_ERROR == hr->http_status) ) 154 { 155 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 156 "Retrying deposit confirmation failed with %u/%d\n", 157 hr->http_status, 158 (int) hr->ec); 159 /* on DB conflicts, do not use backoff */ 160 if (TALER_EC_GENERIC_DB_SOFT_FAILURE == hr->ec) 161 dcs->backoff = GNUNET_TIME_UNIT_ZERO; 162 else 163 dcs->backoff = GNUNET_TIME_randomized_backoff (dcs->backoff, 164 MAX_BACKOFF); 165 TALER_TESTING_inc_tries (dcs->is); 166 dcs->retry_task = GNUNET_SCHEDULER_add_delayed (dcs->backoff, 167 &do_retry, 168 dcs); 169 return; 170 } 171 } 172 TALER_TESTING_unexpected_status (dcs->is, 173 hr->http_status, 174 dcs->expected_response_code); 175 return; 176 } 177 TALER_TESTING_interpreter_next (dcs->is); 178 } 179 180 181 /** 182 * Run the command. 183 * 184 * @param cls closure. 185 * @param cmd the command to execute. 186 * @param is the interpreter state. 187 */ 188 static void 189 deposit_confirmation_run (void *cls, 190 const struct TALER_TESTING_Command *cmd, 191 struct TALER_TESTING_Interpreter *is) 192 { 193 static struct TALER_ExtensionPolicyHashP no_h_policy; 194 struct DepositConfirmationState *dcs = cls; 195 const struct TALER_TESTING_Command *deposit_cmd; 196 struct TALER_MerchantWireHashP h_wire; 197 struct TALER_PrivateContractHashP h_contract_terms; 198 const struct GNUNET_TIME_Timestamp *exchange_timestamp = NULL; 199 struct GNUNET_TIME_Timestamp timestamp; 200 const struct GNUNET_TIME_Timestamp *wire_deadline; 201 struct GNUNET_TIME_Timestamp refund_deadline 202 = GNUNET_TIME_UNIT_ZERO_TS; 203 struct TALER_Amount amount_without_fee; 204 struct TALER_CoinSpendPublicKeyP coin_pubs[dcs->num_coins]; 205 const struct TALER_CoinSpendPublicKeyP *coin_pubps[dcs->num_coins]; 206 const struct TALER_CoinSpendSignatureP *coin_sigps[dcs->num_coins]; 207 const struct TALER_MerchantPrivateKeyP *merchant_priv; 208 struct TALER_MerchantPublicKeyP merchant_pub; 209 const struct TALER_ExchangePublicKeyP *exchange_pub; 210 const struct TALER_ExchangeSignatureP *exchange_sig; 211 const json_t *wire_details; 212 const json_t *contract_terms; 213 const struct TALER_EXCHANGE_Keys *keys; 214 const struct TALER_EXCHANGE_SigningPublicKey *spk; 215 const char *auditor_url; 216 217 (void) cmd; 218 dcs->is = is; 219 GNUNET_assert (NULL != dcs->deposit_reference); 220 { 221 const struct TALER_TESTING_Command *auditor_cmd; 222 223 auditor_cmd 224 = TALER_TESTING_interpreter_get_command (is, 225 "auditor"); 226 if (NULL == auditor_cmd) 227 { 228 GNUNET_break (0); 229 TALER_TESTING_interpreter_fail (is); 230 return; 231 } 232 if (GNUNET_OK != 233 TALER_TESTING_get_trait_auditor_url (auditor_cmd, 234 &auditor_url)) 235 { 236 GNUNET_break (0); 237 TALER_TESTING_interpreter_fail (is); 238 return; 239 } 240 } 241 deposit_cmd 242 = TALER_TESTING_interpreter_lookup_command (is, 243 dcs->deposit_reference); 244 if (NULL == deposit_cmd) 245 { 246 GNUNET_break (0); 247 TALER_TESTING_interpreter_fail (is); 248 return; 249 } 250 251 GNUNET_assert (GNUNET_OK == 252 TALER_TESTING_get_trait_exchange_pub (deposit_cmd, 253 0, 254 &exchange_pub)); 255 GNUNET_assert (GNUNET_OK == 256 TALER_TESTING_get_trait_exchange_sig (deposit_cmd, 257 0, 258 &exchange_sig)); 259 GNUNET_assert (GNUNET_OK == 260 TALER_TESTING_get_trait_timestamp (deposit_cmd, 261 0, 262 &exchange_timestamp)); 263 GNUNET_assert (GNUNET_OK == 264 TALER_TESTING_get_trait_wire_deadline (deposit_cmd, 265 0, 266 &wire_deadline)); 267 GNUNET_assert (NULL != exchange_timestamp); 268 keys = TALER_TESTING_get_keys (is); 269 GNUNET_assert (NULL != keys); 270 spk = TALER_EXCHANGE_get_signing_key_info (keys, 271 exchange_pub); 272 273 GNUNET_assert (GNUNET_OK == 274 TALER_TESTING_get_trait_contract_terms (deposit_cmd, 275 &contract_terms)); 276 /* Very unlikely to fail */ 277 GNUNET_assert (NULL != contract_terms); 278 GNUNET_assert (GNUNET_OK == 279 TALER_JSON_contract_hash (contract_terms, 280 &h_contract_terms)); 281 GNUNET_assert (GNUNET_OK == 282 TALER_TESTING_get_trait_wire_details (deposit_cmd, 283 &wire_details)); 284 GNUNET_assert (GNUNET_OK == 285 TALER_JSON_merchant_wire_signature_hash (wire_details, 286 &h_wire)); 287 288 for (unsigned int i = 0; i<dcs->num_coins; i++) 289 { 290 const struct TALER_CoinSpendPrivateKeyP *coin_priv; 291 292 GNUNET_assert (GNUNET_OK == 293 TALER_TESTING_get_trait_coin_priv (deposit_cmd, 294 i, 295 &coin_priv)); 296 GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, 297 &coin_pubs[i].eddsa_pub); 298 coin_pubps[i] = &coin_pubs[i]; 299 GNUNET_assert (GNUNET_OK == 300 TALER_TESTING_get_trait_coin_sig (deposit_cmd, 301 i, 302 &coin_sigps[i])); 303 } 304 GNUNET_assert (GNUNET_OK == 305 TALER_TESTING_get_trait_merchant_priv (deposit_cmd, 306 &merchant_priv)); 307 GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv->eddsa_priv, 308 &merchant_pub.eddsa_pub); 309 GNUNET_assert (GNUNET_OK == 310 TALER_string_to_amount (dcs->amount_without_fee, 311 &amount_without_fee)); 312 { 313 struct GNUNET_JSON_Specification spec[] = { 314 /* timestamp is mandatory */ 315 GNUNET_JSON_spec_timestamp ("timestamp", 316 ×tamp), 317 GNUNET_JSON_spec_mark_optional ( 318 GNUNET_JSON_spec_timestamp ("refund_deadline", 319 &refund_deadline), 320 NULL), 321 GNUNET_JSON_spec_end () 322 }; 323 324 if (GNUNET_OK != 325 GNUNET_JSON_parse (contract_terms, 326 spec, 327 NULL, NULL)) 328 { 329 GNUNET_break (0); 330 TALER_TESTING_interpreter_fail (is); 331 return; 332 } 333 if (GNUNET_TIME_absolute_is_zero (refund_deadline.abs_time)) 334 refund_deadline = timestamp; 335 } 336 dcs->dc = TALER_AUDITOR_deposit_confirmation ( 337 TALER_TESTING_interpreter_get_context (is), 338 auditor_url, 339 &h_wire, 340 &no_h_policy, 341 &h_contract_terms, 342 *exchange_timestamp, 343 *wire_deadline, 344 refund_deadline, 345 &amount_without_fee, 346 dcs->num_coins, 347 coin_pubps, 348 coin_sigps, 349 &merchant_pub, 350 exchange_pub, 351 exchange_sig, 352 &keys->master_pub, 353 spk->valid_from, 354 spk->valid_until, 355 spk->valid_legal, 356 &spk->master_sig, 357 &deposit_confirmation_cb, 358 dcs); 359 360 if (NULL == dcs->dc) 361 { 362 GNUNET_break (0); 363 TALER_TESTING_interpreter_fail (is); 364 return; 365 } 366 return; 367 } 368 369 370 /** 371 * Free the state of a "deposit_confirmation" CMD, and possibly cancel a 372 * pending operation thereof. 373 * 374 * @param cls closure, a `struct DepositConfirmationState` 375 * @param cmd the command which is being cleaned up. 376 */ 377 static void 378 deposit_confirmation_cleanup (void *cls, 379 const struct TALER_TESTING_Command *cmd) 380 { 381 struct DepositConfirmationState *dcs = cls; 382 383 if (NULL != dcs->dc) 384 { 385 TALER_TESTING_command_incomplete (dcs->is, 386 cmd->label); 387 TALER_AUDITOR_deposit_confirmation_cancel (dcs->dc); 388 dcs->dc = NULL; 389 } 390 if (NULL != dcs->retry_task) 391 { 392 GNUNET_SCHEDULER_cancel (dcs->retry_task); 393 dcs->retry_task = NULL; 394 } 395 GNUNET_free (dcs); 396 } 397 398 399 struct TALER_TESTING_Command 400 TALER_TESTING_cmd_deposit_confirmation (const char *label, 401 const char *deposit_reference, 402 unsigned int num_coins, 403 const char *amount_without_fee, 404 unsigned int expected_response_code) 405 { 406 struct DepositConfirmationState *dcs; 407 408 dcs = GNUNET_new (struct DepositConfirmationState); 409 dcs->deposit_reference = deposit_reference; 410 dcs->num_coins = num_coins; 411 dcs->amount_without_fee = amount_without_fee; 412 dcs->expected_response_code = expected_response_code; 413 414 { 415 struct TALER_TESTING_Command cmd = { 416 .cls = dcs, 417 .label = label, 418 .run = &deposit_confirmation_run, 419 .cleanup = &deposit_confirmation_cleanup 420 }; 421 422 return cmd; 423 } 424 } 425 426 427 struct TALER_TESTING_Command 428 TALER_TESTING_cmd_deposit_confirmation_with_retry ( 429 struct TALER_TESTING_Command cmd) 430 { 431 struct DepositConfirmationState *dcs; 432 433 GNUNET_assert (&deposit_confirmation_run == cmd.run); 434 dcs = cmd.cls; 435 dcs->do_retry = NUM_RETRIES; 436 return cmd; 437 } 438 439 440 /* end of testing_auditor_api_cmd_deposit_confirmation.c */