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