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_age_withdraw.c (25072B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2023-2025 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_age_withdraw.c
     21  * @brief implements the withdraw command for age-restricted coins
     22  * @author Özgür Kesim
     23  */
     24 
     25 #include "taler/taler_json_lib.h"
     26 #include <gnunet/gnunet_common.h>
     27 #include <microhttpd.h>
     28 #include <gnunet/gnunet_curl_lib.h>
     29 #include "taler/taler_signatures.h"
     30 struct AgeWithdrawState;
     31 #define TALER_EXCHANGE_POST_WITHDRAW_RESULT_CLOSURE struct AgeWithdrawState
     32 #include "taler/exchange/post-withdraw.h"
     33 struct AgeRevealWithdrawState;
     34 #define TALER_EXCHANGE_POST_REVEAL_WITHDRAW_RESULT_CLOSURE \
     35         struct AgeRevealWithdrawState
     36 #include "taler/exchange/post-reveal-withdraw.h"
     37 #include "taler/taler_testing_lib.h"
     38 
     39 /*
     40  * The output state of coin
     41  */
     42 struct CoinOutputState
     43 {
     44 
     45   /**
     46    * The calculated details during "withdraw", for the selected coin.
     47    */
     48   struct TALER_EXCHANGE_WithdrawCoinPrivateDetails details;
     49 
     50   /**
     51    * The (wanted) value of the coin, MUST be the same as input.denom_pub.value;
     52    */
     53   struct TALER_Amount amount;
     54 
     55 };
     56 
     57 /**
     58  * State for a "age withdraw" CMD:
     59  */
     60 
     61 struct AgeWithdrawState
     62 {
     63 
     64   /**
     65    * Interpreter state (during command)
     66    */
     67   struct TALER_TESTING_Interpreter *is;
     68 
     69   /**
     70    * The age-withdraw handle
     71    */
     72   struct TALER_EXCHANGE_PostWithdrawHandle *handle;
     73 
     74   /**
     75    * Exchange base URL.  Only used as offered trait.
     76    */
     77   char *exchange_url;
     78 
     79   /**
     80    * URI of the reserve we are withdrawing from.
     81    */
     82   struct TALER_NormalizedPayto reserve_payto_uri;
     83 
     84   /**
     85    * Private key of the reserve we are withdrawing from.
     86    */
     87   struct TALER_ReservePrivateKeyP reserve_priv;
     88 
     89   /**
     90    * Public key of the reserve we are withdrawing from.
     91    */
     92   struct TALER_ReservePublicKeyP reserve_pub;
     93 
     94   /**
     95    * Which reserve should we withdraw from?
     96    */
     97   const char *reserve_reference;
     98 
     99   /**
    100    * Expected HTTP response code to the request.
    101    */
    102   unsigned int expected_response_code;
    103 
    104   /**
    105    * Age mask
    106    */
    107   struct TALER_AgeMask mask;
    108 
    109   /**
    110    * The maximum age we commit to
    111    */
    112   uint8_t max_age;
    113 
    114   /**
    115    * Number of coins to withdraw
    116    */
    117   size_t num_coins;
    118 
    119   /**
    120    * The @e num_coins denomination public keys that are provided
    121    * to the `TALER_EXCHANGE_post_withdraw()` API.
    122    */
    123   struct TALER_EXCHANGE_DenomPublicKey *denoms_pub;
    124 
    125   /**
    126    * The master seed from which all the other seeds are derived from
    127    */
    128   struct TALER_WithdrawMasterSeedP seed;
    129 
    130   /**
    131    * The #TALER_CNC_KAPPA seeds derived from @e seed
    132    */
    133   struct TALER_KappaWithdrawMasterSeedP kappa_seed;
    134 
    135   /**
    136    * The master seed from which all the other seeds are derived from
    137    */
    138   struct TALER_BlindingMasterSeedP blinding_seed;
    139 
    140   /**
    141    * The output state of @e num_coins coins, calculated during the
    142    * "age-withdraw" operation.
    143    */
    144   struct CoinOutputState *coin_outputs;
    145 
    146   /**
    147    * The index returned by the exchange for the "age-withdraw" operation,
    148    * of the kappa coin candidates that we do not disclose and keep.
    149    */
    150   uint8_t noreveal_index;
    151 
    152   /**
    153    * The hash of the commitment, needed for the reveal step.
    154    */
    155   struct TALER_HashBlindedPlanchetsP planchets_h;
    156 
    157   /**
    158      *  The hash of the selected blinded planchets
    159      */
    160   struct TALER_HashBlindedPlanchetsP selected_h;
    161 
    162   /**
    163    * Set to the KYC requirement payto hash *if* the exchange replied with a
    164    * request for KYC.
    165    */
    166   struct TALER_NormalizedPaytoHashP h_payto;
    167 
    168   /**
    169    * Set to the KYC requirement row *if* the exchange replied with
    170    * a request for KYC.
    171    */
    172   uint64_t requirement_row;
    173 
    174   /**
    175    * Reserve history entry that corresponds to this withdraw.
    176    * Will be of type #TALER_EXCHANGE_RTT_WITHDRAWAL.
    177    */
    178   struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history;
    179 };
    180 
    181 /**
    182  * Callback for the "age-withdraw" operation;  It checks that the response
    183  * code is expected and store the exchange signature in the state.
    184  *
    185  * @param aws Closure of type `struct AgeWithdrawState *`
    186  * @param response Response details
    187  */
    188 static void
    189 age_withdraw_cb (
    190   struct AgeWithdrawState *aws,
    191   const struct TALER_EXCHANGE_PostWithdrawResponse *response)
    192 {
    193   struct TALER_TESTING_Interpreter *is = aws->is;
    194 
    195   aws->handle = NULL;
    196   if (aws->expected_response_code != response->hr.http_status)
    197   {
    198     TALER_TESTING_unexpected_status_with_body (is,
    199                                                response->hr.http_status,
    200                                                aws->expected_response_code,
    201                                                response->hr.reply);
    202     return;
    203   }
    204 
    205   switch (response->hr.http_status)
    206   {
    207   case MHD_HTTP_CREATED:
    208     aws->noreveal_index = response->details.created.noreveal_index;
    209     aws->planchets_h = response->details.created.planchets_h;
    210     aws->selected_h = response->details.created.selected_h;
    211     aws->reserve_history.details.withdraw.planchets_h = aws->planchets_h;
    212     aws->reserve_history.details.withdraw.selected_h = aws->selected_h;
    213     aws->reserve_history.details.withdraw.noreveal_index = aws->noreveal_index;
    214     aws->kappa_seed = response->details.created.kappa_seed;
    215 
    216     GNUNET_assert (aws->num_coins == response->details.created.num_coins);
    217     for (size_t n = 0; n < aws->num_coins; n++)
    218     {
    219       aws->coin_outputs[n].details = response->details.created.coin_details[n];
    220       TALER_age_commitment_proof_deep_copy (
    221         &aws->coin_outputs[n].details.age_commitment_proof,
    222         &response->details.created.coin_details[n].age_commitment_proof);
    223       TALER_denom_ewv_copy (
    224         &aws->coin_outputs[n].details.blinding_values,
    225         &response->details.created.coin_details[n].blinding_values);
    226     }
    227     break;
    228   case MHD_HTTP_FORBIDDEN:
    229   case MHD_HTTP_NOT_FOUND:
    230   case MHD_HTTP_GONE:
    231     /* nothing to check */
    232     break;
    233   case MHD_HTTP_CONFLICT:
    234     /* FIXME[oec]: Add this to the response-type and handle it here */
    235     break;
    236   case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
    237   default:
    238     /* Unsupported status code (by test harness) */
    239     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    240                 "test command for age-withdraw not support status code %u, body:\n"
    241                 ">>%s<<\n",
    242                 response->hr.http_status,
    243                 json_dumps (response->hr.reply, JSON_INDENT (2)));
    244     GNUNET_break (0);
    245     break;
    246   }
    247 
    248   /* We are done with this command, pick the next one */
    249   TALER_TESTING_interpreter_next (is);
    250 }
    251 
    252 
    253 /**
    254  * Run the command for age-withdraw.
    255  */
    256 static void
    257 age_withdraw_run (
    258   void *cls,
    259   const struct TALER_TESTING_Command *cmd,
    260   struct TALER_TESTING_Interpreter *is)
    261 {
    262   struct AgeWithdrawState *aws = cls;
    263   struct TALER_EXCHANGE_Keys *keys = TALER_TESTING_get_keys (is);
    264   const struct TALER_ReservePrivateKeyP *rp;
    265   const struct TALER_TESTING_Command *create_reserve;
    266   const struct TALER_EXCHANGE_DenomPublicKey *dpk;
    267 
    268   aws->is = is;
    269 
    270   /* Prepare the reserve related data */
    271   create_reserve
    272     = TALER_TESTING_interpreter_lookup_command (
    273         is,
    274         aws->reserve_reference);
    275 
    276   if (NULL == create_reserve)
    277   {
    278     GNUNET_break (0);
    279     TALER_TESTING_interpreter_fail (is);
    280     return;
    281   }
    282   if (GNUNET_OK !=
    283       TALER_TESTING_get_trait_reserve_priv (create_reserve,
    284                                             &rp))
    285   {
    286     GNUNET_break (0);
    287     TALER_TESTING_interpreter_fail (is);
    288     return;
    289   }
    290   if (NULL == aws->exchange_url)
    291     aws->exchange_url
    292       = GNUNET_strdup (TALER_TESTING_get_exchange_url (is));
    293   aws->reserve_priv = *rp;
    294   GNUNET_CRYPTO_eddsa_key_get_public (&aws->reserve_priv.eddsa_priv,
    295                                       &aws->reserve_pub.eddsa_pub);
    296   aws->reserve_payto_uri
    297     = TALER_reserve_make_payto (aws->exchange_url,
    298                                 &aws->reserve_pub);
    299 
    300   aws->denoms_pub = GNUNET_new_array (aws->num_coins,
    301                                       struct TALER_EXCHANGE_DenomPublicKey);
    302 
    303   GNUNET_CRYPTO_random_block (&aws->seed,
    304                               sizeof(aws->seed));
    305 
    306   for (unsigned int i = 0; i<aws->num_coins; i++)
    307   {
    308     struct TALER_EXCHANGE_DenomPublicKey *denom_pub = &aws->denoms_pub[i];
    309     struct CoinOutputState *cos = &aws->coin_outputs[i];
    310 
    311     /* Find denomination */
    312     dpk = TALER_TESTING_find_pk (keys,
    313                                  &cos->amount,
    314                                  true); /* _always_ use denominations with age-striction */
    315     if (NULL == dpk)
    316     {
    317       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    318                   "Failed to determine denomination key for amount at %s\n",
    319                   (NULL != cmd) ? cmd->label : "<retried command>");
    320       GNUNET_break (0);
    321       TALER_TESTING_interpreter_fail (is);
    322       return;
    323     }
    324 
    325     /* We copy the denomination key, as re-querying /keys
    326      * would free the old one. */
    327     *denom_pub = *dpk;
    328     TALER_denom_pub_copy (&denom_pub->key,
    329                           &dpk->key);
    330 
    331     /* Accumulate the expected total amount and fee for the history */
    332     if (i == 0)
    333     {
    334       GNUNET_assert (GNUNET_OK ==
    335                      TALER_amount_set_zero (
    336                        cos->amount.currency,
    337                        &aws->reserve_history.amount));
    338       GNUNET_assert (GNUNET_OK ==
    339                      TALER_amount_set_zero (
    340                        denom_pub->fees.withdraw.currency,
    341                        &aws->reserve_history.details.withdraw.fee));
    342     }
    343     GNUNET_assert (0 <=
    344                    TALER_amount_add (&aws->reserve_history.amount,
    345                                      &aws->reserve_history.amount,
    346                                      &cos->amount));
    347     GNUNET_assert (0 <=
    348                    TALER_amount_add (&aws->reserve_history.amount,
    349                                      &aws->reserve_history.amount,
    350                                      &denom_pub->fees.withdraw));
    351     GNUNET_assert (0 <=
    352                    TALER_amount_add (&aws->reserve_history.details.withdraw.fee,
    353                                      &aws->reserve_history.details.withdraw.fee,
    354                                      &denom_pub->fees.withdraw));
    355 
    356   }
    357   /* Save the expected history entry */
    358   aws->reserve_history.type = TALER_EXCHANGE_RTT_WITHDRAWAL;
    359   aws->reserve_history.details.withdraw.age_restricted = true;
    360   aws->reserve_history.details.withdraw.max_age = aws->max_age;
    361 
    362 
    363   /* Execute the age-restricted variant of withdraw protocol */
    364   aws->handle =
    365     TALER_EXCHANGE_post_withdraw_create (
    366       TALER_TESTING_interpreter_get_context (is),
    367       TALER_TESTING_get_exchange_url (is),
    368       keys,
    369       rp,
    370       aws->num_coins,
    371       aws->denoms_pub,
    372       &aws->seed,
    373       aws->max_age);
    374   if (NULL == aws->handle)
    375   {
    376     GNUNET_break (0);
    377     TALER_TESTING_interpreter_fail (is);
    378     return;
    379   }
    380   GNUNET_assert (GNUNET_OK ==
    381                  TALER_EXCHANGE_post_withdraw_set_options (
    382                    aws->handle,
    383                    TALER_EXCHANGE_post_withdraw_option_with_age_proof (
    384                      aws->max_age)));
    385   GNUNET_assert (TALER_EC_NONE ==
    386                  TALER_EXCHANGE_post_withdraw_start (aws->handle,
    387                                                      &age_withdraw_cb,
    388                                                      aws));
    389 }
    390 
    391 
    392 /**
    393  * Free the state of a "age withdraw" CMD, and possibly cancel a
    394  * pending operation thereof
    395  *
    396  * @param cls Closure of type `struct AgeWithdrawState`
    397  * @param cmd The command being freed.
    398  */
    399 static void
    400 age_withdraw_cleanup (
    401   void *cls,
    402   const struct TALER_TESTING_Command *cmd)
    403 {
    404   struct AgeWithdrawState *aws = cls;
    405 
    406   if (NULL != aws->handle)
    407   {
    408     TALER_TESTING_command_incomplete (aws->is,
    409                                       cmd->label);
    410     TALER_EXCHANGE_post_withdraw_cancel (aws->handle);
    411     aws->handle = NULL;
    412   }
    413 
    414   if (NULL != aws->denoms_pub)
    415   {
    416     for (size_t n = 0; n < aws->num_coins; n++)
    417       TALER_denom_pub_free (&aws->denoms_pub[n].key);
    418 
    419     GNUNET_free (aws->denoms_pub);
    420     aws->denoms_pub = NULL;
    421   }
    422 
    423   if (NULL != aws->coin_outputs)
    424   {
    425     for (size_t n = 0; n < aws->num_coins; n++)
    426     {
    427       struct CoinOutputState *out = &aws->coin_outputs[n];
    428       TALER_age_commitment_proof_free (&out->details.age_commitment_proof);
    429       TALER_denom_ewv_free (&out->details.blinding_values);
    430     }
    431     GNUNET_free (aws->coin_outputs);
    432     aws->coin_outputs = NULL;
    433   }
    434 
    435   GNUNET_free (aws->exchange_url);
    436   aws->exchange_url  = NULL;
    437   GNUNET_free (aws->reserve_payto_uri.normalized_payto);
    438   aws->reserve_payto_uri.normalized_payto = NULL;
    439   GNUNET_free (aws);
    440 }
    441 
    442 
    443 /**
    444  * Offer internal data of a "age withdraw" CMD state to other commands.
    445  *
    446  * @param cls Closure of type `struct AgeWithdrawState`
    447  * @param[out] ret result (could be anything)
    448  * @param trait name of the trait
    449  * @param idx index number of the object to offer.
    450  * @return #GNUNET_OK on success
    451  */
    452 static enum GNUNET_GenericReturnValue
    453 age_withdraw_traits (
    454   void *cls,
    455   const void **ret,
    456   const char *trait,
    457   unsigned int idx)
    458 {
    459   struct AgeWithdrawState *aws = cls;
    460   struct CoinOutputState *out = &aws->coin_outputs[idx];
    461   struct TALER_EXCHANGE_WithdrawCoinPrivateDetails *details =
    462     &aws->coin_outputs[idx].details;
    463   struct TALER_TESTING_Trait traits[] = {
    464     /* history entry MUST be first due to response code logic below! */
    465     TALER_TESTING_make_trait_reserve_history (idx,
    466                                               &aws->reserve_history),
    467     TALER_TESTING_make_trait_denom_pub (idx,
    468                                         &aws->denoms_pub[idx]),
    469     TALER_TESTING_make_trait_reserve_priv (&aws->reserve_priv),
    470     TALER_TESTING_make_trait_reserve_pub (&aws->reserve_pub),
    471     TALER_TESTING_make_trait_withdraw_commitment (&aws->planchets_h),
    472     TALER_TESTING_make_trait_amounts (idx,
    473                                       &out->amount),
    474     /* FIXME[oec]: add legal requirement to response and handle it here, as well
    475     TALER_TESTING_make_trait_legi_requirement_row (&aws->requirement_row),
    476     TALER_TESTING_make_trait_h_payto (&aws->h_payto),
    477     */
    478     TALER_TESTING_make_trait_normalized_payto_uri (&aws->reserve_payto_uri),
    479     TALER_TESTING_make_trait_exchange_url (aws->exchange_url),
    480     TALER_TESTING_make_trait_coin_priv (idx,
    481                                         &details->coin_priv),
    482     TALER_TESTING_make_trait_withdraw_seed (&aws->seed),
    483     /* FIXME[oec]: needed!?
    484     TALER_TESTING_make_trait_planchet_secrets (idx,
    485                                                &aws->secrets[k][idx]),
    486     */
    487     TALER_TESTING_make_trait_blinding_key (idx,
    488                                            &details->blinding_key),
    489     TALER_TESTING_make_trait_exchange_blinding_values (idx,
    490                                                        &details->blinding_values
    491                                                        ),
    492     TALER_TESTING_make_trait_age_commitment_proof (
    493       idx,
    494       &details->age_commitment_proof),
    495     TALER_TESTING_make_trait_h_age_commitment (
    496       idx,
    497       &details->h_age_commitment),
    498     TALER_TESTING_trait_end ()
    499   };
    500 
    501   if (idx >= aws->num_coins)
    502     return GNUNET_NO;
    503 
    504   return TALER_TESTING_get_trait ((aws->expected_response_code == MHD_HTTP_OK)
    505                                   ? &traits[0] /* we have reserve history */
    506                                   : &traits[1], /* skip reserve history */
    507                                   ret,
    508                                   trait,
    509                                   idx);
    510 }
    511 
    512 
    513 struct TALER_TESTING_Command
    514 TALER_TESTING_cmd_withdraw_with_age_proof (const char *label,
    515                                            const char *reserve_reference,
    516                                            uint8_t max_age,
    517                                            unsigned int
    518                                            expected_response_code,
    519                                            const char *amount,
    520                                            ...)
    521 {
    522   struct AgeWithdrawState *aws;
    523   unsigned int cnt;
    524   va_list ap;
    525 
    526   aws = GNUNET_new (struct AgeWithdrawState);
    527   aws->reserve_reference = reserve_reference;
    528   aws->expected_response_code = expected_response_code;
    529   aws->mask = TALER_testing_age_restriction_mask;
    530   aws->max_age = TALER_get_lowest_age (&aws->mask,
    531                                        max_age);
    532   cnt = 1;
    533   va_start (ap, amount);
    534   while (NULL != (va_arg (ap, const char *)))
    535     cnt++;
    536   aws->num_coins = cnt;
    537   aws->coin_outputs = GNUNET_new_array (cnt,
    538                                         struct CoinOutputState);
    539   va_end (ap);
    540   va_start (ap, amount);
    541 
    542   for (unsigned int i = 0; i<aws->num_coins; i++)
    543   {
    544     struct CoinOutputState *out = &aws->coin_outputs[i];
    545     if (GNUNET_OK !=
    546         TALER_string_to_amount (amount,
    547                                 &out->amount))
    548     {
    549       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    550                   "Failed to parse amount `%s' at %s\n",
    551                   amount,
    552                   label);
    553       GNUNET_assert (0);
    554     }
    555     /* move on to next vararg! */
    556     amount = va_arg (ap, const char *);
    557   }
    558 
    559   GNUNET_assert (NULL == amount);
    560   va_end (ap);
    561 
    562   {
    563     struct TALER_TESTING_Command cmd = {
    564       .cls = aws,
    565       .label = label,
    566       .run = &age_withdraw_run,
    567       .cleanup = &age_withdraw_cleanup,
    568       .traits = &age_withdraw_traits,
    569     };
    570 
    571     return cmd;
    572   }
    573 }
    574 
    575 
    576 /**
    577  * The state for the age-withdraw-reveal operation
    578  */
    579 struct AgeRevealWithdrawState
    580 {
    581   /**
    582    * The reference to the CMD resembling the previous call to age-withdraw
    583    */
    584   const char *age_withdraw_reference;
    585 
    586   /**
    587    * The state to the previous age-withdraw command
    588    */
    589   const struct AgeWithdrawState *aws;
    590 
    591   /**
    592    * The expected response code from the call to the
    593    * age-withdraw-reveal operation
    594    */
    595   unsigned int expected_response_code;
    596 
    597   /**
    598    * Interpreter state (during command)
    599    */
    600   struct TALER_TESTING_Interpreter *is;
    601 
    602   /**
    603    * The handle to the reveal-operation
    604    */
    605   struct TALER_EXCHANGE_PostRevealWithdrawHandle *handle;
    606 
    607 
    608   /**
    609    * Number of coins, extracted form the age withdraw command
    610    */
    611   size_t num_coins;
    612 
    613   /**
    614    * The signatures of the @e num_coins coins returned
    615    */
    616   struct TALER_DenominationSignature *denom_sigs;
    617 
    618 };
    619 
    620 
    621 /**
    622  * Callback for the reveal response
    623  *
    624  * @param awrs Closure of type `struct AgeRevealWithdrawState`
    625  * @param response The response
    626  */
    627 static void
    628 age_reveal_withdraw_cb (
    629   struct AgeRevealWithdrawState *awrs,
    630   const struct TALER_EXCHANGE_PostRevealWithdrawResponse *response)
    631 {
    632   struct TALER_TESTING_Interpreter *is = awrs->is;
    633 
    634   awrs->handle = NULL;
    635   if (awrs->expected_response_code != response->hr.http_status)
    636   {
    637     TALER_TESTING_unexpected_status_with_body (is,
    638                                                response->hr.http_status,
    639                                                awrs->expected_response_code,
    640                                                response->hr.reply);
    641     return;
    642   }
    643   switch (response->hr.http_status)
    644   {
    645   case MHD_HTTP_OK:
    646     {
    647       const struct AgeWithdrawState *aws = awrs->aws;
    648 
    649       GNUNET_assert (awrs->num_coins == response->details.ok.num_sigs);
    650       awrs->denom_sigs = GNUNET_new_array (awrs->num_coins,
    651                                            struct TALER_DenominationSignature);
    652       for (size_t n = 0; n < awrs->num_coins; n++)
    653       {
    654         GNUNET_assert (GNUNET_OK ==
    655                        TALER_denom_sig_unblind (
    656                          &awrs->denom_sigs[n],
    657                          &response->details.ok.blinded_denom_sigs[n],
    658                          &aws->coin_outputs[n].details.blinding_key,
    659                          &aws->coin_outputs[n].details.h_coin_pub,
    660                          &aws->coin_outputs[n].details.blinding_values,
    661                          &aws->denoms_pub[n].key));
    662       }
    663 
    664       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    665                   "age-withdraw reveal success!\n");
    666     }
    667     break;
    668   case MHD_HTTP_NOT_FOUND:
    669   case MHD_HTTP_FORBIDDEN:
    670     /* nothing to check */
    671     break;
    672   /* FIXME[oec]: handle more cases !? */
    673   default:
    674     /* Unsupported status code (by test harness) */
    675     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    676                 "Age withdraw reveal test command does not support status code %u\n",
    677                 response->hr.http_status);
    678     GNUNET_break (0);
    679     break;
    680   }
    681 
    682   /* We are done with this command, pick the next one */
    683   TALER_TESTING_interpreter_next (is);
    684 }
    685 
    686 
    687 /**
    688  * Run the command for age-withdraw-reveal
    689  */
    690 static void
    691 age_reveal_withdraw_run (
    692   void *cls,
    693   const struct TALER_TESTING_Command *cmd,
    694   struct TALER_TESTING_Interpreter *is)
    695 {
    696   struct AgeRevealWithdrawState *awrs = cls;
    697   const struct TALER_TESTING_Command *age_withdraw_cmd;
    698   const struct AgeWithdrawState *aws;
    699 
    700   (void) cmd;
    701   awrs->is = is;
    702 
    703   /*
    704    * Get the command and state for the previous call to "age witdraw"
    705    */
    706   age_withdraw_cmd  =
    707     TALER_TESTING_interpreter_lookup_command (is,
    708                                               awrs->age_withdraw_reference);
    709   if (NULL == age_withdraw_cmd)
    710   {
    711     GNUNET_break (0);
    712     TALER_TESTING_interpreter_fail (is);
    713     return;
    714   }
    715   GNUNET_assert (age_withdraw_cmd->run == age_withdraw_run);
    716   aws = age_withdraw_cmd->cls;
    717   awrs->aws = aws;
    718   awrs->num_coins = aws->num_coins;
    719 
    720   {
    721     struct TALER_RevealWithdrawMasterSeedsP revealed_seeds;
    722     size_t j = 0;
    723     for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
    724     {
    725       if (aws->noreveal_index == k)
    726         continue;
    727 
    728       revealed_seeds.tuple[j] = aws->kappa_seed.tuple[k];
    729       j++;
    730     }
    731 
    732     awrs->handle =
    733       TALER_EXCHANGE_post_reveal_withdraw_create (
    734         TALER_TESTING_interpreter_get_context (is),
    735         TALER_TESTING_get_exchange_url (is),
    736         aws->num_coins,
    737         &aws->planchets_h,
    738         &revealed_seeds);
    739     GNUNET_assert (NULL != awrs->handle);
    740     GNUNET_assert (TALER_EC_NONE ==
    741                    TALER_EXCHANGE_post_reveal_withdraw_start (
    742                      awrs->handle,
    743                      &age_reveal_withdraw_cb,
    744                      awrs));
    745   }
    746 }
    747 
    748 
    749 /**
    750  * Free the state of a "age-withdraw-reveal" CMD, and possibly
    751  * cancel a pending operation thereof
    752  *
    753  * @param cls Closure of type `struct AgeRevealWithdrawState`
    754  * @param cmd The command being freed.
    755  */
    756 static void
    757 age_reveal_withdraw_cleanup (
    758   void *cls,
    759   const struct TALER_TESTING_Command *cmd)
    760 {
    761   struct AgeRevealWithdrawState *awrs = cls;
    762 
    763   if (NULL != awrs->handle)
    764   {
    765     TALER_TESTING_command_incomplete (awrs->is,
    766                                       cmd->label);
    767     TALER_EXCHANGE_post_reveal_withdraw_cancel (awrs->handle);
    768     awrs->handle = NULL;
    769   }
    770   if (NULL != awrs->denom_sigs)
    771   {
    772     for (size_t n = 0; n < awrs->num_coins; n++)
    773     {
    774       TALER_denom_sig_free (&awrs->denom_sigs[n]);
    775     }
    776     GNUNET_free (awrs->denom_sigs);
    777   }
    778   GNUNET_free (awrs);
    779 }
    780 
    781 
    782 /**
    783  * Offer internal data of a "age withdraw reveal" CMD state to other commands.
    784  *
    785  * @param cls Closure of they `struct AgeRevealWithdrawState`
    786  * @param[out] ret result (could be anything)
    787  * @param trait name of the trait
    788  * @param idx index number of the object to offer.
    789  * @return #GNUNET_OK on success
    790  */
    791 static enum GNUNET_GenericReturnValue
    792 age_reveal_withdraw_traits (
    793   void *cls,
    794   const void **ret,
    795   const char *trait,
    796   unsigned int idx)
    797 {
    798   struct AgeRevealWithdrawState *awrs = cls;
    799   struct TALER_TESTING_Trait traits[] = {
    800     TALER_TESTING_make_trait_denom_sig (idx,
    801                                         &awrs->denom_sigs[idx]),
    802     /* FIXME: shall we provide the traits from the previous
    803      * call to "age withdraw" as well? */
    804     TALER_TESTING_trait_end ()
    805   };
    806 
    807   if (NULL == awrs->denom_sigs)
    808     return GNUNET_NO;
    809   if (idx >= awrs->num_coins)
    810     return GNUNET_NO;
    811 
    812   return TALER_TESTING_get_trait (traits,
    813                                   ret,
    814                                   trait,
    815                                   idx);
    816 }
    817 
    818 
    819 struct TALER_TESTING_Command
    820 TALER_TESTING_cmd_withdraw_reveal_age_proof (
    821   const char *label,
    822   const char *age_withdraw_reference,
    823   unsigned int expected_response_code)
    824 {
    825   struct AgeRevealWithdrawState *awrs =
    826     GNUNET_new (struct AgeRevealWithdrawState);
    827 
    828   awrs->age_withdraw_reference = age_withdraw_reference;
    829   awrs->expected_response_code = expected_response_code;
    830   {
    831     struct TALER_TESTING_Command cmd = {
    832       .cls = awrs,
    833       .label = label,
    834       .run = age_reveal_withdraw_run,
    835       .cleanup = age_reveal_withdraw_cleanup,
    836       .traits = age_reveal_withdraw_traits,
    837     };
    838 
    839     return cmd;
    840   }
    841 }
    842 
    843 
    844 /* end of testing_api_cmd_age_withdraw.c */