exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

testing_api_cmd_withdraw.c (22316B)


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