summaryrefslogtreecommitdiff
path: root/src/testing/testing_api_cmd_age_withdraw.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/testing/testing_api_cmd_age_withdraw.c')
-rw-r--r--src/testing/testing_api_cmd_age_withdraw.c671
1 files changed, 625 insertions, 46 deletions
diff --git a/src/testing/testing_api_cmd_age_withdraw.c b/src/testing/testing_api_cmd_age_withdraw.c
index 52efc5f7f..edf292970 100644
--- a/src/testing/testing_api_cmd_age_withdraw.c
+++ b/src/testing/testing_api_cmd_age_withdraw.c
@@ -23,7 +23,9 @@
*/
#include "platform.h"
+#include "taler_exchange_service.h"
#include "taler_json_lib.h"
+#include <gnunet/gnunet_common.h>
#include <microhttpd.h>
#include <gnunet/gnunet_curl_lib.h>
#include "taler_signatures.h"
@@ -31,15 +33,26 @@
#include "taler_testing_lib.h"
/*
- * The input and state of coin
+ * The output state of coin
*/
-struct CoinState
+struct CoinOutputState
{
- struct TALER_RefreshMasterSecretP secret;
+ /**
+ * The calculated details during "age-withdraw", for the selected coin.
+ */
struct TALER_EXCHANGE_AgeWithdrawCoinPrivateDetails details;
+ /**
+ * The (wanted) value of the coin, MUST be the same as input.denom_pub.value;
+ */
struct TALER_Amount amount;
+
+ /**
+ * Reserve history entry that corresponds to this coin.
+ * Will be of type #TALER_EXCHANGE_RTT_AGEWITHDRAWAL.
+ */
+ struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history;
};
/**
@@ -48,7 +61,38 @@ struct CoinState
struct AgeWithdrawState
{
- /*
+
+ /**
+ * Interpreter state (during command)
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * The age-withdraw handle
+ */
+ struct TALER_EXCHANGE_AgeWithdrawHandle *handle;
+
+ /**
+ * Exchange base URL. Only used as offered trait.
+ */
+ char *exchange_url;
+
+ /**
+ * URI of the reserve we are withdrawing from.
+ */
+ char *reserve_payto_uri;
+
+ /**
+ * Private key of the reserve we are withdrawing from.
+ */
+ struct TALER_ReservePrivateKeyP reserve_priv;
+
+ /**
+ * Public key of the reserve we are withdrawing from.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
* Which reserve should we withdraw from?
*/
const char *reserve_reference;
@@ -59,6 +103,11 @@ struct AgeWithdrawState
unsigned int expected_response_code;
/**
+ * Age mask
+ */
+ struct TALER_AgeMask mask;
+
+ /**
* The maximum age we commit to
*/
uint8_t max_age;
@@ -69,12 +118,336 @@ struct AgeWithdrawState
size_t num_coins;
/**
- * The input for the coins
+ * The @e num_coins input that is provided to the
+ * `TALER_EXCHANGE_age_withdraw` API.
+ * Each contains kappa secrets, from which we will have
+ * to disclose kappa-1 in a subsequent age-withdraw-reveal operation.
*/
- struct CoinState *coins;
+ struct TALER_EXCHANGE_AgeWithdrawCoinInput *coin_inputs;
+
+ /**
+ * The output state of @e num_coins coins, calculated during the
+ * "age-withdraw" operation.
+ */
+ struct CoinOutputState *coin_outputs;
+
+ /**
+ * The index returned by the exchange for the "age-withdraw" operation,
+ * of the kappa coin candidates that we do not disclose and keep.
+ */
+ uint8_t noreveal_index;
+
+ /**
+ * The blinded hashes of the non-revealed (to keep) @e num_coins coins.
+ */
+ const struct TALER_BlindedCoinHashP *blinded_coin_hs;
+
+ /**
+ * The hash of the commitment, needed for the reveal step.
+ */
+ struct TALER_AgeWithdrawCommitmentHashP h_commitment;
+
+ /**
+ * Set to the KYC requirement payto hash *if* the exchange replied with a
+ * request for KYC.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * Set to the KYC requirement row *if* the exchange replied with
+ * a request for KYC.
+ */
+ uint64_t requirement_row;
};
+/**
+ * Callback for the "age-withdraw" ooperation; It checks that the response
+ * code is expected and store the exchange signature in the state.
+ *
+ * @param cls Closure of type `struct AgeWithdrawState *`
+ * @param awr Repsonse details
+ */
+static void
+age_withdraw_cb (
+ void *cls,
+ const struct TALER_EXCHANGE_AgeWithdrawResponse *response)
+{
+ struct AgeWithdrawState *aws = cls;
+ struct TALER_TESTING_Interpreter *is = aws->is;
+
+ aws->handle = NULL;
+ if (aws->expected_response_code != response->hr.http_status)
+ {
+ TALER_TESTING_unexpected_status_with_body (is,
+ response->hr.http_status,
+ aws->expected_response_code,
+ response->hr.reply);
+ return;
+ }
+
+ switch (response->hr.http_status)
+ {
+ case MHD_HTTP_OK:
+ aws->noreveal_index = response->details.ok.noreveal_index;
+ aws->h_commitment = response->details.ok.h_commitment;
+
+ GNUNET_assert (aws->num_coins == response->details.ok.num_coins);
+ for (size_t n = 0; n < aws->num_coins; n++)
+ {
+ aws->coin_outputs[n].details = response->details.ok.coin_details[n];
+ TALER_age_commitment_proof_deep_copy (
+ &response->details.ok.coin_details[n].age_commitment_proof,
+ &aws->coin_outputs[n].details.age_commitment_proof);
+ }
+ aws->blinded_coin_hs = response->details.ok.blinded_coin_hs;
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ case MHD_HTTP_NOT_FOUND:
+ case MHD_HTTP_GONE:
+ /* nothing to check */
+ break;
+ case MHD_HTTP_CONFLICT:
+ /* TODO[oec]: Add this to the response-type and handle it here */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Age withdraw test command does not YET support status code %u\n",
+ response->hr.http_status);
+ break;
+ case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+ /* TODO[oec]: Add this to response-type and handle it here */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Age withdraw test command does not YET support status code %u\n",
+ response->hr.http_status);
+ break;
+ default:
+ /* Unsupported status code (by test harness) */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Age withdraw test command does not support status code %u\n",
+ response->hr.http_status);
+ GNUNET_break (0);
+ break;
+ }
+
+ /* We are done with this command, pick the next one */
+ TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Run the command for age-withdraw.
+ */
+static void
+age_withdraw_run (
+ void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct AgeWithdrawState *aws = cls;
+ struct TALER_EXCHANGE_Keys *keys = TALER_TESTING_get_keys (is);
+ const struct TALER_ReservePrivateKeyP *rp;
+ const struct TALER_TESTING_Command *create_reserve;
+ const struct TALER_EXCHANGE_DenomPublicKey *dpk;
+
+ aws->is = is;
+
+ /* Prepare the reserve related data */
+ create_reserve
+ = TALER_TESTING_interpreter_lookup_command (
+ is,
+ aws->reserve_reference);
+
+ if (NULL == create_reserve)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_reserve_priv (create_reserve,
+ &rp))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (NULL == aws->exchange_url)
+ aws->exchange_url
+ = GNUNET_strdup (TALER_TESTING_get_exchange_url (is));
+ aws->reserve_priv = *rp;
+ GNUNET_CRYPTO_eddsa_key_get_public (&aws->reserve_priv.eddsa_priv,
+ &aws->reserve_pub.eddsa_pub);
+ aws->reserve_payto_uri
+ = TALER_reserve_make_payto (aws->exchange_url,
+ &aws->reserve_pub);
+
+ aws->coin_inputs = GNUNET_new_array (
+ aws->num_coins,
+ struct TALER_EXCHANGE_AgeWithdrawCoinInput);
+
+ for (unsigned int i = 0; i<aws->num_coins; i++)
+ {
+ struct TALER_EXCHANGE_AgeWithdrawCoinInput *input = &aws->coin_inputs[i];
+ struct CoinOutputState *cos = &aws->coin_outputs[i];
+
+ /* randomly create the secrets for the kappa coin-candidates */
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &input->secrets,
+ sizeof(input->secrets));
+ /* Find denomination */
+ dpk = TALER_TESTING_find_pk (keys,
+ &cos->amount,
+ true); /* _always_ use denominations with age-striction */
+ if (NULL == dpk)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to determine denomination key at %s\n",
+ (NULL != cmd) ? cmd->label : "<retried command>");
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ /* We copy the denomination key, as re-querying /keys
+ * would free the old one. */
+ input->denom_pub = TALER_EXCHANGE_copy_denomination_key (dpk);
+ cos->reserve_history.type = TALER_EXCHANGE_RTT_AGEWITHDRAWAL;
+ GNUNET_assert (0 <=
+ TALER_amount_add (&cos->reserve_history.amount,
+ &cos->amount,
+ &input->denom_pub->fees.withdraw));
+ cos->reserve_history.details.withdraw.fee = input->denom_pub->fees.withdraw;
+ }
+
+ /* Execute the age-withdraw protocol */
+ aws->handle =
+ TALER_EXCHANGE_age_withdraw (
+ TALER_TESTING_interpreter_get_context (is),
+ keys,
+ TALER_TESTING_get_exchange_url (is),
+ rp,
+ aws->num_coins,
+ aws->coin_inputs,
+ aws->max_age,
+ &age_withdraw_cb,
+ aws);
+
+ if (NULL == aws->handle)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+}
+
+
+/**
+ * Free the state of a "age withdraw" CMD, and possibly cancel a
+ * pending operation thereof
+ *
+ * @param cls Closure of type `struct AgeWithdrawState`
+ * @param cmd The command beeing freed.
+ */
+static void
+age_withdraw_cleanup (
+ void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct AgeWithdrawState *aws = cls;
+
+ if (NULL != aws->handle)
+ {
+ TALER_TESTING_command_incomplete (aws->is,
+ cmd->label);
+ TALER_EXCHANGE_age_withdraw_cancel (aws->handle);
+ aws->handle = NULL;
+ }
+
+ for (size_t n = 0; n < aws->num_coins; n++)
+ {
+ struct TALER_EXCHANGE_AgeWithdrawCoinInput *in = &aws->coin_inputs[n];
+ struct CoinOutputState *out = &aws->coin_outputs[n];
+
+ if (NULL != in->denom_pub)
+ {
+ TALER_EXCHANGE_destroy_denomination_key (in->denom_pub);
+ in->denom_pub = NULL;
+ }
+ TALER_age_commitment_proof_free (&out->details.age_commitment_proof);
+ }
+ GNUNET_free (aws->coin_inputs);
+ GNUNET_free (aws->coin_outputs);
+ GNUNET_free (aws->exchange_url);
+ GNUNET_free (aws->reserve_payto_uri);
+ GNUNET_free (aws);
+}
+
+
+/**
+ * Offer internal data of a "age withdraw" CMD state to other commands.
+ *
+ * @param cls Closure of type `struct AgeWithdrawState`
+ * @param[out] ret result (could be anything)
+ * @param trait name of the trait
+ * @param idx index number of the object to offer.
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+age_withdraw_traits (
+ void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int idx)
+{
+ struct AgeWithdrawState *aws = cls;
+ uint8_t k = aws->noreveal_index;
+ struct TALER_EXCHANGE_AgeWithdrawCoinInput *in = &aws->coin_inputs[idx];
+ struct CoinOutputState *out = &aws->coin_outputs[idx];
+ struct TALER_EXCHANGE_AgeWithdrawCoinPrivateDetails *details =
+ &aws->coin_outputs[idx].details;
+ struct TALER_TESTING_Trait traits[] = {
+ /* history entry MUST be first due to response code logic below! */
+ TALER_TESTING_make_trait_reserve_history (idx,
+ &out->reserve_history),
+ TALER_TESTING_make_trait_denom_pub (idx,
+ in->denom_pub),
+ TALER_TESTING_make_trait_reserve_priv (&aws->reserve_priv),
+ TALER_TESTING_make_trait_reserve_pub (&aws->reserve_pub),
+ TALER_TESTING_make_trait_amounts (idx,
+ &out->amount),
+ /* TODO[oec]: add legal requirement to response and handle it here, as well
+ TALER_TESTING_make_trait_legi_requirement_row (&aws->requirement_row),
+ TALER_TESTING_make_trait_h_payto (&aws->h_payto),
+ */
+ TALER_TESTING_make_trait_h_blinded_coin (idx,
+ &aws->blinded_coin_hs[idx]),
+ TALER_TESTING_make_trait_payto_uri (aws->reserve_payto_uri),
+ TALER_TESTING_make_trait_exchange_url (aws->exchange_url),
+ TALER_TESTING_make_trait_coin_priv (idx,
+ &details->coin_priv),
+ TALER_TESTING_make_trait_planchet_secrets (idx,
+ &in->secrets[k]),
+ TALER_TESTING_make_trait_blinding_key (idx,
+ &details->blinding_key),
+ TALER_TESTING_make_trait_exchange_wd_value (idx,
+ &details->alg_values),
+ TALER_TESTING_make_trait_age_commitment_proof (
+ idx,
+ &details->age_commitment_proof),
+ TALER_TESTING_make_trait_h_age_commitment (
+ idx,
+ &details->h_age_commitment),
+ };
+
+ if (idx >= aws->num_coins)
+ return GNUNET_NO;
+
+ return TALER_TESTING_get_trait ((aws->expected_response_code == MHD_HTTP_OK)
+ ? &traits[0] /* we have reserve history */
+ : &traits[1], /* skip reserve history */
+ ret,
+ trait,
+ idx);
+}
+
struct TALER_TESTING_Command
TALER_TESTING_cmd_age_withdraw (const char *label,
@@ -89,55 +462,27 @@ TALER_TESTING_cmd_age_withdraw (const char *label,
va_list ap;
aws = GNUNET_new (struct AgeWithdrawState);
- aws->max_age = max_age;
aws->reserve_reference = reserve_reference;
aws->expected_response_code = expected_response_code;
-
-// TODO[oec]: check max_age!?
+ aws->mask = TALER_extensions_get_age_restriction_mask ();
+ aws->max_age = TALER_get_lowest_age (&aws->mask, max_age);
cnt = 1;
va_start (ap, amount);
while (NULL != (va_arg (ap, const char *)))
cnt++;
aws->num_coins = cnt;
- aws->coins = GNUNET_new_array (cnt,
- struct CoinState);
+ aws->coin_outputs = GNUNET_new_array (cnt,
+ struct CoinOutputState);
va_end (ap);
va_start (ap, amount);
+
for (unsigned int i = 0; i<aws->num_coins; i++)
{
- struct CoinState *cs = &aws->coins[i];
- if (0 < max_age)
- {
- struct GNUNET_HashCode seed;
- struct TALER_AgeMask mask;
-
- mask = TALER_extensions_get_age_restriction_mask ();
- GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
- &seed,
- sizeof(seed));
-
- if (GNUNET_OK !=
- TALER_age_restriction_commit (
- &mask,
- max_age,
- &seed,
- &cs->details.age_commitment_proof))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to generate age commitment for age %d at %s\n",
- max_age,
- label);
- GNUNET_assert (0);
- }
-
- TALER_age_commitment_hash (&cs->details.age_commitment_proof.commitment,
- &cs->details.h_age_commitment);
- }
-
+ struct CoinOutputState *out = &aws->coin_outputs[i];
if (GNUNET_OK !=
TALER_string_to_amount (amount,
- &cs->amount))
+ &out->amount))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to parse amount `%s' at %s\n",
@@ -145,9 +490,10 @@ TALER_TESTING_cmd_age_withdraw (const char *label,
label);
GNUNET_assert (0);
}
- /* move on to next vararg! */
- amount = va_arg (ap, const char *);
}
+
+ /* move on to next vararg! */
+ amount = va_arg (ap, const char *);
GNUNET_assert (NULL == amount);
va_end (ap);
@@ -155,9 +501,9 @@ TALER_TESTING_cmd_age_withdraw (const char *label,
struct TALER_TESTING_Command cmd = {
.cls = aws,
.label = label,
- .run = NULL, // &age_withdraw_run,
- .cleanup = NULL, // &age_withdraw_cleanup,
- .traits = NULL, // &age_withdraw_traits
+ .run = &age_withdraw_run,
+ .cleanup = &age_withdraw_cleanup,
+ .traits = &age_withdraw_traits,
};
return cmd;
@@ -165,4 +511,237 @@ TALER_TESTING_cmd_age_withdraw (const char *label,
}
+/**
+ * The state for the age-withdraw-reveal operation
+ */
+struct AgeWithdrawRevealState
+{
+ /**
+ * The reference to the CMD resembling the previous call to age-withdraw
+ */
+ const char *age_withdraw_reference;
+
+ /**
+ * The state to the previous age-withdraw command
+ */
+ const struct AgeWithdrawState *aws;
+
+ /**
+ * The expected response code from the call to the
+ * age-withdraw-reveal operation
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * Interpreter state (during command)
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * The handle to the reveal-operation
+ */
+ struct TALER_EXCHANGE_AgeWithdrawRevealHandle *handle;
+
+
+ /**
+ * Number of coins, extracted form the age withdraw command
+ */
+ size_t num_coins;
+
+ /**
+ * The signatures of the @e num_coins coins returned
+ */
+ struct TALER_DenominationSignature *denom_sigs;
+
+};
+
+/*
+ * Callback for the reveal response
+ *
+ * @param cls Closure of type `struct AgeWithdrawRevealState`
+ * @param awr The response
+ */
+static void
+age_withdraw_reveal_cb (
+ void *cls,
+ const struct TALER_EXCHANGE_AgeWithdrawRevealResponse *response)
+{
+ struct AgeWithdrawRevealState *awrs = cls;
+ struct TALER_TESTING_Interpreter *is = awrs->is;
+
+ awrs->handle = NULL;
+ if (awrs->expected_response_code != response->hr.http_status)
+ {
+ TALER_TESTING_unexpected_status_with_body (is,
+ response->hr.http_status,
+ awrs->expected_response_code,
+ response->hr.reply);
+ return;
+ }
+ switch (response->hr.http_status)
+ {
+ case MHD_HTTP_OK:
+ {
+ const struct AgeWithdrawState *aws = awrs->aws;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Got age-withdraw reveal success!\n");
+ GNUNET_assert (awrs->num_coins == response->details.ok.num_sigs);
+ for (size_t n = 0; n < awrs->num_coins; n++)
+ TALER_denom_sig_unblind (&awrs->denom_sigs[n],
+ &response->details.ok.blinded_denom_sigs[n],
+ &aws->coin_outputs[n].details.blinding_key,
+ &aws->coin_outputs[n].details.h_coin_pub,
+ &aws->coin_outputs[n].details.alg_values,
+ &aws->coin_inputs[n].denom_pub->key);
+ }
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ case MHD_HTTP_FORBIDDEN:
+ /* nothing to check */
+ break;
+ /* TODO[oec]: handle more cases !? */
+ default:
+ /* Unsupported status code (by test harness) */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Age withdraw reveal test command does not support status code %u\n",
+ response->hr.http_status);
+ GNUNET_break (0);
+ break;
+ }
+
+ /* We are done with this command, pick the next one */
+ TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Run the command for age-withdraw-reveal
+ */
+static void
+age_withdraw_reveal_run (
+ void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct AgeWithdrawRevealState *awrs = cls;
+ const struct TALER_TESTING_Command *age_withdraw_cmd;
+ const struct AgeWithdrawState *aws;
+
+ (void) cmd;
+ awrs->is = is;
+
+ /*
+ * Get the command and state for the previous call to "age witdraw"
+ */
+ age_withdraw_cmd =
+ TALER_TESTING_interpreter_get_command (is,
+ awrs->age_withdraw_reference);
+ if (NULL == age_withdraw_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ }
+ GNUNET_assert (age_withdraw_cmd->run == age_withdraw_run);
+ aws = age_withdraw_cmd->cls;
+ awrs->aws = aws;
+ awrs->num_coins = aws->num_coins;
+
+ awrs->handle =
+ TALER_EXCHANGE_age_withdraw_reveal (
+ TALER_TESTING_interpreter_get_context (is),
+ TALER_TESTING_get_exchange_url (is),
+ aws->num_coins,
+ aws->coin_inputs,
+ aws->noreveal_index,
+ &aws->h_commitment,
+ age_withdraw_reveal_cb,
+ awrs);
+}
+
+
+/**
+ * Free the state of a "age-withdraw-reveal" CMD, and possibly
+ * cancel a pending operation thereof
+ *
+ * @param cls Closure of type `struct AgeWithdrawRevealState`
+ * @param cmd The command being freed.
+ */
+static void
+age_withdraw_reveal_cleanup (
+ void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct AgeWithdrawRevealState *awrs = cls;
+
+ if (NULL != awrs->handle)
+ {
+ TALER_TESTING_command_incomplete (awrs->is,
+ cmd->label);
+ TALER_EXCHANGE_age_withdraw_reveal_cancel (awrs->handle);
+ awrs->handle = NULL;
+ }
+ GNUNET_free (awrs->denom_sigs);
+ awrs->denom_sigs = NULL;
+ GNUNET_free (awrs);
+}
+
+
+/**
+ * Offer internal data of a "age withdraw reveal" CMD state to other commands.
+ *
+ * @param cls Closure of they `struct AgeWithdrawRevealState`
+ * @param[out] ret result (could be anything)
+ * @param trait name of the trait
+ * @param idx index number of the object to offer.
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+age_withdraw_reveal_traits (
+ void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int idx)
+{
+ struct AgeWithdrawRevealState *awrs = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_denom_sig (idx,
+ &awrs->denom_sigs[idx]),
+ /* FIXME: shall we provide the traits from the previous
+ * call to "age withdraw" as well? */
+ };
+
+ if (idx >= awrs->num_coins)
+ return GNUNET_NO;
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ idx);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_age_withdraw_reveal (
+ const char *label,
+ const char *age_withdraw_reference,
+ unsigned int expected_response_code)
+{
+ struct AgeWithdrawRevealState *awrs =
+ GNUNET_new (struct AgeWithdrawRevealState);
+
+ awrs->age_withdraw_reference = age_withdraw_reference;
+ awrs->expected_response_code = expected_response_code;
+
+ struct TALER_TESTING_Command cmd = {
+ .cls = awrs,
+ .label = label,
+ .run = age_withdraw_reveal_run,
+ .cleanup = age_withdraw_reveal_cleanup,
+ .traits = age_withdraw_reveal_traits,
+ };
+
+ return cmd;
+}
+
+
/* end of testing_api_cmd_age_withdraw.c */