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


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