/* This file is part of TALER Copyright (C) 2018-2020 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 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 #include "taler_testing_lib.h" #include "taler_signatures.h" #include "backoff.h" /** * How long do we wait AT MOST when retrying? */ #define MAX_BACKOFF GNUNET_TIME_relative_multiply ( \ GNUNET_TIME_UNIT_MILLISECONDS, 100) /** * How often do we retry before giving up? */ #define NUM_RETRIES 5 /** * How long do we wait AT MOST when retrying? */ #define MAX_BACKOFF GNUNET_TIME_relative_multiply ( \ GNUNET_TIME_UNIT_MILLISECONDS, 100) /** * 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_MeltHandle *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; /** * How long did we wait in total for retries? */ struct GNUNET_TIME_Relative total_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; /** * How often should we retry on (transient) failures? */ unsigned 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_RefreshesRevealHandle *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; /** * How long did we wait in total for retries? */ struct GNUNET_TIME_Relative total_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; /** * How often should we retry on (transient) failures? */ unsigned 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_LinkHandle *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; /** * How long did we wait in total for retries? */ struct GNUNET_TIME_Relative total_backoff; /** * Expected HTTP response code. */ unsigned int expected_response_code; /** * How often should we retry on (transient) failures? */ unsigned 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; rrs->is->commands[rrs->is->ip].last_req_time = GNUNET_TIME_absolute_get (); 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, a `struct RefreshRevealState` * @param hr HTTP response details * @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. */ static void reveal_cb (void *cls, const struct TALER_EXCHANGE_HttpResponse *hr, unsigned int num_coins, const struct TALER_PlanchetSecretsP *coin_privs, const struct TALER_DenominationSignature *sigs) { struct RefreshRevealState *rrs = cls; const struct TALER_TESTING_Command *melt_cmd; rrs->rrh = NULL; if (rrs->expected_response_code != hr->http_status) { if (0 != rrs->do_retry) { rrs->do_retry--; if ( (0 == hr->http_status) || (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == hr->ec) || (MHD_HTTP_INTERNAL_SERVER_ERROR == hr->http_status) ) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Retrying refresh reveal failed with %u/%d\n", hr->http_status, (int) hr->ec); /* on DB conflicts, do not use backoff */ if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == hr->ec) rrs->backoff = GNUNET_TIME_UNIT_ZERO; else rrs->backoff = GNUNET_TIME_randomized_backoff (rrs->backoff, MAX_BACKOFF); rrs->total_backoff = GNUNET_TIME_relative_add (rrs->total_backoff, rrs->backoff); rrs->is->commands[rrs->is->ip].num_tries++; 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", hr->http_status, (int) hr->ec, rrs->is->commands[rrs->is->ip].label, __FILE__, __LINE__); json_dumpf (hr->reply, 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 (hr->http_status) { case MHD_HTTP_OK: rrs->fresh_coins = GNUNET_new_array (num_coins, struct TALER_TESTING_FreshCoinData); for (unsigned int i = 0; ifresh_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); } if (0 != rrs->total_backoff.rel_value_us) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Total reveal backoff for %s was %s\n", rrs->is->commands[rrs->is->ip].label, GNUNET_STRINGS_relative_time_to_string (rrs->total_backoff, GNUNET_YES)); } break; default: GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Unknown HTTP status %u/%d\n", hr->http_status, (int) hr->ec); } 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_refreshes_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_refreshes_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 (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; rls->is->commands[rls->is->ip].last_req_time = GNUNET_TIME_absolute_get (); 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 hr HTTP response details * @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. */ static void link_cb (void *cls, const struct TALER_EXCHANGE_HttpResponse *hr, unsigned int num_coins, const struct TALER_CoinSpendPrivateKeyP *coin_privs, const struct TALER_DenominationSignature *sigs, const struct TALER_DenominationPublicKey *pubs) { 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 != hr->http_status) { if (0 != rls->do_retry) { rls->do_retry--; if ( (0 == hr->http_status) || (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == hr->ec) || (MHD_HTTP_INTERNAL_SERVER_ERROR == hr->http_status) ) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Retrying refresh link failed with %u/%d\n", hr->http_status, (int) hr->ec); /* on DB conflicts, do not use backoff */ if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == hr->ec) rls->backoff = GNUNET_TIME_UNIT_ZERO; else rls->backoff = GNUNET_TIME_randomized_backoff (rls->backoff, MAX_BACKOFF); rls->total_backoff = GNUNET_TIME_relative_add (rls->total_backoff, rls->backoff); rls->is->commands[rls->is->ip].num_tries++; 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", hr->http_status, (int) hr->ec, link_cmd->label, __FILE__, __LINE__); json_dumpf (hr->reply, 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 (hr->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; } if (0 != rls->total_backoff.rel_value_us) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Total link backoff for %s was %s\n", rls->is->commands[rls->is->ip].label, GNUNET_STRINGS_relative_time_to_string (rls->total_backoff, GNUNET_YES)); } break; default: GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unknown HTTP response code %u/%d.\n", hr->http_status, hr->ec); } 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_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_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 melt_run (void *cls, const struct TALER_TESTING_Command *cmd, struct TALER_TESTING_Interpreter *is); /** * Task scheduled to re-try #melt_run. * * @param cls a `struct RefreshMeltState` */ static void do_melt_retry (void *cls) { struct RefreshMeltState *rms = cls; rms->retry_task = NULL; rms->is->commands[rms->is->ip].last_req_time = GNUNET_TIME_absolute_get (); 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 hr HTTP response details * @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. */ static void melt_cb (void *cls, const struct TALER_EXCHANGE_HttpResponse *hr, uint32_t noreveal_index, const struct TALER_ExchangePublicKeyP *exchange_pub) { struct RefreshMeltState *rms = cls; rms->rmh = NULL; if (rms->expected_response_code != hr->http_status) { if (0 != rms->do_retry) { rms->do_retry--; if ( (0 == hr->http_status) || (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == hr->ec) || (MHD_HTTP_INTERNAL_SERVER_ERROR == hr->http_status) ) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Retrying refresh melt failed with %u/%d\n", hr->http_status, (int) hr->ec); /* on DB conflicts, do not use backoff */ if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == hr->ec) rms->backoff = GNUNET_TIME_UNIT_ZERO; else rms->backoff = GNUNET_TIME_randomized_backoff (rms->backoff, MAX_BACKOFF); rms->total_backoff = GNUNET_TIME_relative_add (rms->total_backoff, rms->backoff); rms->is->commands[rms->is->ip].num_tries++; 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", hr->http_status, (int) hr->ec, rms->is->commands[rms->is->ip].label, __FILE__, __LINE__); json_dumpf (hr->reply, stderr, 0); TALER_TESTING_interpreter_fail (rms->is); return; } rms->noreveal_index = noreveal_index; if (0 != rms->total_backoff.rel_value_us) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Total melt backoff for %s was %s\n", rms->is->commands[rms->is->ip].label, GNUNET_STRINGS_relative_time_to_string (rms->total_backoff, GNUNET_YES)); } 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_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 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; 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; } GNUNET_assert (0 <= TALER_amount_add (&melt_amount, &melt_amount, &fresh_amount)); GNUNET_assert (0 <= 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_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 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_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 (rms->fresh_pks); rms->fresh_pks = NULL; GNUNET_free (rms->refresh_data); rms->refresh_data = NULL; rms->refresh_data_length = 0; GNUNET_free (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 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_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 = &melt_run, .cleanup = &melt_cleanup, .traits = &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_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 = &melt_run, .cleanup = &melt_cleanup, .traits = &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_melt_with_retry (struct TALER_TESTING_Command cmd) { struct RefreshMeltState *rms; GNUNET_assert (&melt_run == cmd.run); rms = cmd.cls; rms->do_retry = NUM_RETRIES; 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; 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); /* blinding key traits */ for (unsigned int i = 0; ifresh_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 = NUM_RETRIES; 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 = NUM_RETRIES; return cmd; }