From 54fc83ee6b910d482948c6ec8185df7aab1b0cb1 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Fri, 11 Jan 2019 21:27:34 +0100 Subject: fix cyclic dependency by combining exchange-lib and auditor-lib directories --- src/lib/testing_api_cmd_refresh.c | 1317 +++++++++++++++++++++++++++++++++++++ 1 file changed, 1317 insertions(+) create mode 100644 src/lib/testing_api_cmd_refresh.c (limited to 'src/lib/testing_api_cmd_refresh.c') diff --git a/src/lib/testing_api_cmd_refresh.c b/src/lib/testing_api_cmd_refresh.c new file mode 100644 index 000000000..55900cf27 --- /dev/null +++ b/src/lib/testing_api_cmd_refresh.c @@ -0,0 +1,1317 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3, or (at your + option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + +*/ + +/** + * @file exchange-lib/testing_api_cmd_refresh.c + * @brief commands for testing all "refresh" features. + * @author Marcello Stanisci + */ + +#include "platform.h" +#include "taler_json_lib.h" +#include +#include "exchange_api_handle.h" +#include "taler_testing_lib.h" +#include "taler_signatures.h" +#include "backoff.h" + +/** + * Data for a coin to be melted. + */ +struct MeltDetails +{ + + /** + * Amount to melt (including fee). + */ + const char *amount; + + /** + * Reference to reserve_withdraw operations for coin to + * be used for the /refresh/melt operation. + */ + const char *coin_reference; +}; + + +/** + * State for a "refresh melt" command. + */ +struct RefreshMeltState +{ + + /** + * Information about coins to be melted. + */ + struct MeltDetails melted_coin; + + /** + * "Crypto data" used in the refresh operation. + */ + char *refresh_data; + + /** + * Reference to a previous melt command. + */ + const char *melt_reference; + + /** + * Melt handle while operation is running. + */ + struct TALER_EXCHANGE_RefreshMeltHandle *rmh; + + /** + * Interpreter state. + */ + struct TALER_TESTING_Interpreter *is; + + /** + * Array of the denomination public keys + * corresponding to the @e fresh_amounts. + */ + struct TALER_EXCHANGE_DenomPublicKey *fresh_pks; + + /** + * Task scheduled to try later. + */ + struct GNUNET_SCHEDULER_Task *retry_task; + + /** + * How long do we wait until we retry? + */ + struct GNUNET_TIME_Relative backoff; + + /** + * Number of bytes in @e refresh_data. + */ + size_t refresh_data_length; + + /** + * Expected HTTP response code. + */ + unsigned int expected_response_code; + + /** + * if set to #GNUNET_YES, then two /refresh/melt operations + * will be performed. This is needed to trigger the logic + * that manages those already-made requests. Note: it + * is not possible to just copy-and-paste a test refresh melt + * CMD to have the same effect, because every data preparation + * generates new planchets that (in turn) make the whole "hash" + * different from any previous one, therefore NOT allowing the + * exchange to pick any previous /rerfesh/melt operation from + * the database. + */ + unsigned int double_melt; + + /** + * Should we retry on (transient) failures? + */ + int do_retry; + + /** + * Set by the melt callback as it comes from the exchange. + */ + uint16_t noreveal_index; +}; + + +/** + * State for a "refresh reveal" CMD. + */ +struct RefreshRevealState +{ + /** + * Link to a "refresh melt" command. + */ + const char *melt_reference; + + /** + * Reveal handle while operation is running. + */ + struct TALER_EXCHANGE_RefreshRevealHandle *rrh; + + /** + * Convenience struct to keep in one place all the + * data related to one fresh coin, set by the reveal callback + * as it comes from the exchange. + */ + struct FreshCoin *fresh_coins; + + /** + * Interpreter state. + */ + struct TALER_TESTING_Interpreter *is; + + /** + * Task scheduled to try later. + */ + struct GNUNET_SCHEDULER_Task *retry_task; + + /** + * How long do we wait until we retry? + */ + struct GNUNET_TIME_Relative backoff; + + /** + * Number of fresh coins withdrawn, set by the + * reveal callback as it comes from the exchange, + * it is the length of the @e fresh_coins array. + */ + unsigned int num_fresh_coins; + + /** + * Expected HTTP response code. + */ + unsigned int expected_response_code; + + /** + * Should we retry on (transient) failures? + */ + int do_retry; + +}; + + +/** + * State for a "refresh link" CMD. + */ +struct RefreshLinkState +{ + /** + * Link to a "refresh reveal" command. + */ + const char *reveal_reference; + + /** + * Handle to the ongoing operation. + */ + struct TALER_EXCHANGE_RefreshLinkHandle *rlh; + + /** + * Interpreter state. + */ + struct TALER_TESTING_Interpreter *is; + + /** + * Task scheduled to try later. + */ + struct GNUNET_SCHEDULER_Task *retry_task; + + /** + * How long do we wait until we retry? + */ + struct GNUNET_TIME_Relative backoff; + + /** + * Expected HTTP response code. + */ + unsigned int expected_response_code; + + /** + * Should we retry on (transient) failures? + */ + int do_retry; + +}; + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +refresh_reveal_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is); + + +/** + * Task scheduled to re-try #refresh_reveal_run. + * + * @param cls a `struct RefreshRevealState` + */ +static void +do_reveal_retry (void *cls) +{ + struct RefreshRevealState *rrs = cls; + + rrs->retry_task = NULL; + refresh_reveal_run (rrs, + NULL, + rrs->is); +} + + +/** + * "refresh reveal" request callback; it checks that the response + * code is expected and copies into its command's state the data + * coming from the exchange, namely the fresh coins. + * + * @param cls closure. + * @param http_status HTTP response code. + * @param ec taler-specific error code. + * @param num_coins number of fresh coins created, length of the + * @a sigs and @a coin_privs arrays, 0 if the operation + * failed. + * @param coin_privs array of @a num_coins private keys for the + * coins that were created, NULL on error. + * @param sigs array of signature over @a num_coins coins, + * NULL on error. + * @param full_response raw exchange response. + */ +static void +reveal_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + unsigned int num_coins, + const struct TALER_CoinSpendPrivateKeyP *coin_privs, + const struct TALER_DenominationSignature *sigs, + const json_t *full_response) +{ + struct RefreshRevealState *rrs = cls; + const struct TALER_TESTING_Command *melt_cmd; + + rrs->rrh = NULL; + if (rrs->expected_response_code != http_status) + { + if (GNUNET_YES == rrs->do_retry) + { + if ( (0 == http_status) || + (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) || + (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Retrying refresh reveal failed with %u/%d\n", + http_status, + (int) ec); + /* on DB conflicts, do not use backoff */ + if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) + rrs->backoff = GNUNET_TIME_UNIT_ZERO; + else + rrs->backoff = EXCHANGE_LIB_BACKOFF (rrs->backoff); + rrs->retry_task = GNUNET_SCHEDULER_add_delayed (rrs->backoff, + &do_reveal_retry, + rrs); + return; + } + } + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d to command %s in %s:%u\n", + http_status, + (int) ec, + rrs->is->commands[rrs->is->ip].label, + __FILE__, + __LINE__); + json_dumpf (full_response, stderr, 0); + TALER_TESTING_interpreter_fail (rrs->is); + return; + } + melt_cmd = TALER_TESTING_interpreter_lookup_command + (rrs->is, rrs->melt_reference); + if (NULL == melt_cmd) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rrs->is); + return; + } + rrs->num_fresh_coins = num_coins; + switch (http_status) + { + case MHD_HTTP_OK: + rrs->fresh_coins = GNUNET_new_array + (num_coins, struct FreshCoin); + + const struct TALER_EXCHANGE_DenomPublicKey *fresh_pks; + + if (GNUNET_OK != + TALER_TESTING_get_trait_denom_pub (melt_cmd, + 0, + &fresh_pks)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rrs->is); + return; + } + + for (unsigned int i=0; ifresh_coins[i]; + + fc->pk = &fresh_pks[i]; + fc->coin_priv = coin_privs[i]; + fc->sig.rsa_signature = GNUNET_CRYPTO_rsa_signature_dup + (sigs[i].rsa_signature); + } + break; + default: + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Unknown HTTP status %d\n", + http_status); + } + TALER_TESTING_interpreter_next (rrs->is); +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +refresh_reveal_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct RefreshRevealState *rrs = cls; + struct RefreshMeltState *rms; + const struct TALER_TESTING_Command *melt_cmd; + + rrs->is = is; + melt_cmd = TALER_TESTING_interpreter_lookup_command + (is, rrs->melt_reference); + + if (NULL == melt_cmd) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rrs->is); + return; + } + rms = melt_cmd->cls; + rrs->rrh = TALER_EXCHANGE_refresh_reveal + (is->exchange, + rms->refresh_data_length, + rms->refresh_data, + rms->noreveal_index, + &reveal_cb, rrs); + + if (NULL == rrs->rrh) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } +} + + +/** + * Free the state from a "refresh reveal" CMD, and possibly + * cancel a pending operation thereof. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +static void +refresh_reveal_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct RefreshRevealState *rrs = cls; + + if (NULL != rrs->rrh) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %u (%s) did not complete\n", + rrs->is->ip, + cmd->label); + + TALER_EXCHANGE_refresh_reveal_cancel (rrs->rrh); + rrs->rrh = NULL; + } + if (NULL != rrs->retry_task) + { + GNUNET_SCHEDULER_cancel (rrs->retry_task); + rrs->retry_task = NULL; + } + + for (unsigned int j=0; j < rrs->num_fresh_coins; j++) + GNUNET_CRYPTO_rsa_signature_free (rrs->fresh_coins[j].sig.rsa_signature); + + GNUNET_free_non_null (rrs->fresh_coins); + rrs->fresh_coins = NULL; + rrs->num_fresh_coins = 0; + GNUNET_free (rrs); +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +refresh_link_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is); + + +/** + * Task scheduled to re-try #refresh_link_run. + * + * @param cls a `struct RefreshLinkState` + */ +static void +do_link_retry (void *cls) +{ + struct RefreshLinkState *rls = cls; + + rls->retry_task = NULL; + refresh_link_run (rls, + NULL, + rls->is); +} + + +/** + * "refresh link" operation callback, checks that HTTP response + * code is expected _and_ that all the linked coins were actually + * withdrawn by the "refresh reveal" CMD. + * + * @param cls closure. + * @param http_status HTTP response code. + * @param ec taler-specific error code + * @param num_coins number of fresh coins created, length of the + * @a sigs and @a coin_privs arrays, 0 if the operation + * failed. + * @param coin_privs array of @a num_coins private keys for the + * coins that were created, NULL on error. + * @param sigs array of signature over @a num_coins coins, NULL on + * error. + * @param pubs array of public keys for the @a sigs, + * NULL on error. + * @param full_response raw response from the exchange. + */ +static void +link_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + unsigned int num_coins, + const struct TALER_CoinSpendPrivateKeyP *coin_privs, + const struct TALER_DenominationSignature *sigs, + const struct TALER_DenominationPublicKey *pubs, + const json_t *full_response) +{ + + struct RefreshLinkState *rls = cls; + const struct TALER_TESTING_Command *reveal_cmd; + struct TALER_TESTING_Command *link_cmd + = &rls->is->commands[rls->is->ip]; + unsigned int found; + const unsigned int *num_fresh_coins; + + rls->rlh = NULL; + if (rls->expected_response_code != http_status) + { + if (GNUNET_YES == rls->do_retry) + { + if ( (0 == http_status) || + (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) || + (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Retrying refresh link failed with %u/%d\n", + http_status, + (int) ec); + /* on DB conflicts, do not use backoff */ + if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) + rls->backoff = GNUNET_TIME_UNIT_ZERO; + else + rls->backoff = EXCHANGE_LIB_BACKOFF (rls->backoff); + rls->retry_task = GNUNET_SCHEDULER_add_delayed (rls->backoff, + &do_link_retry, + rls); + return; + } + } + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d to command %s in %s:%u\n", + http_status, + (int) ec, + link_cmd->label, + __FILE__, + __LINE__); + json_dumpf (full_response, stderr, 0); + TALER_TESTING_interpreter_fail (rls->is); + return; + } + reveal_cmd = TALER_TESTING_interpreter_lookup_command + (rls->is, rls->reveal_reference); + + if (NULL == reveal_cmd) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rls->is); + return; + } + + switch (http_status) + { + case MHD_HTTP_OK: + /* check that number of coins returned matches */ + if (GNUNET_OK != TALER_TESTING_get_trait_uint + (reveal_cmd, 0, &num_fresh_coins)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rls->is); + return; + } + if (num_coins != *num_fresh_coins) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected number of fresh coins: %d vs %d in %s:%u\n", + num_coins, + *num_fresh_coins, + __FILE__, + __LINE__); + TALER_TESTING_interpreter_fail (rls->is); + return; + } + /* check that the coins match */ + for (unsigned int i=0;iis); + return; + } + + for (unsigned int i=0;ikey.rsa_public_key, + pubs[j].rsa_public_key)) ) + { + found++; + break; + } + } + if (found != num_coins) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Only %u/%u coins match expectations\n", + found, num_coins); + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rls->is); + return; + } + break; + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unknown HTTP response code %u.\n", + http_status); + } + TALER_TESTING_interpreter_next (rls->is); +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +refresh_link_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct RefreshLinkState *rls = cls; + struct RefreshRevealState *rrs; + struct RefreshMeltState *rms; + const struct TALER_TESTING_Command *reveal_cmd; + const struct TALER_TESTING_Command *melt_cmd; + const struct TALER_TESTING_Command *coin_cmd; + rls->is = is; + + reveal_cmd = TALER_TESTING_interpreter_lookup_command + (rls->is, rls->reveal_reference); + + if (NULL == reveal_cmd) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rls->is); + return; + } + rrs = reveal_cmd->cls; + melt_cmd = TALER_TESTING_interpreter_lookup_command + (rls->is, rrs->melt_reference); + + if (NULL == melt_cmd) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rls->is); + return; + } + + /* find reserve_withdraw command */ + { + const struct MeltDetails *md; + + rms = melt_cmd->cls; + md = &rms->melted_coin; + coin_cmd = TALER_TESTING_interpreter_lookup_command + (rls->is, md->coin_reference); + if (NULL == coin_cmd) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rls->is); + return; + } + } + + const struct TALER_CoinSpendPrivateKeyP *coin_priv; + if (GNUNET_OK != TALER_TESTING_get_trait_coin_priv + (coin_cmd, 0, &coin_priv)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rls->is); + return; + } + + /* finally, use private key from withdraw sign command */ + rls->rlh = TALER_EXCHANGE_refresh_link + (is->exchange, coin_priv, &link_cb, rls); + + if (NULL == rls->rlh) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rls->is); + return; + } +} + + +/** + * Free the state of the "refresh link" CMD, and possibly + * cancel a operation thereof. + * + * @param cls closure + * @param cmd the command which is being cleaned up. + */ +static void +refresh_link_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct RefreshLinkState *rls = cls; + + if (NULL != rls->rlh) + { + + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %u (%s) did not complete\n", + rls->is->ip, + cmd->label); + TALER_EXCHANGE_refresh_link_cancel (rls->rlh); + rls->rlh = NULL; + } + if (NULL != rls->retry_task) + { + GNUNET_SCHEDULER_cancel (rls->retry_task); + rls->retry_task = NULL; + } + GNUNET_free (rls); +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +refresh_melt_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is); + + +/** + * Task scheduled to re-try #refresh_melt_run. + * + * @param cls a `struct RefreshMeltState` + */ +static void +do_melt_retry (void *cls) +{ + struct RefreshMeltState *rms = cls; + + rms->retry_task = NULL; + refresh_melt_run (rms, + NULL, + rms->is); +} + + +/** + * Callback for a "refresh melt" operation; checks if the HTTP + * response code is okay and re-run the melt operation if the + * CMD was set to do so. + * + * @param cls closure. + * @param http_status HTTP response code. + * @param ec taler-specific error code. + * @param noreveal_index choice by the exchange in the + * cut-and-choose protocol, UINT16_MAX on error. + * @param exchange_pub public key the exchange used for signing. + * @param full_response raw response body from the exchange. + */ +static void +melt_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + uint32_t noreveal_index, + const struct TALER_ExchangePublicKeyP *exchange_pub, + const json_t *full_response) +{ + struct RefreshMeltState *rms = cls; + + rms->rmh = NULL; + if (rms->expected_response_code != http_status) + { + if (GNUNET_YES == rms->do_retry) + { + if ( (0 == http_status) || + (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) || + (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Retrying refresh melt failed with %u/%d\n", + http_status, + (int) ec); + /* on DB conflicts, do not use backoff */ + if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) + rms->backoff = GNUNET_TIME_UNIT_ZERO; + else + rms->backoff = EXCHANGE_LIB_BACKOFF (rms->backoff); + rms->retry_task = GNUNET_SCHEDULER_add_delayed (rms->backoff, + &do_melt_retry, + rms); + return; + } + } + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d to command %s in %s:%u\n", + http_status, + (int) ec, + rms->is->commands[rms->is->ip].label, + __FILE__, + __LINE__); + json_dumpf (full_response, stderr, 0); + TALER_TESTING_interpreter_fail (rms->is); + return; + } + rms->noreveal_index = noreveal_index; + + if (GNUNET_YES == rms->double_melt) + { + TALER_LOG_DEBUG ("Doubling the melt (%s)\n", + rms->is->commands[rms->is->ip].label); + rms->rmh = TALER_EXCHANGE_refresh_melt + (rms->is->exchange, rms->refresh_data_length, + rms->refresh_data, &melt_cb, rms); + rms->double_melt = GNUNET_NO; + return; + } + TALER_TESTING_interpreter_next (rms->is); +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +refresh_melt_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct RefreshMeltState *rms = cls; + unsigned int num_fresh_coins; + const struct TALER_TESTING_Command *coin_command; + /* FIXME: this should be dynamic */ + const char *melt_fresh_amounts[] = { + "EUR:1", "EUR:1", "EUR:1", "EUR:0.1", + NULL}; + const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk; + + rms->is = is; + rms->noreveal_index = UINT16_MAX; + for (num_fresh_coins=0; + NULL != melt_fresh_amounts[num_fresh_coins]; + num_fresh_coins++) ; + + rms->fresh_pks = GNUNET_new_array + (num_fresh_coins, + struct TALER_EXCHANGE_DenomPublicKey); + { + const struct TALER_CoinSpendPrivateKeyP *melt_priv; + struct TALER_Amount melt_amount; + struct TALER_Amount fresh_amount; + const struct TALER_DenominationSignature *melt_sig; + const struct TALER_EXCHANGE_DenomPublicKey *melt_denom_pub; + + const struct MeltDetails *md = &rms->melted_coin; + if (NULL == (coin_command + = TALER_TESTING_interpreter_lookup_command + (is, md->coin_reference))) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rms->is); + return; + } + + if (GNUNET_OK != TALER_TESTING_get_trait_coin_priv + (coin_command, 0, &melt_priv)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rms->is); + return; + } + + if (GNUNET_OK != + TALER_string_to_amount (md->amount, + &melt_amount)) + { + GNUNET_break (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse amount `%s' at %u\n", + md->amount, + is->ip); + TALER_TESTING_interpreter_fail (rms->is); + return; + } + if (GNUNET_OK != + TALER_TESTING_get_trait_denom_sig (coin_command, + 0, + &melt_sig)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rms->is); + return; + } + if (GNUNET_OK != TALER_TESTING_get_trait_denom_pub + (coin_command, 0, &melt_denom_pub)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rms->is); + return; + } + + for (unsigned int i=0;iis); + return; + } + fresh_pk = TALER_TESTING_find_pk + (TALER_EXCHANGE_get_keys (is->exchange), &fresh_amount); + if (NULL == fresh_pk) + { + GNUNET_break (0); + /* Subroutine logs specific error */ + TALER_TESTING_interpreter_fail (rms->is); + return; + } + + rms->fresh_pks[i] = *fresh_pk; + } + rms->refresh_data = TALER_EXCHANGE_refresh_prepare + (melt_priv, &melt_amount, melt_sig, melt_denom_pub, + GNUNET_YES, num_fresh_coins, rms->fresh_pks, + &rms->refresh_data_length); + + if (NULL == rms->refresh_data) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rms->is); + return; + } + rms->rmh = TALER_EXCHANGE_refresh_melt + (is->exchange, rms->refresh_data_length, + rms->refresh_data, &melt_cb, rms); + + if (NULL == rms->rmh) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rms->is); + return; + } + } +} + + +/** + * Free the "refresh melt" CMD state, and possibly cancel a + * pending operation thereof. + * + * @param cls closure, typically a #struct RefreshMeltState. + * @param cmd the command which is being cleaned up. + */ +static void +refresh_melt_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct RefreshMeltState *rms = cls; + + if (NULL != rms->rmh) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %u (%s) did not complete\n", + rms->is->ip, rms->is->commands[rms->is->ip].label); + TALER_EXCHANGE_refresh_melt_cancel (rms->rmh); + rms->rmh = NULL; + } + if (NULL != rms->retry_task) + { + GNUNET_SCHEDULER_cancel (rms->retry_task); + rms->retry_task = NULL; + } + GNUNET_free_non_null (rms->fresh_pks); + rms->fresh_pks = NULL; + GNUNET_free_non_null (rms->refresh_data); + rms->refresh_data = NULL; + rms->refresh_data_length = 0; + GNUNET_free (rms); +} + + +/** + * Offer internal data to the "refresh melt" CMD. + * + * @param cls closure. + * @param ret[out] result (could be anything). + * @param trait name of the trait. + * @param index index number of the object to offer. + * + * @return #GNUNET_OK on success. + */ +static int +refresh_melt_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + struct RefreshMeltState *rms = cls; + + struct TALER_TESTING_Trait traits[] = { + TALER_TESTING_make_trait_denom_pub (0, rms->fresh_pks), + TALER_TESTING_trait_end () + }; + + return TALER_TESTING_get_trait (traits, + ret, + trait, + index); +} + + +/** + * Create a "refresh melt" command. + * + * @param label command label. + * @param amount amount to be melted. + * @param coin_reference reference to a command + * that will provide a coin to refresh. + * @param expected_response_code expected HTTP code. + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_refresh_melt + (const char *label, + const char *amount, + const char *coin_reference, + unsigned int expected_response_code) +{ + struct RefreshMeltState *rms; + struct MeltDetails md; + + md.coin_reference = coin_reference; + md.amount = amount; + + rms = GNUNET_new (struct RefreshMeltState); + rms->melted_coin = md; + rms->expected_response_code = expected_response_code; + + struct TALER_TESTING_Command cmd = { + .label = label, + .cls = rms, + .run = &refresh_melt_run, + .cleanup = &refresh_melt_cleanup, + .traits = &refresh_melt_traits + }; + + return cmd; +} + + +/** + * Create a "refresh melt" CMD that does TWO /refresh/melt + * requests. This was needed to test the replay of a valid melt + * request, see #5312. + * + * @param label command label + * @param exchange connection to the exchange + * @param amount amount to be melted. + * @param coin_reference reference to a command that will provide + * a coin to refresh + * @param expected_response_code expected HTTP code + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_refresh_melt_double + (const char *label, + const char *amount, + const char *coin_reference, + unsigned int expected_response_code) +{ + struct RefreshMeltState *rms; + struct MeltDetails md; + + md.coin_reference = coin_reference; + md.amount = amount; + + rms = GNUNET_new (struct RefreshMeltState); + rms->melted_coin = md; + rms->expected_response_code = expected_response_code; + rms->double_melt = GNUNET_YES; + + struct TALER_TESTING_Command cmd = { + .label = label, + .cls = rms, + .run = &refresh_melt_run, + .cleanup = &refresh_melt_cleanup, + .traits = &refresh_melt_traits + }; + + return cmd; +} + + +/** + * Modify a "refresh melt" command to enable retries. + * + * @param cmd command + * @return modified command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_refresh_melt_with_retry (struct TALER_TESTING_Command cmd) +{ + struct RefreshMeltState *rms; + + GNUNET_assert (&refresh_melt_run == cmd.run); + rms = cmd.cls; + rms->do_retry = GNUNET_YES; + return cmd; +} + + +/** + * Offer internal data from a "refresh reveal" CMD. + * + * @param cls closure. + * @param ret[out] result (could be anything). + * @param trait name of the trait. + * @param index index number of the object to offer. + * + * @return #GNUNET_OK on success. + */ +static int +refresh_reveal_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + struct RefreshRevealState *rrs = cls; + unsigned int num_coins = rrs->num_fresh_coins; +#define NUM_TRAITS ((num_coins * 3) + 3) + struct TALER_TESTING_Trait traits[NUM_TRAITS]; + + /* Making coin privs traits */ + for (unsigned int i=0; ifresh_coins[i].coin_priv); + + /* Making denom pubs traits */ + for (unsigned int i=0; ifresh_coins[i].pk); + + /* Making denom sigs traits */ + for (unsigned int i=0; ifresh_coins[i].sig); + + /* number of fresh coins */ + traits[(num_coins * 3)] = TALER_TESTING_make_trait_uint + (0, &rrs->num_fresh_coins); + + /* whole array of fresh coins */ + traits[(num_coins * 3) + 1] + = TALER_TESTING_make_trait_fresh_coins (0, rrs->fresh_coins), + + /* end of traits */ + traits[(num_coins * 3) + 2] = TALER_TESTING_trait_end (); + + return TALER_TESTING_get_trait (traits, + ret, + trait, + index); +} + + +/** + * Create a "refresh reveal" command. + * + * @param label command label. + * @param exchange connection to the exchange. + * @param melt_reference reference to a "refresh melt" command. + * @param expected_response_code expected HTTP response code. + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_refresh_reveal + (const char *label, + const char *melt_reference, + unsigned int expected_response_code) +{ + struct RefreshRevealState *rrs; + + rrs = GNUNET_new (struct RefreshRevealState); + rrs->melt_reference = melt_reference; + rrs->expected_response_code = expected_response_code; + + struct TALER_TESTING_Command cmd = { + .cls = rrs, + .label = label, + .run = &refresh_reveal_run, + .cleanup = &refresh_reveal_cleanup, + .traits = &refresh_reveal_traits + }; + + return cmd; +} + + +/** + * Modify a "refresh reveal" command to enable retries. + * + * @param cmd command + * @return modified command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_refresh_reveal_with_retry (struct TALER_TESTING_Command cmd) +{ + struct RefreshRevealState *rrs; + + GNUNET_assert (&refresh_reveal_run == cmd.run); + rrs = cmd.cls; + rrs->do_retry = GNUNET_YES; + return cmd; +} + + +/** + * Create a "refresh link" command. + * + * @param label command label. + * @param reveal_reference reference to a "refresh reveal" CMD. + * @param expected_response_code expected HTTP response code + * + * @return the "refresh link" command + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_refresh_link + (const char *label, + const char *reveal_reference, + unsigned int expected_response_code) +{ + struct RefreshLinkState *rrs; + + rrs = GNUNET_new (struct RefreshLinkState); + rrs->reveal_reference = reveal_reference; + rrs->expected_response_code = expected_response_code; + + struct TALER_TESTING_Command cmd = { + .cls = rrs, + .label = label, + .run = &refresh_link_run, + .cleanup = &refresh_link_cleanup + }; + + return cmd; +} + + +/** + * Modify a "refresh link" command to enable retries. + * + * @param cmd command + * @return modified command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_refresh_link_with_retry (struct TALER_TESTING_Command cmd) +{ + struct RefreshLinkState *rls; + + GNUNET_assert (&refresh_link_run == cmd.run); + rls = cmd.cls; + rls->do_retry = GNUNET_YES; + return cmd; +} -- cgit v1.2.3