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 (21604B)


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