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_batch_deposit.c (22594B)


      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_batch_deposit.c
     21  * @brief command for testing /batch-deposit.
     22  * @author Marcello Stanisci
     23  * @author Christian Grothoff
     24  */
     25 #include "taler/taler_json_lib.h"
     26 #include <gnunet/gnunet_curl_lib.h>
     27 #include "taler/taler_testing_lib.h"
     28 #include "taler/taler_signatures.h"
     29 #include "backoff.h"
     30 
     31 
     32 /**
     33  * How often do we retry before giving up?
     34  */
     35 #define NUM_RETRIES 5
     36 
     37 /**
     38  * How long do we wait AT MOST when retrying?
     39  */
     40 #define MAX_BACKOFF GNUNET_TIME_relative_multiply ( \
     41           GNUNET_TIME_UNIT_MILLISECONDS, 100)
     42 
     43 
     44 /**
     45  * Information per coin in the batch.
     46  */
     47 struct Coin
     48 {
     49 
     50   /**
     51    * Amount to deposit.
     52    */
     53   struct TALER_Amount amount;
     54 
     55   /**
     56    * Deposit fee.
     57    */
     58   struct TALER_Amount deposit_fee;
     59 
     60   /**
     61    * Our coin signature.
     62    */
     63   struct TALER_CoinSpendSignatureP coin_sig;
     64 
     65   /**
     66    * Reference to any command that is able to provide a coin,
     67    * possibly using $LABEL#$INDEX notation.
     68    */
     69   char *coin_reference;
     70 
     71   /**
     72    * Denomination public key of the coin.
     73    */
     74   const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
     75 
     76   /**
     77    * The command being referenced.
     78    */
     79   const struct TALER_TESTING_Command *coin_cmd;
     80 
     81   /**
     82    * Expected entry in the coin history created by this
     83    * coin.
     84    */
     85   struct TALER_EXCHANGE_CoinHistoryEntry che;
     86 
     87   /**
     88    * Index of the coin at @e coin_cmd.
     89    */
     90   unsigned int coin_idx;
     91 };
     92 
     93 
     94 /**
     95  * State for a "batch deposit" CMD.
     96  */
     97 struct BatchDepositState
     98 {
     99 
    100   /**
    101    * Refund deadline. Zero for no refunds.
    102    */
    103   struct GNUNET_TIME_Timestamp refund_deadline;
    104 
    105   /**
    106    * Wire deadline.
    107    */
    108   struct GNUNET_TIME_Timestamp wire_deadline;
    109 
    110   /**
    111    * Timestamp of the /deposit operation in the wallet (contract signing time).
    112    */
    113   struct GNUNET_TIME_Timestamp wallet_timestamp;
    114 
    115   /**
    116    * How long do we wait until we retry?
    117    */
    118   struct GNUNET_TIME_Relative backoff;
    119 
    120   /**
    121    * When did the exchange receive the deposit?
    122    */
    123   struct GNUNET_TIME_Timestamp exchange_timestamp;
    124 
    125   /**
    126    * Signing key used by the exchange to sign the
    127    * deposit confirmation.
    128    */
    129   struct TALER_ExchangePublicKeyP exchange_pub;
    130 
    131   /**
    132    * Set (by the interpreter) to a fresh private key.  This
    133    * key will be used to sign the deposit request.
    134    */
    135   union TALER_AccountPrivateKeyP account_priv;
    136 
    137   /**
    138    * Set (by the interpreter) to the public key
    139    * corresponding to @e account_priv.
    140    */
    141   union TALER_AccountPublicKeyP account_pub;
    142 
    143   /**
    144    * Deposit handle while operation is running.
    145    */
    146   struct TALER_EXCHANGE_PostBatchDepositHandle *dh;
    147 
    148   /**
    149    * Array of coins to batch-deposit.
    150    */
    151   struct Coin *coins;
    152 
    153   /**
    154    * Wire details of who is depositing -- this would be merchant
    155    * wire details in a normal scenario.
    156    */
    157   json_t *wire_details;
    158 
    159   /**
    160    * JSON string describing what a proposal is about.
    161    */
    162   json_t *contract_terms;
    163 
    164   /**
    165    * Interpreter state.
    166    */
    167   struct TALER_TESTING_Interpreter *is;
    168 
    169   /**
    170    * Task scheduled to try later.
    171    */
    172   struct GNUNET_SCHEDULER_Task *retry_task;
    173 
    174   /**
    175    * Deposit confirmation signature from the exchange.
    176    */
    177   struct TALER_ExchangeSignatureP exchange_sig;
    178 
    179   /**
    180    * Set to the KYC requirement payto hash *if* the exchange replied with a
    181    * request for KYC.
    182    */
    183   struct TALER_NormalizedPaytoHashP h_payto;
    184 
    185   /**
    186    * Set to the KYC requirement row *if* the exchange replied with
    187    * a request for KYC.
    188    */
    189   uint64_t requirement_row;
    190 
    191   /**
    192    * Reference to previous deposit operation.
    193    * Only present if we're supposed to replay the previous deposit.
    194    */
    195   const char *deposit_reference;
    196 
    197   /**
    198    * If @e coin_reference refers to an operation that generated
    199    * an array of coins, this value determines which coin to pick.
    200    */
    201   unsigned int num_coins;
    202 
    203   /**
    204    * Expected HTTP response code.
    205    */
    206   unsigned int expected_response_code;
    207 
    208   /**
    209    * Set to true if the /deposit succeeded
    210    * and we now can provide the resulting traits.
    211    */
    212   bool deposit_succeeded;
    213 
    214 };
    215 
    216 
    217 /**
    218  * Callback to analyze the /batch-deposit response, just used to check if the
    219  * response code is acceptable.
    220  *
    221  * @param cls closure.
    222  * @param dr deposit response details
    223  */
    224 static void
    225 batch_deposit_cb (void *cls,
    226                   const struct TALER_EXCHANGE_PostBatchDepositResponse *dr)
    227 {
    228   struct BatchDepositState *ds = cls;
    229 
    230   ds->dh = NULL;
    231   if (ds->expected_response_code != dr->hr.http_status)
    232   {
    233     TALER_TESTING_unexpected_status (ds->is,
    234                                      dr->hr.http_status,
    235                                      ds->expected_response_code);
    236     return;
    237   }
    238   switch (dr->hr.http_status)
    239   {
    240   case MHD_HTTP_OK:
    241     ds->deposit_succeeded = GNUNET_YES;
    242     ds->exchange_timestamp = dr->details.ok.deposit_timestamp;
    243     ds->exchange_pub = *dr->details.ok.exchange_pub;
    244     ds->exchange_sig = *dr->details.ok.exchange_sig;
    245     break;
    246   case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
    247     /* nothing to check */
    248     ds->requirement_row
    249       = dr->details.unavailable_for_legal_reasons.requirement_row;
    250     ds->h_payto
    251       = dr->details.unavailable_for_legal_reasons.h_payto;
    252     break;
    253   }
    254   TALER_TESTING_interpreter_next (ds->is);
    255 }
    256 
    257 
    258 /**
    259  * Run the command.
    260  *
    261  * @param cls closure.
    262  * @param cmd the command to execute.
    263  * @param is the interpreter state.
    264  */
    265 static void
    266 batch_deposit_run (void *cls,
    267                    const struct TALER_TESTING_Command *cmd,
    268                    struct TALER_TESTING_Interpreter *is)
    269 {
    270   struct BatchDepositState *ds = cls;
    271   const struct TALER_DenominationSignature *denom_pub_sig;
    272   struct TALER_PrivateContractHashP h_contract_terms;
    273   enum TALER_ErrorCode ec;
    274   struct TALER_WireSaltP wire_salt;
    275   struct TALER_MerchantWireHashP h_wire;
    276   struct TALER_FullPayto payto_uri;
    277   struct TALER_EXCHANGE_CoinDepositDetail cdds[ds->num_coins];
    278   struct GNUNET_JSON_Specification spec[] = {
    279     TALER_JSON_spec_full_payto_uri ("payto_uri",
    280                                     &payto_uri),
    281     GNUNET_JSON_spec_fixed_auto ("salt",
    282                                  &wire_salt),
    283     GNUNET_JSON_spec_end ()
    284   };
    285   const char *exchange_url
    286     = TALER_TESTING_get_exchange_url (is);
    287 
    288   (void) cmd;
    289   if (NULL == exchange_url)
    290   {
    291     GNUNET_break (0);
    292     return;
    293   }
    294   memset (cdds,
    295           0,
    296           sizeof (cdds));
    297   ds->is = is;
    298   GNUNET_assert (NULL != ds->wire_details);
    299   if (GNUNET_OK !=
    300       GNUNET_JSON_parse (ds->wire_details,
    301                          spec,
    302                          NULL, NULL))
    303   {
    304     json_dumpf (ds->wire_details,
    305                 stderr,
    306                 JSON_INDENT (2));
    307     GNUNET_break (0);
    308     TALER_TESTING_interpreter_fail (is);
    309     return;
    310   }
    311 #if DUMP_CONTRACT
    312   fprintf (stderr,
    313            "Using contract:\n");
    314   json_dumpf (ds->contract_terms,
    315               stderr,
    316               JSON_INDENT (2));
    317 #endif
    318   if (GNUNET_OK !=
    319       TALER_JSON_contract_hash (ds->contract_terms,
    320                                 &h_contract_terms))
    321   {
    322     GNUNET_break (0);
    323     TALER_TESTING_interpreter_fail (is);
    324     return;
    325   }
    326   GNUNET_assert (GNUNET_OK ==
    327                  TALER_JSON_merchant_wire_signature_hash (ds->wire_details,
    328                                                           &h_wire));
    329   if (! GNUNET_TIME_absolute_is_zero (ds->refund_deadline.abs_time))
    330   {
    331     struct GNUNET_TIME_Relative refund_deadline;
    332 
    333     refund_deadline
    334       = GNUNET_TIME_absolute_get_remaining (ds->refund_deadline.abs_time);
    335     ds->wire_deadline
    336       =
    337         GNUNET_TIME_relative_to_timestamp (
    338           GNUNET_TIME_relative_multiply (refund_deadline,
    339                                          2));
    340   }
    341   else
    342   {
    343     ds->refund_deadline = ds->wallet_timestamp;
    344     ds->wire_deadline = GNUNET_TIME_timestamp_get ();
    345   }
    346 
    347   {
    348     const struct TALER_TESTING_Command *acc_var;
    349     if (NULL != (acc_var
    350                    = TALER_TESTING_interpreter_get_command (
    351                        is,
    352                        "account-priv")))
    353     {
    354       const union TALER_AccountPrivateKeyP *account_priv;
    355 
    356       if ( (GNUNET_OK !=
    357             TALER_TESTING_get_trait_account_priv (acc_var,
    358                                                   &account_priv)) )
    359       {
    360         GNUNET_break (0);
    361         TALER_TESTING_interpreter_fail (is);
    362         return;
    363       }
    364       ds->account_priv = *account_priv;
    365       GNUNET_CRYPTO_eddsa_key_get_public (
    366         &ds->account_priv.merchant_priv.eddsa_priv,
    367         &ds->account_pub.merchant_pub.eddsa_pub);
    368     }
    369     else
    370     {
    371       GNUNET_CRYPTO_eddsa_key_create (
    372         &ds->account_priv.merchant_priv.eddsa_priv);
    373       GNUNET_CRYPTO_eddsa_key_get_public (
    374         &ds->account_priv.merchant_priv.eddsa_priv,
    375         &ds->account_pub.merchant_pub.eddsa_pub);
    376     }
    377   }
    378   for (unsigned int i = 0; i<ds->num_coins; i++)
    379   {
    380     struct Coin *coin = &ds->coins[i];
    381     struct TALER_EXCHANGE_CoinDepositDetail *cdd = &cdds[i];
    382     const struct TALER_CoinSpendPrivateKeyP *coin_priv;
    383     const struct TALER_AgeCommitmentProof *age_commitment_proof = NULL;
    384 
    385     GNUNET_assert (NULL != coin->coin_reference);
    386     cdd->amount = coin->amount;
    387     coin->coin_cmd = TALER_TESTING_interpreter_lookup_command (
    388       is,
    389       coin->coin_reference);
    390     if (NULL == coin->coin_cmd)
    391     {
    392       GNUNET_break (0);
    393       TALER_TESTING_interpreter_fail (is);
    394       return;
    395     }
    396 
    397     if ( (GNUNET_OK !=
    398           TALER_TESTING_get_trait_coin_priv (coin->coin_cmd,
    399                                              coin->coin_idx,
    400                                              &coin_priv)) ||
    401          (GNUNET_OK !=
    402           TALER_TESTING_get_trait_age_commitment_proof (coin->coin_cmd,
    403                                                         coin->coin_idx,
    404                                                         &age_commitment_proof))
    405          ||
    406          (GNUNET_OK !=
    407           TALER_TESTING_get_trait_denom_pub (coin->coin_cmd,
    408                                              coin->coin_idx,
    409                                              &coin->denom_pub)) ||
    410          (GNUNET_OK !=
    411           TALER_TESTING_get_trait_denom_sig (coin->coin_cmd,
    412                                              coin->coin_idx,
    413                                              &denom_pub_sig)) )
    414     {
    415       GNUNET_break (0);
    416       TALER_TESTING_interpreter_fail (is);
    417       return;
    418     }
    419     if (NULL != age_commitment_proof)
    420     {
    421       TALER_age_commitment_hash (&age_commitment_proof->commitment,
    422                                  &cdd->h_age_commitment);
    423     }
    424     coin->deposit_fee = coin->denom_pub->fees.deposit;
    425     GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
    426                                         &cdd->coin_pub.eddsa_pub);
    427     cdd->denom_sig = *denom_pub_sig;
    428     cdd->h_denom_pub = coin->denom_pub->h_key;
    429     TALER_wallet_deposit_sign (&coin->amount,
    430                                &coin->denom_pub->fees.deposit,
    431                                &h_wire,
    432                                &h_contract_terms,
    433                                NULL, /* wallet_data_hash */
    434                                &cdd->h_age_commitment,
    435                                NULL, /* hash of extensions */
    436                                &coin->denom_pub->h_key,
    437                                ds->wallet_timestamp,
    438                                &ds->account_pub.merchant_pub,
    439                                ds->refund_deadline,
    440                                coin_priv,
    441                                &cdd->coin_sig);
    442     coin->coin_sig = cdd->coin_sig;
    443     coin->che.type = TALER_EXCHANGE_CTT_DEPOSIT;
    444     coin->che.amount = coin->amount;
    445     coin->che.details.deposit.h_wire = h_wire;
    446     coin->che.details.deposit.h_contract_terms = h_contract_terms;
    447     coin->che.details.deposit.no_h_policy = true;
    448     coin->che.details.deposit.no_wallet_data_hash = true;
    449     coin->che.details.deposit.wallet_timestamp = ds->wallet_timestamp;
    450     coin->che.details.deposit.merchant_pub = ds->account_pub.merchant_pub;
    451     coin->che.details.deposit.refund_deadline = ds->refund_deadline;
    452     coin->che.details.deposit.sig = cdd->coin_sig;
    453     coin->che.details.deposit.no_hac = GNUNET_is_zero (&cdd->h_age_commitment);
    454     coin->che.details.deposit.hac = cdd->h_age_commitment;
    455     coin->che.details.deposit.deposit_fee = coin->denom_pub->fees.deposit;
    456   }
    457 
    458   GNUNET_assert (NULL == ds->dh);
    459   {
    460     struct TALER_EXCHANGE_DepositContractDetail dcd = {
    461       .wire_deadline = ds->wire_deadline,
    462       .merchant_payto_uri = payto_uri,
    463       .wire_salt = wire_salt,
    464       .h_contract_terms = h_contract_terms,
    465       .policy_details = NULL /* FIXME #7270-OEC */,
    466       .wallet_timestamp = ds->wallet_timestamp,
    467       .merchant_pub = ds->account_pub.merchant_pub,
    468       .refund_deadline = ds->refund_deadline
    469     };
    470 
    471     TALER_merchant_contract_sign (&h_contract_terms,
    472                                   &ds->account_priv.merchant_priv,
    473                                   &dcd.merchant_sig);
    474     ds->dh = TALER_EXCHANGE_post_batch_deposit_create (
    475       TALER_TESTING_interpreter_get_context (is),
    476       exchange_url,
    477       TALER_TESTING_get_keys (is),
    478       &dcd,
    479       ds->num_coins,
    480       cdds,
    481       &ec);
    482   }
    483   if (NULL == ds->dh)
    484   {
    485     GNUNET_break (0);
    486     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    487                 "Could not create deposit with EC %d\n",
    488                 (int) ec);
    489     TALER_TESTING_interpreter_fail (is);
    490     return;
    491   }
    492   TALER_EXCHANGE_post_batch_deposit_start (ds->dh,
    493                                            &batch_deposit_cb,
    494                                            ds);
    495 }
    496 
    497 
    498 /**
    499  * Free the state of a "batch-deposit" CMD, and possibly cancel a
    500  * pending operation thereof.
    501  *
    502  * @param cls closure, must be a `struct BatchDepositState`.
    503  * @param cmd the command which is being cleaned up.
    504  */
    505 static void
    506 batch_deposit_cleanup (void *cls,
    507                        const struct TALER_TESTING_Command *cmd)
    508 {
    509   struct BatchDepositState *ds = cls;
    510 
    511   if (NULL != ds->dh)
    512   {
    513     TALER_TESTING_command_incomplete (ds->is,
    514                                       cmd->label);
    515     TALER_EXCHANGE_post_batch_deposit_cancel (ds->dh);
    516     ds->dh = NULL;
    517   }
    518   if (NULL != ds->retry_task)
    519   {
    520     GNUNET_SCHEDULER_cancel (ds->retry_task);
    521     ds->retry_task = NULL;
    522   }
    523   for (unsigned int i = 0; i<ds->num_coins; i++)
    524     GNUNET_free (ds->coins[i].coin_reference);
    525   GNUNET_free (ds->coins);
    526   json_decref (ds->wire_details);
    527   json_decref (ds->contract_terms);
    528   GNUNET_free (ds);
    529 }
    530 
    531 
    532 /**
    533  * Offer internal data from a "batch-deposit" CMD, to other commands.
    534  *
    535  * @param cls closure.
    536  * @param[out] ret result.
    537  * @param trait name of the trait.
    538  * @param index index number of the object to offer.
    539  * @return #GNUNET_OK on success.
    540  */
    541 static enum GNUNET_GenericReturnValue
    542 batch_deposit_traits (void *cls,
    543                       const void **ret,
    544                       const char *trait,
    545                       unsigned int index)
    546 {
    547   struct BatchDepositState *ds = cls;
    548   const struct Coin *coin = &ds->coins[index];
    549   /* Will point to coin cmd internals. */
    550   const struct TALER_CoinSpendPrivateKeyP *coin_spent_priv;
    551   const struct TALER_CoinSpendPublicKeyP *coin_spent_pub;
    552   const struct TALER_AgeCommitmentProof *age_commitment_proof;
    553 
    554   if (index >= ds->num_coins)
    555   {
    556     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    557                 "[batch_deposit_traits] asked for index #%u while num_coins is #%u\n",
    558                 index,
    559                 ds->num_coins);
    560     return GNUNET_NO;
    561   }
    562   if (NULL == coin->coin_cmd)
    563   {
    564     GNUNET_break (0);
    565     TALER_TESTING_interpreter_fail (ds->is);
    566     return GNUNET_NO;
    567   }
    568   if ( (GNUNET_OK !=
    569         TALER_TESTING_get_trait_coin_priv (coin->coin_cmd,
    570                                            coin->coin_idx,
    571                                            &coin_spent_priv)) ||
    572        (GNUNET_OK !=
    573         TALER_TESTING_get_trait_coin_pub (coin->coin_cmd,
    574                                           coin->coin_idx,
    575                                           &coin_spent_pub)) ||
    576        (GNUNET_OK !=
    577         TALER_TESTING_get_trait_age_commitment_proof (coin->coin_cmd,
    578                                                       coin->coin_idx,
    579                                                       &age_commitment_proof)) )
    580   {
    581     GNUNET_break (0);
    582     TALER_TESTING_interpreter_fail (ds->is);
    583     return GNUNET_NO;
    584   }
    585 
    586   {
    587     struct TALER_TESTING_Trait traits[] = {
    588       /* First two traits are only available if
    589          ds->traits is #GNUNET_YES */
    590       TALER_TESTING_make_trait_exchange_pub (0,
    591                                              &ds->exchange_pub),
    592       TALER_TESTING_make_trait_exchange_sig (0,
    593                                              &ds->exchange_sig),
    594       /* These traits are always available */
    595       TALER_TESTING_make_trait_wire_details (ds->wire_details),
    596       TALER_TESTING_make_trait_contract_terms (ds->contract_terms),
    597       TALER_TESTING_make_trait_merchant_priv (&ds->account_priv.merchant_priv),
    598       TALER_TESTING_make_trait_merchant_pub (&ds->account_pub.merchant_pub),
    599       TALER_TESTING_make_trait_account_priv (&ds->account_priv),
    600       TALER_TESTING_make_trait_account_pub (&ds->account_pub),
    601       TALER_TESTING_make_trait_age_commitment_proof (index,
    602                                                      age_commitment_proof),
    603       TALER_TESTING_make_trait_coin_history (index,
    604                                              &coin->che),
    605       TALER_TESTING_make_trait_coin_pub (index,
    606                                          coin_spent_pub),
    607       TALER_TESTING_make_trait_denom_pub (index,
    608                                           coin->denom_pub),
    609       TALER_TESTING_make_trait_coin_priv (index,
    610                                           coin_spent_priv),
    611       TALER_TESTING_make_trait_coin_sig (index,
    612                                          &coin->coin_sig),
    613       TALER_TESTING_make_trait_deposit_amount (index,
    614                                                &coin->amount),
    615       TALER_TESTING_make_trait_deposit_fee_amount (index,
    616                                                    &coin->deposit_fee),
    617       TALER_TESTING_make_trait_timestamp (index,
    618                                           &ds->exchange_timestamp),
    619       TALER_TESTING_make_trait_wire_deadline (index,
    620                                               &ds->wire_deadline),
    621       TALER_TESTING_make_trait_refund_deadline (index,
    622                                                 &ds->refund_deadline),
    623       TALER_TESTING_make_trait_legi_requirement_row (&ds->requirement_row),
    624       TALER_TESTING_make_trait_h_normalized_payto (&ds->h_payto),
    625       TALER_TESTING_trait_end ()
    626     };
    627 
    628     return TALER_TESTING_get_trait ((ds->deposit_succeeded)
    629                                     ? traits
    630                                     : &traits[2],
    631                                     ret,
    632                                     trait,
    633                                     index);
    634   }
    635 }
    636 
    637 
    638 struct TALER_TESTING_Command
    639 TALER_TESTING_cmd_batch_deposit (
    640   const char *label,
    641   const struct TALER_FullPayto target_account_payto,
    642   const char *contract_terms,
    643   struct GNUNET_TIME_Relative refund_deadline,
    644   unsigned int expected_response_code,
    645   ...)
    646 {
    647   struct BatchDepositState *ds;
    648   va_list ap;
    649   unsigned int num_coins = 0;
    650   const char *ref;
    651 
    652   va_start (ap,
    653             expected_response_code);
    654   while (NULL != (ref = va_arg (ap,
    655                                 const char *)))
    656   {
    657     GNUNET_assert (NULL != va_arg (ap,
    658                                    const char *));
    659     num_coins++;
    660   }
    661   va_end (ap);
    662 
    663   ds = GNUNET_new (struct BatchDepositState);
    664   ds->num_coins = num_coins;
    665   ds->coins = GNUNET_new_array (num_coins,
    666                                 struct Coin);
    667   num_coins = 0;
    668   va_start (ap,
    669             expected_response_code);
    670   while (NULL != (ref = va_arg (ap,
    671                                 const char *)))
    672   {
    673     struct Coin *coin = &ds->coins[num_coins++];
    674     const char *amount = va_arg (ap,
    675                                  const char *);
    676 
    677     GNUNET_assert (GNUNET_OK ==
    678                    TALER_TESTING_parse_coin_reference (ref,
    679                                                        &coin->coin_reference,
    680                                                        &coin->coin_idx));
    681     GNUNET_assert (GNUNET_OK ==
    682                    TALER_string_to_amount (amount,
    683                                            &coin->amount));
    684   }
    685   va_end (ap);
    686 
    687   ds->wire_details = TALER_TESTING_make_wire_details (target_account_payto);
    688   GNUNET_assert (NULL != ds->wire_details);
    689   ds->contract_terms = json_loads (contract_terms,
    690                                    JSON_REJECT_DUPLICATES,
    691                                    NULL);
    692   if (NULL == ds->contract_terms)
    693   {
    694     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    695                 "Failed to parse contract terms `%s' for CMD `%s'\n",
    696                 contract_terms,
    697                 label);
    698     GNUNET_assert (0);
    699   }
    700   ds->wallet_timestamp = GNUNET_TIME_timestamp_get ();
    701   GNUNET_assert (0 ==
    702                  json_object_set_new (ds->contract_terms,
    703                                       "timestamp",
    704                                       GNUNET_JSON_from_timestamp (
    705                                         ds->wallet_timestamp)));
    706   if (! GNUNET_TIME_relative_is_zero (refund_deadline))
    707   {
    708     ds->refund_deadline = GNUNET_TIME_relative_to_timestamp (refund_deadline);
    709     GNUNET_assert (0 ==
    710                    json_object_set_new (ds->contract_terms,
    711                                         "refund_deadline",
    712                                         GNUNET_JSON_from_timestamp (
    713                                           ds->refund_deadline)));
    714   }
    715   ds->expected_response_code = expected_response_code;
    716   {
    717     struct TALER_TESTING_Command cmd = {
    718       .cls = ds,
    719       .label = label,
    720       .run = &batch_deposit_run,
    721       .cleanup = &batch_deposit_cleanup,
    722       .traits = &batch_deposit_traits
    723     };
    724 
    725     return cmd;
    726   }
    727 }
    728 
    729 
    730 /* end of testing_api_cmd_batch_deposit.c */