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


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