summaryrefslogtreecommitdiff
path: root/src/testing/testing_api_cmd_refresh.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/testing/testing_api_cmd_refresh.c')
-rw-r--r--src/testing/testing_api_cmd_refresh.c1410
1 files changed, 1410 insertions, 0 deletions
diff --git a/src/testing/testing_api_cmd_refresh.c b/src/testing/testing_api_cmd_refresh.c
new file mode 100644
index 000000000..73b74dafb
--- /dev/null
+++ b/src/testing/testing_api_cmd_refresh.c
@@ -0,0 +1,1410 @@
+/*
+ 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
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file testing/testing_api_cmd_refresh.c
+ * @brief commands for testing all "refresh" features.
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+#include "taler_signatures.h"
+#include "backoff.h"
+
+
+/**
+ * Information about a fresh coin generated by the refresh
+ * operation.
+ */
+struct TALER_TESTING_FreshCoinData
+{
+
+ /**
+ * If @e amount is NULL, this specifies the denomination key to
+ * use. Otherwise, this will be set (by the interpreter) to the
+ * denomination PK matching @e amount.
+ */
+ const struct TALER_EXCHANGE_DenomPublicKey *pk;
+
+ /**
+ * Set (by the interpreter) to the exchange's signature over the
+ * coin's public key.
+ */
+ struct TALER_DenominationSignature sig;
+
+ /**
+ * Set (by the interpreter) to the coin's private key.
+ */
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+
+ /**
+ * The blinding key (needed for recoup operations).
+ */
+ struct TALER_DenominationBlindingKeyP blinding_key;
+
+};
+
+
+/**
+ * State for a "refresh melt" command.
+ */
+struct RefreshMeltState
+{
+
+ /**
+ * Reference to reserve_withdraw operations for coin to
+ * be used for the /refresh/melt operation.
+ */
+ const char *coin_reference;
+
+ /**
+ * "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 num_fresh_coins;
+ */
+ struct TALER_EXCHANGE_DenomPublicKey *fresh_pks;
+
+ /**
+ * Private key of the dirty coin being melted.
+ */
+ const struct TALER_CoinSpendPrivateKeyP *melt_priv;
+
+ /**
+ * 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;
+
+ /**
+ * Amounts to be generated during melt.
+ */
+ const char **melt_fresh_amounts;
+
+ /**
+ * Number of fresh coins generated by the melt.
+ */
+ unsigned int num_fresh_coins;
+
+ /**
+ * 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 TALER_TESTING_FreshCoinData *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_PlanchetSecretsP *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 TALER_TESTING_FreshCoinData);
+ for (unsigned int i = 0; i<num_coins; i++)
+ {
+ struct TALER_TESTING_FreshCoinData *fc = &rrs->fresh_coins[i];
+
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_denom_pub (melt_cmd,
+ i,
+ &fc->pk))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (rrs->is);
+ return;
+ }
+ fc->coin_priv = coin_privs[i].coin_priv;
+ fc->blinding_key = coin_privs[i].blinding_key;
+ 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; i<num_coins; i++)
+ for (unsigned int j = i + 1; j<num_coins; j++)
+ if (0 == GNUNET_memcmp
+ (&coin_privs[i], &coin_privs[j]))
+ GNUNET_break (0);
+ /* Note: coins might be legitimately permutated in here... */
+ found = 0;
+
+ /* Will point to the pointer inside the cmd state. */
+ const struct TALER_TESTING_FreshCoinData *fc = NULL;
+
+ if (GNUNET_OK != TALER_TESTING_get_trait_fresh_coins
+ (reveal_cmd, 0, &fc))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (rls->is);
+ return;
+ }
+
+ for (unsigned int i = 0; i<num_coins; i++)
+ for (unsigned int j = 0; j<num_coins; j++)
+ {
+ if ( (0 == GNUNET_memcmp
+ (&coin_privs[i], &fc[j].coin_priv)) &&
+ (0 == GNUNET_CRYPTO_rsa_signature_cmp
+ (fc[i].sig.rsa_signature,
+ sigs[j].rsa_signature)) &&
+ (0 == GNUNET_CRYPTO_rsa_public_key_cmp
+ (fc[i].pk->key.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 */
+ {
+ rms = melt_cmd->cls;
+ coin_cmd = TALER_TESTING_interpreter_lookup_command
+ (rls->is, rms->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 char *default_melt_fresh_amounts[] = {
+ "EUR:1", "EUR:1", "EUR:1", "EUR:0.1",
+ NULL
+ };
+ const char **melt_fresh_amounts;
+
+ if (NULL == (melt_fresh_amounts = rms->melt_fresh_amounts))
+ melt_fresh_amounts = default_melt_fresh_amounts;
+ rms->is = is;
+ rms->noreveal_index = UINT16_MAX;
+ for (num_fresh_coins = 0;
+ NULL != melt_fresh_amounts[num_fresh_coins];
+ num_fresh_coins++)
+ ;
+ rms->num_fresh_coins = num_fresh_coins;
+ rms->fresh_pks = GNUNET_new_array
+ (num_fresh_coins,
+ struct TALER_EXCHANGE_DenomPublicKey);
+ {
+ 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 TALER_TESTING_Command *coin_command;
+
+ if (NULL == (coin_command
+ = TALER_TESTING_interpreter_lookup_command
+ (is, rms->coin_reference)))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (rms->is);
+ return;
+ }
+
+ if (GNUNET_OK != TALER_TESTING_get_trait_coin_priv
+ (coin_command, 0, &rms->melt_priv))
+ {
+ GNUNET_break (0);
+ 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;
+ }
+ /* Melt amount starts with the melt fee of the old coin; we'll add the
+ values and withdraw fees of the fresh coins next */
+ melt_amount = melt_denom_pub->fee_refresh;
+ for (unsigned int i = 0; i<num_fresh_coins; i++)
+ {
+ const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk;
+
+ if (GNUNET_OK != TALER_string_to_amount
+ (melt_fresh_amounts[i], &fresh_amount))
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse amount `%s' at index %u\n",
+ melt_fresh_amounts[i], i);
+ TALER_TESTING_interpreter_fail (rms->is);
+ 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;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_add (&melt_amount,
+ &melt_amount,
+ &fresh_amount));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_add (&melt_amount,
+ &melt_amount,
+ &fresh_pk->fee_withdraw));
+ rms->fresh_pks[i] = *fresh_pk;
+ /* Make a deep copy of the RSA key */
+ rms->fresh_pks[i].key.rsa_public_key
+ = GNUNET_CRYPTO_rsa_public_key_dup (fresh_pk->key.rsa_public_key);
+ }
+ rms->refresh_data
+ = TALER_EXCHANGE_refresh_prepare (rms->melt_priv,
+ &melt_amount,
+ melt_sig,
+ melt_denom_pub,
+ 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, must be 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;
+ }
+ if (NULL != rms->fresh_pks)
+ {
+ for (unsigned int i = 0; i < rms->num_fresh_coins; i++)
+ GNUNET_CRYPTO_rsa_public_key_free (rms->fresh_pks[i].key.rsa_public_key);
+ }
+ 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_non_null (rms->melt_fresh_amounts);
+ GNUNET_free (rms);
+}
+
+
+/**
+ * Offer internal data to the "refresh melt" CMD.
+ *
+ * @param cls closure.
+ * @param[out] ret 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;
+
+ if (index >= rms->num_fresh_coins)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ {
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_denom_pub (index, &rms->fresh_pks[index]),
+ TALER_TESTING_make_trait_coin_priv (0, rms->melt_priv),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+ }
+}
+
+
+/**
+ * Parse list of amounts for melt operation.
+ *
+ * @param[in,out] rms where to store the list
+ * @param ap NULL-termianted list of amounts to be melted (one per fresh coin)
+ * @return #GNUNET_OK on success
+ */
+static int
+parse_amounts (struct RefreshMeltState *rms,
+ va_list ap)
+{
+ unsigned int len;
+ unsigned int off;
+ const char *amount;
+
+ len = 0;
+ off = 0;
+ while (NULL != (amount = va_arg (ap, const char *)))
+ {
+ if (len == off)
+ {
+ struct TALER_Amount a;
+
+ GNUNET_array_grow (rms->melt_fresh_amounts,
+ len,
+ off + 16);
+ if (GNUNET_OK !=
+ TALER_string_to_amount (amount, &a))
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse amount `%s' at index %u\n",
+ amount, off);
+ GNUNET_free (rms->melt_fresh_amounts);
+ rms->melt_fresh_amounts = NULL;
+ return GNUNET_SYSERR;
+ }
+ rms->melt_fresh_amounts[off++] = amount;
+ }
+ }
+ if (0 == off)
+ return GNUNET_OK; /* no amounts given == use defaults! */
+ /* ensure NULL-termination */
+ GNUNET_array_grow (rms->melt_fresh_amounts,
+ len,
+ off + 1);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Create a "refresh melt" command.
+ *
+ * @param label command label.
+ * @param coin_reference reference to a command
+ * that will provide a coin to refresh.
+ * @param expected_response_code expected HTTP code.
+ * @param ... NULL-terminated list of amounts to be melted
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_refresh_melt (const char *label,
+ const char *coin_reference,
+ unsigned int expected_response_code,
+ ...)
+{
+ struct RefreshMeltState *rms;
+ va_list ap;
+
+ rms = GNUNET_new (struct RefreshMeltState);
+ rms->coin_reference = coin_reference;
+ rms->expected_response_code = expected_response_code;
+ va_start (ap, expected_response_code);
+ GNUNET_assert (GNUNET_OK ==
+ parse_amounts (rms, ap));
+ va_end (ap);
+ {
+ 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 coin_reference reference to a command that will provide
+ * a coin to refresh
+ * @param expected_response_code expected HTTP code
+ * @param ... NULL-terminated list of amounts to be melted
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_refresh_melt_double (const char *label,
+ const char *coin_reference,
+ unsigned int expected_response_code,
+ ...)
+{
+ struct RefreshMeltState *rms;
+ va_list ap;
+
+ rms = GNUNET_new (struct RefreshMeltState);
+ rms->coin_reference = coin_reference;
+ rms->expected_response_code = expected_response_code;
+ rms->double_melt = GNUNET_YES;
+ va_start (ap, expected_response_code);
+ GNUNET_assert (GNUNET_OK ==
+ parse_amounts (rms, ap));
+ va_end (ap);
+ {
+ 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[out] ret 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 * 4) + 3)
+ struct TALER_TESTING_Trait traits[NUM_TRAITS];
+
+ /* Making coin privs traits */
+ for (unsigned int i = 0; i<num_coins; i++)
+ traits[i] = TALER_TESTING_make_trait_coin_priv
+ (i, &rrs->fresh_coins[i].coin_priv);
+
+ /* Making denom pubs traits */
+ for (unsigned int i = 0; i<num_coins; i++)
+ traits[num_coins + i]
+ = TALER_TESTING_make_trait_denom_pub
+ (i, rrs->fresh_coins[i].pk);
+
+ /* Making denom sigs traits */
+ for (unsigned int i = 0; i<num_coins; i++)
+ traits[(num_coins * 2) + i]
+ = TALER_TESTING_make_trait_denom_sig
+ (i, &rrs->fresh_coins[i].sig);
+ /* blinding key traits */
+ for (unsigned int i = 0; i<num_coins; i++)
+ traits[(num_coins * 3) + i]
+ = TALER_TESTING_make_trait_blinding_key (i,
+ &rrs->fresh_coins[i].blinding_key),
+
+ /* number of fresh coins */
+ traits[(num_coins * 4)] = TALER_TESTING_make_trait_uint
+ (0, &rrs->num_fresh_coins);
+
+ /* whole array of fresh coins */
+ traits[(num_coins * 4) + 1]
+ = TALER_TESTING_make_trait_fresh_coins (0, rrs->fresh_coins),
+
+ /* end of traits */
+ traits[(num_coins * 4) + 2] = TALER_TESTING_trait_end ();
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+/**
+ * Create a "refresh reveal" command.
+ *
+ * @param label command label.
+ * @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;
+}