Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2018-2022 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_withdraw.c
21 : * @brief main interpreter loop for testcases
22 : * @author Christian Grothoff
23 : * @author Marcello Stanisci
24 : */
25 : #include "platform.h"
26 : #include "taler_json_lib.h"
27 : #include <microhttpd.h>
28 : #include <gnunet/gnunet_curl_lib.h>
29 : #include "taler_signatures.h"
30 : #include "taler_extensions.h"
31 : #include "taler_testing_lib.h"
32 : #include "backoff.h"
33 :
34 :
35 : /**
36 : * How often do we retry before giving up?
37 : */
38 : #define NUM_RETRIES 15
39 :
40 : /**
41 : * How long do we wait AT LEAST if the exchange says the reserve is unknown?
42 : */
43 : #define UNKNOWN_MIN_BACKOFF GNUNET_TIME_relative_multiply ( \
44 : GNUNET_TIME_UNIT_MILLISECONDS, 10)
45 :
46 : /**
47 : * How long do we wait AT MOST if the exchange says the reserve is unknown?
48 : */
49 : #define UNKNOWN_MAX_BACKOFF GNUNET_TIME_relative_multiply ( \
50 : GNUNET_TIME_UNIT_MILLISECONDS, 100)
51 :
52 : /**
53 : * State for a "withdraw" CMD.
54 : */
55 : struct WithdrawState
56 : {
57 :
58 : /**
59 : * Which reserve should we withdraw from?
60 : */
61 : const char *reserve_reference;
62 :
63 : /**
64 : * Reference to a withdraw or reveal operation from which we should
65 : * re-use the private coin key, or NULL for regular withdrawal.
66 : */
67 : const char *reuse_coin_key_ref;
68 :
69 : /**
70 : * String describing the denomination value we should withdraw.
71 : * A corresponding denomination key must exist in the exchange's
72 : * offerings. Can be NULL if @e pk is set instead.
73 : */
74 : struct TALER_Amount amount;
75 :
76 : /**
77 : * If @e amount is NULL, this specifies the denomination key to
78 : * use. Otherwise, this will be set (by the interpreter) to the
79 : * denomination PK matching @e amount.
80 : */
81 : struct TALER_EXCHANGE_DenomPublicKey *pk;
82 :
83 : /**
84 : * Exchange base URL. Only used as offered trait.
85 : */
86 : char *exchange_url;
87 :
88 : /**
89 : * URI if the reserve we are withdrawing from.
90 : */
91 : char *reserve_payto_uri;
92 :
93 : /**
94 : * Private key of the reserve we are withdrawing from.
95 : */
96 : struct TALER_ReservePrivateKeyP reserve_priv;
97 :
98 : /**
99 : * Public key of the reserve we are withdrawing from.
100 : */
101 : struct TALER_ReservePublicKeyP reserve_pub;
102 :
103 : /**
104 : * Private key of the coin.
105 : */
106 : struct TALER_CoinSpendPrivateKeyP coin_priv;
107 :
108 : /**
109 : * Blinding key used during the operation.
110 : */
111 : union TALER_DenominationBlindingKeyP bks;
112 :
113 : /**
114 : * Values contributed from the exchange during the
115 : * withdraw protocol.
116 : */
117 : struct TALER_ExchangeWithdrawValues exchange_vals;
118 :
119 : /**
120 : * Interpreter state (during command).
121 : */
122 : struct TALER_TESTING_Interpreter *is;
123 :
124 : /**
125 : * Set (by the interpreter) to the exchange's signature over the
126 : * coin's public key.
127 : */
128 : struct TALER_DenominationSignature sig;
129 :
130 : /**
131 : * Private key material of the coin, set by the interpreter.
132 : */
133 : struct TALER_PlanchetMasterSecretP ps;
134 :
135 : /**
136 : * An age > 0 signifies age restriction is required
137 : */
138 : uint8_t age;
139 :
140 : /**
141 : * If age > 0, put here the corresponding age commitment with its proof and
142 : * its hash, respectivelly, NULL otherwise.
143 : */
144 : struct TALER_AgeCommitmentProof *age_commitment_proof;
145 : struct TALER_AgeCommitmentHash *h_age_commitment;
146 :
147 : /**
148 : * Reserve history entry that corresponds to this operation.
149 : * Will be of type #TALER_EXCHANGE_RTT_WITHDRAWAL.
150 : */
151 : struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history;
152 :
153 : /**
154 : * Withdraw handle (while operation is running).
155 : */
156 : struct TALER_EXCHANGE_WithdrawHandle *wsh;
157 :
158 : /**
159 : * Task scheduled to try later.
160 : */
161 : struct GNUNET_SCHEDULER_Task *retry_task;
162 :
163 : /**
164 : * How long do we wait until we retry?
165 : */
166 : struct GNUNET_TIME_Relative backoff;
167 :
168 : /**
169 : * Total withdraw backoff applied.
170 : */
171 : struct GNUNET_TIME_Relative total_backoff;
172 :
173 : /**
174 : * Set to the KYC requirement payto hash *if* the exchange replied with a
175 : * request for KYC.
176 : */
177 : struct TALER_PaytoHashP h_payto;
178 :
179 : /**
180 : * Set to the KYC requirement row *if* the exchange replied with
181 : * a request for KYC.
182 : */
183 : uint64_t requirement_row;
184 :
185 : /**
186 : * Expected HTTP response code to the request.
187 : */
188 : unsigned int expected_response_code;
189 :
190 : /**
191 : * Was this command modified via
192 : * #TALER_TESTING_cmd_withdraw_with_retry to
193 : * enable retries? How often should we still retry?
194 : */
195 : unsigned int do_retry;
196 : };
197 :
198 :
199 : /**
200 : * Run the command.
201 : *
202 : * @param cls closure.
203 : * @param cmd the commaind being run.
204 : * @param is interpreter state.
205 : */
206 : static void
207 : withdraw_run (void *cls,
208 : const struct TALER_TESTING_Command *cmd,
209 : struct TALER_TESTING_Interpreter *is);
210 :
211 :
212 : /**
213 : * Task scheduled to re-try #withdraw_run.
214 : *
215 : * @param cls a `struct WithdrawState`
216 : */
217 : static void
218 0 : do_retry (void *cls)
219 : {
220 0 : struct WithdrawState *ws = cls;
221 :
222 0 : ws->retry_task = NULL;
223 0 : ws->is->commands[ws->is->ip].last_req_time
224 0 : = GNUNET_TIME_absolute_get ();
225 0 : withdraw_run (ws,
226 : NULL,
227 : ws->is);
228 0 : }
229 :
230 :
231 : /**
232 : * "reserve withdraw" operation callback; checks that the
233 : * response code is expected and store the exchange signature
234 : * in the state.
235 : *
236 : * @param cls closure.
237 : * @param wr withdraw response details
238 : */
239 : static void
240 0 : reserve_withdraw_cb (void *cls,
241 : const struct TALER_EXCHANGE_WithdrawResponse *wr)
242 : {
243 0 : struct WithdrawState *ws = cls;
244 0 : struct TALER_TESTING_Interpreter *is = ws->is;
245 :
246 0 : ws->wsh = NULL;
247 0 : if (ws->expected_response_code != wr->hr.http_status)
248 : {
249 0 : if (0 != ws->do_retry)
250 : {
251 0 : if (TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN != wr->hr.ec)
252 0 : ws->do_retry--; /* we don't count reserve unknown as failures here */
253 0 : if ( (0 == wr->hr.http_status) ||
254 0 : (TALER_EC_GENERIC_DB_SOFT_FAILURE == wr->hr.ec) ||
255 0 : (TALER_EC_EXCHANGE_WITHDRAW_INSUFFICIENT_FUNDS == wr->hr.ec) ||
256 0 : (TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN == wr->hr.ec) ||
257 0 : (MHD_HTTP_INTERNAL_SERVER_ERROR == wr->hr.http_status) )
258 : {
259 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
260 : "Retrying withdraw failed with %u/%d\n",
261 : wr->hr.http_status,
262 : (int) wr->hr.ec);
263 : /* on DB conflicts, do not use backoff */
264 0 : if (TALER_EC_GENERIC_DB_SOFT_FAILURE == wr->hr.ec)
265 0 : ws->backoff = GNUNET_TIME_UNIT_ZERO;
266 0 : else if (TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN != wr->hr.ec)
267 0 : ws->backoff = EXCHANGE_LIB_BACKOFF (ws->backoff);
268 : else
269 0 : ws->backoff = GNUNET_TIME_relative_max (UNKNOWN_MIN_BACKOFF,
270 : ws->backoff);
271 0 : ws->backoff = GNUNET_TIME_relative_min (ws->backoff,
272 : UNKNOWN_MAX_BACKOFF);
273 0 : ws->total_backoff = GNUNET_TIME_relative_add (ws->total_backoff,
274 : ws->backoff);
275 0 : ws->is->commands[ws->is->ip].num_tries++;
276 0 : ws->retry_task = GNUNET_SCHEDULER_add_delayed (ws->backoff,
277 : &do_retry,
278 : ws);
279 0 : return;
280 : }
281 : }
282 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
283 : "Unexpected response code %u/%d to command %s in %s:%u\n",
284 : wr->hr.http_status,
285 : (int) wr->hr.ec,
286 : TALER_TESTING_interpreter_get_current_label (is),
287 : __FILE__,
288 : __LINE__);
289 0 : json_dumpf (wr->hr.reply,
290 : stderr,
291 : 0);
292 0 : GNUNET_break (0);
293 0 : TALER_TESTING_interpreter_fail (is);
294 0 : return;
295 : }
296 0 : switch (wr->hr.http_status)
297 : {
298 0 : case MHD_HTTP_OK:
299 0 : TALER_denom_sig_deep_copy (&ws->sig,
300 : &wr->details.success.sig);
301 0 : ws->coin_priv = wr->details.success.coin_priv;
302 0 : ws->bks = wr->details.success.bks;
303 0 : ws->exchange_vals = wr->details.success.exchange_vals;
304 0 : if (0 != ws->total_backoff.rel_value_us)
305 : {
306 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
307 : "Total withdraw backoff for %s was %s\n",
308 : is->commands[is->ip].label,
309 : GNUNET_STRINGS_relative_time_to_string (ws->total_backoff,
310 : GNUNET_YES));
311 : }
312 0 : break;
313 0 : case MHD_HTTP_FORBIDDEN:
314 : /* nothing to check */
315 0 : break;
316 0 : case MHD_HTTP_NOT_FOUND:
317 : /* nothing to check */
318 0 : break;
319 0 : case MHD_HTTP_CONFLICT:
320 : /* nothing to check */
321 0 : break;
322 0 : case MHD_HTTP_GONE:
323 : /* theoretically could check that the key was actually */
324 0 : break;
325 0 : case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
326 : /* KYC required */
327 0 : ws->requirement_row =
328 0 : wr->details.unavailable_for_legal_reasons.requirement_row;
329 : ws->h_payto
330 0 : = wr->details.unavailable_for_legal_reasons.h_payto;
331 0 : break;
332 0 : default:
333 : /* Unsupported status code (by test harness) */
334 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
335 : "Withdraw test command does not support status code %u\n",
336 : wr->hr.http_status);
337 0 : GNUNET_break (0);
338 0 : break;
339 : }
340 0 : TALER_TESTING_interpreter_next (is);
341 : }
342 :
343 :
344 : /**
345 : * Run the command.
346 : */
347 : static void
348 0 : withdraw_run (void *cls,
349 : const struct TALER_TESTING_Command *cmd,
350 : struct TALER_TESTING_Interpreter *is)
351 : {
352 0 : struct WithdrawState *ws = cls;
353 : const struct TALER_ReservePrivateKeyP *rp;
354 : const struct TALER_TESTING_Command *create_reserve;
355 : const struct TALER_EXCHANGE_DenomPublicKey *dpk;
356 :
357 : (void) cmd;
358 0 : ws->is = is;
359 : create_reserve
360 0 : = TALER_TESTING_interpreter_lookup_command (
361 : is,
362 : ws->reserve_reference);
363 :
364 0 : if (NULL == create_reserve)
365 : {
366 0 : GNUNET_break (0);
367 0 : TALER_TESTING_interpreter_fail (is);
368 0 : return;
369 : }
370 :
371 0 : if (GNUNET_OK !=
372 0 : TALER_TESTING_get_trait_reserve_priv (create_reserve,
373 : &rp))
374 : {
375 0 : GNUNET_break (0);
376 0 : TALER_TESTING_interpreter_fail (is);
377 0 : return;
378 : }
379 :
380 0 : if (NULL == ws->exchange_url)
381 : ws->exchange_url
382 0 : = GNUNET_strdup (TALER_EXCHANGE_get_base_url (is->exchange));
383 0 : ws->reserve_priv = *rp;
384 0 : GNUNET_CRYPTO_eddsa_key_get_public (&ws->reserve_priv.eddsa_priv,
385 : &ws->reserve_pub.eddsa_pub);
386 : ws->reserve_payto_uri
387 0 : = TALER_reserve_make_payto (ws->exchange_url,
388 0 : &ws->reserve_pub);
389 :
390 0 : if (NULL == ws->reuse_coin_key_ref)
391 : {
392 0 : TALER_planchet_master_setup_random (&ws->ps);
393 : }
394 : else
395 : {
396 : const struct TALER_PlanchetMasterSecretP *ps;
397 : const struct TALER_TESTING_Command *cref;
398 : char *cstr;
399 : unsigned int index;
400 :
401 0 : GNUNET_assert (GNUNET_OK ==
402 : TALER_TESTING_parse_coin_reference (
403 : ws->reuse_coin_key_ref,
404 : &cstr,
405 : &index));
406 0 : cref = TALER_TESTING_interpreter_lookup_command (is,
407 : cstr);
408 0 : GNUNET_assert (NULL != cref);
409 0 : GNUNET_free (cstr);
410 0 : GNUNET_assert (GNUNET_OK ==
411 : TALER_TESTING_get_trait_planchet_secret (cref,
412 : &ps));
413 0 : ws->ps = *ps;
414 : }
415 :
416 0 : if (NULL == ws->pk)
417 : {
418 0 : dpk = TALER_TESTING_find_pk (TALER_EXCHANGE_get_keys (is->exchange),
419 0 : &ws->amount,
420 0 : ws->age > 0);
421 0 : if (NULL == dpk)
422 : {
423 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
424 : "Failed to determine denomination key at %s\n",
425 : (NULL != cmd) ? cmd->label : "<retried command>");
426 0 : GNUNET_break (0);
427 0 : TALER_TESTING_interpreter_fail (is);
428 0 : return;
429 : }
430 : /* We copy the denomination key, as re-querying /keys
431 : * would free the old one. */
432 0 : ws->pk = TALER_EXCHANGE_copy_denomination_key (dpk);
433 : }
434 : else
435 : {
436 0 : ws->amount = ws->pk->value;
437 : }
438 :
439 0 : ws->reserve_history.type = TALER_EXCHANGE_RTT_WITHDRAWAL;
440 0 : GNUNET_assert (0 <=
441 : TALER_amount_add (&ws->reserve_history.amount,
442 : &ws->amount,
443 : &ws->pk->fees.withdraw));
444 0 : ws->reserve_history.details.withdraw.fee = ws->pk->fees.withdraw;
445 : {
446 0 : struct TALER_EXCHANGE_WithdrawCoinInput wci = {
447 0 : .pk = ws->pk,
448 0 : .ps = &ws->ps,
449 0 : .ach = ws->h_age_commitment
450 : };
451 0 : ws->wsh = TALER_EXCHANGE_withdraw (is->exchange,
452 : rp,
453 : &wci,
454 : &reserve_withdraw_cb,
455 : ws);
456 : }
457 0 : if (NULL == ws->wsh)
458 : {
459 0 : GNUNET_break (0);
460 0 : TALER_TESTING_interpreter_fail (is);
461 0 : return;
462 : }
463 : }
464 :
465 :
466 : /**
467 : * Free the state of a "withdraw" CMD, and possibly cancel
468 : * a pending operation thereof.
469 : *
470 : * @param cls closure.
471 : * @param cmd the command being freed.
472 : */
473 : static void
474 0 : withdraw_cleanup (void *cls,
475 : const struct TALER_TESTING_Command *cmd)
476 : {
477 0 : struct WithdrawState *ws = cls;
478 :
479 0 : if (NULL != ws->wsh)
480 : {
481 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
482 : "Command %s did not complete\n",
483 : cmd->label);
484 0 : TALER_EXCHANGE_withdraw_cancel (ws->wsh);
485 0 : ws->wsh = NULL;
486 : }
487 0 : if (NULL != ws->retry_task)
488 : {
489 0 : GNUNET_SCHEDULER_cancel (ws->retry_task);
490 0 : ws->retry_task = NULL;
491 : }
492 0 : TALER_denom_sig_free (&ws->sig);
493 0 : if (NULL != ws->pk)
494 : {
495 0 : TALER_EXCHANGE_destroy_denomination_key (ws->pk);
496 0 : ws->pk = NULL;
497 : }
498 0 : if (NULL != ws->age_commitment_proof)
499 : {
500 0 : TALER_age_commitment_proof_free (ws->age_commitment_proof);
501 0 : ws->age_commitment_proof = NULL;
502 : }
503 0 : if (NULL != ws->h_age_commitment)
504 : {
505 0 : GNUNET_free (ws->h_age_commitment);
506 0 : ws->h_age_commitment = NULL;
507 : }
508 0 : GNUNET_free (ws->exchange_url);
509 0 : GNUNET_free (ws->reserve_payto_uri);
510 0 : GNUNET_free (ws);
511 0 : }
512 :
513 :
514 : /**
515 : * Offer internal data to a "withdraw" CMD state to other
516 : * commands.
517 : *
518 : * @param cls closure
519 : * @param[out] ret result (could be anything)
520 : * @param trait name of the trait
521 : * @param index index number of the object to offer.
522 : * @return #GNUNET_OK on success
523 : */
524 : static enum GNUNET_GenericReturnValue
525 0 : withdraw_traits (void *cls,
526 : const void **ret,
527 : const char *trait,
528 : unsigned int index)
529 : {
530 0 : struct WithdrawState *ws = cls;
531 : struct TALER_TESTING_Trait traits[] = {
532 : /* history entry MUST be first due to response code logic below! */
533 0 : TALER_TESTING_make_trait_reserve_history (0,
534 0 : &ws->reserve_history),
535 0 : TALER_TESTING_make_trait_coin_priv (0 /* only one coin */,
536 0 : &ws->coin_priv),
537 0 : TALER_TESTING_make_trait_planchet_secret (&ws->ps),
538 0 : TALER_TESTING_make_trait_blinding_key (0 /* only one coin */,
539 0 : &ws->bks),
540 0 : TALER_TESTING_make_trait_exchange_wd_value (0 /* only one coin */,
541 0 : &ws->exchange_vals),
542 0 : TALER_TESTING_make_trait_denom_pub (0 /* only one coin */,
543 0 : ws->pk),
544 0 : TALER_TESTING_make_trait_denom_sig (0 /* only one coin */,
545 0 : &ws->sig),
546 0 : TALER_TESTING_make_trait_reserve_priv (&ws->reserve_priv),
547 0 : TALER_TESTING_make_trait_reserve_pub (&ws->reserve_pub),
548 0 : TALER_TESTING_make_trait_amount (&ws->amount),
549 0 : TALER_TESTING_make_trait_legi_requirement_row (&ws->requirement_row),
550 0 : TALER_TESTING_make_trait_h_payto (
551 0 : &ws->h_payto),
552 0 : TALER_TESTING_make_trait_payto_uri (
553 0 : (const char **) &ws->reserve_payto_uri),
554 0 : TALER_TESTING_make_trait_exchange_url (
555 0 : (const char **) &ws->exchange_url),
556 0 : TALER_TESTING_make_trait_age_commitment_proof (0,
557 0 : ws->age_commitment_proof),
558 0 : TALER_TESTING_make_trait_h_age_commitment (0,
559 0 : ws->h_age_commitment),
560 0 : TALER_TESTING_trait_end ()
561 : };
562 :
563 0 : return TALER_TESTING_get_trait ((ws->expected_response_code == MHD_HTTP_OK)
564 : ? &traits[0] /* we have reserve history */
565 : : &traits[1], /* skip reserve history */
566 : ret,
567 : trait,
568 : index);
569 : }
570 :
571 :
572 : struct TALER_TESTING_Command
573 0 : TALER_TESTING_cmd_withdraw_amount (const char *label,
574 : const char *reserve_reference,
575 : const char *amount,
576 : const uint8_t age,
577 : unsigned int expected_response_code)
578 : {
579 : struct WithdrawState *ws;
580 :
581 0 : ws = GNUNET_new (struct WithdrawState);
582 :
583 0 : ws->age = age;
584 0 : if (0 < age)
585 : {
586 : struct TALER_AgeCommitmentProof *acp;
587 : struct TALER_AgeCommitmentHash *hac;
588 : struct GNUNET_HashCode seed;
589 : struct TALER_AgeMask mask;
590 :
591 0 : acp = GNUNET_new (struct TALER_AgeCommitmentProof);
592 0 : hac = GNUNET_new (struct TALER_AgeCommitmentHash);
593 0 : mask = TALER_extensions_age_restriction_ageMask ();
594 0 : GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
595 : &seed,
596 : sizeof(seed));
597 :
598 0 : if (GNUNET_OK !=
599 0 : TALER_age_restriction_commit (
600 : &mask,
601 : age,
602 : &seed,
603 : acp))
604 : {
605 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
606 : "Failed to generate age commitment for age %d at %s\n",
607 : age,
608 : label);
609 0 : GNUNET_assert (0);
610 : }
611 :
612 0 : TALER_age_commitment_hash (&acp->commitment,hac);
613 0 : ws->age_commitment_proof = acp;
614 0 : ws->h_age_commitment = hac;
615 : }
616 :
617 0 : ws->reserve_reference = reserve_reference;
618 0 : if (GNUNET_OK !=
619 0 : TALER_string_to_amount (amount,
620 : &ws->amount))
621 : {
622 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
623 : "Failed to parse amount `%s' at %s\n",
624 : amount,
625 : label);
626 0 : GNUNET_assert (0);
627 : }
628 0 : ws->expected_response_code = expected_response_code;
629 : {
630 0 : struct TALER_TESTING_Command cmd = {
631 : .cls = ws,
632 : .label = label,
633 : .run = &withdraw_run,
634 : .cleanup = &withdraw_cleanup,
635 : .traits = &withdraw_traits
636 : };
637 :
638 0 : return cmd;
639 : }
640 : }
641 :
642 :
643 : struct TALER_TESTING_Command
644 0 : TALER_TESTING_cmd_withdraw_amount_reuse_key (
645 : const char *label,
646 : const char *reserve_reference,
647 : const char *amount,
648 : uint8_t age,
649 : const char *coin_ref,
650 : unsigned int expected_response_code)
651 : {
652 : struct TALER_TESTING_Command cmd;
653 :
654 0 : cmd = TALER_TESTING_cmd_withdraw_amount (label,
655 : reserve_reference,
656 : amount,
657 : age,
658 : expected_response_code);
659 : {
660 0 : struct WithdrawState *ws = cmd.cls;
661 :
662 0 : ws->reuse_coin_key_ref = coin_ref;
663 : }
664 0 : return cmd;
665 : }
666 :
667 :
668 : struct TALER_TESTING_Command
669 0 : TALER_TESTING_cmd_withdraw_denomination (
670 : const char *label,
671 : const char *reserve_reference,
672 : const struct TALER_EXCHANGE_DenomPublicKey *dk,
673 : unsigned int expected_response_code)
674 : {
675 : struct WithdrawState *ws;
676 :
677 0 : if (NULL == dk)
678 : {
679 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
680 : "Denomination key not specified at %s\n",
681 : label);
682 0 : GNUNET_assert (0);
683 : }
684 0 : ws = GNUNET_new (struct WithdrawState);
685 0 : ws->reserve_reference = reserve_reference;
686 0 : ws->pk = TALER_EXCHANGE_copy_denomination_key (dk);
687 0 : ws->expected_response_code = expected_response_code;
688 : {
689 0 : struct TALER_TESTING_Command cmd = {
690 : .cls = ws,
691 : .label = label,
692 : .run = &withdraw_run,
693 : .cleanup = &withdraw_cleanup,
694 : .traits = &withdraw_traits
695 : };
696 :
697 0 : return cmd;
698 : }
699 : }
700 :
701 :
702 : struct TALER_TESTING_Command
703 0 : TALER_TESTING_cmd_withdraw_with_retry (struct TALER_TESTING_Command cmd)
704 : {
705 : struct WithdrawState *ws;
706 :
707 0 : GNUNET_assert (&withdraw_run == cmd.run);
708 0 : ws = cmd.cls;
709 0 : ws->do_retry = NUM_RETRIES;
710 0 : return cmd;
711 : }
712 :
713 :
714 : /* end of testing_api_cmd_withdraw.c */
|