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


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