commit e6f102bf5b8767b9f99921dd92be928d7023b762
parent 36929158c3b344c4f807401436a9424f686a8855
Author: Christian Grothoff <christian@grothoff.org>
Date: Sun, 11 Oct 2026 12:14:17 +0200
check successive /keys replies in the cherry-picking tests
Diffstat:
6 files changed, 746 insertions(+), 1 deletion(-)
diff --git a/src/include/taler/taler_testing_lib.h b/src/include/taler/taler_testing_lib.h
@@ -706,6 +706,38 @@ TALER_TESTING_cmd_get_exchange (
/**
+ * Check the /keys of successive "get exchange" commands.
+ *
+ * The /keys of @a later_ref must have the same master public key and
+ * currency as those of @a earlier_ref, contain every denomination of
+ * @a earlier_ref that was still valid when @a later_ref received its
+ * reply (with the same meta data), contain no denomination that had
+ * expired when @a later_ref sent its request, and list no denomination
+ * twice.
+ *
+ * If @a full_ref is given, it must be a "get exchange" command that
+ * downloaded the full /keys after @a later_ref (typically, @a later_ref
+ * used cherry picking). The /keys of both must then be the same: the
+ * same denominations, signing keys and auditors, except for
+ * denominations expiring between the two downloads.
+ *
+ * Denominations must not be revoked between the downloads.
+ *
+ * @param label command label
+ * @param earlier_ref label of a "get exchange" command
+ * @param later_ref label of a later "get exchange" command
+ * @param full_ref label of a "get exchange" command that downloaded the
+ * full /keys after @a later_ref, or NULL
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_check_keys (const char *label,
+ const char *earlier_ref,
+ const char *later_ref,
+ const char *full_ref);
+
+
+/**
* Connects to the auditor.
*
* @param label command label
diff --git a/src/testing/meson.build b/src/testing/meson.build
@@ -35,6 +35,7 @@ libtalertesting_la_SOURCES = [
'testing_api_cmd_batch_deposit.c',
'testing_api_cmd_batch_withdraw.c',
'testing_api_cmd_check_aml_decisions.c',
+ 'testing_api_cmd_check_keys.c',
'testing_api_cmd_coin_history.c',
'testing_api_cmd_common.c',
'testing_api_cmd_contract_get.c',
diff --git a/src/testing/test_exchange_api_keys_cherry_picking.c b/src/testing/test_exchange_api_keys_cherry_picking.c
@@ -64,18 +64,40 @@ run (void *cls,
NULL,
true,
true),
+ /* Denominations can be deposited for about 12 s after their start
+ (see the -rsa/-cs configurations), so after this sleep the ones
+ that were valid when we first downloaded /keys have expired,
+ while those that started in the future are still valid. */
TALER_TESTING_cmd_sleep ("sleep",
- 6 /* seconds */),
+ 12 /* seconds */),
TALER_TESTING_cmd_get_exchange ("get-exchange-1",
cred.cfg,
"get-exchange",
true,
true),
+ TALER_TESTING_cmd_get_exchange ("get-exchange-1-full",
+ cred.cfg,
+ NULL,
+ true,
+ true),
+ TALER_TESTING_cmd_check_keys ("check-keys-1",
+ "get-exchange",
+ "get-exchange-1",
+ "get-exchange-1-full"),
TALER_TESTING_cmd_get_exchange ("get-exchange-2",
cred.cfg,
"get-exchange-1",
true,
true),
+ TALER_TESTING_cmd_get_exchange ("get-exchange-2-full",
+ cred.cfg,
+ NULL,
+ true,
+ true),
+ TALER_TESTING_cmd_check_keys ("check-keys-2",
+ "get-exchange-1",
+ "get-exchange-2",
+ "get-exchange-2-full"),
TALER_TESTING_cmd_end ()
};
diff --git a/src/testing/test_exchange_api_overlapping_keys_bug.c b/src/testing/test_exchange_api_overlapping_keys_bug.c
@@ -78,6 +78,12 @@ run (void *cls,
NULL,
true,
true),
+ /* The cherry-picked /keys overlap with the ones we already had:
+ merging them must neither lose nor duplicate denominations. */
+ TALER_TESTING_cmd_check_keys ("check-keys",
+ "get-exchange",
+ "get-exchange-1",
+ "get-exchange-2"),
TALER_TESTING_cmd_end ()
};
diff --git a/src/testing/testing_api_cmd_check_keys.c b/src/testing/testing_api_cmd_check_keys.c
@@ -0,0 +1,664 @@
+/*
+ This file is part of TALER
+ (C) 2026 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_check_keys.c
+ * @brief command to compare the /keys of successive "get exchange" commands
+ * @author Christian Grothoff
+ */
+#include "taler/taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler/taler_testing_lib.h"
+
+
+/**
+ * State for a "check keys" CMD.
+ */
+struct CheckKeysState
+{
+ /**
+ * Label of the "get exchange" command with the earlier /keys.
+ */
+ const char *earlier_ref;
+
+ /**
+ * Label of the "get exchange" command with the later /keys.
+ */
+ const char *later_ref;
+
+ /**
+ * Label of a "get exchange" command that downloaded the full /keys
+ * after @e later_ref, or NULL.
+ */
+ const char *full_ref;
+};
+
+
+/**
+ * /keys of a "get exchange" command and when they were requested.
+ */
+struct KeysSnapshot
+{
+ /**
+ * Label of the command.
+ */
+ const char *label;
+
+ /**
+ * The keys.
+ */
+ const struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * When the request for @e keys was sent.
+ */
+ struct GNUNET_TIME_Absolute request_time;
+
+ /**
+ * When the reply with @e keys was received.
+ */
+ struct GNUNET_TIME_Absolute reply_time;
+};
+
+
+/**
+ * Obtain the /keys of the "get exchange" command @a label.
+ *
+ * @param is interpreter state
+ * @param label label of the command
+ * @param[out] ks set to the keys of the command
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+get_snapshot (struct TALER_TESTING_Interpreter *is,
+ const char *label,
+ struct KeysSnapshot *ks)
+{
+ const struct TALER_TESTING_Command *cmd;
+ struct TALER_EXCHANGE_Keys *keys;
+ const struct GNUNET_TIME_Absolute *request_time;
+ const struct GNUNET_TIME_Absolute *reply_time;
+
+ cmd = TALER_TESTING_interpreter_lookup_command (is,
+ label);
+ if (NULL == cmd)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if ( (GNUNET_OK !=
+ TALER_TESTING_get_trait_keys (cmd,
+ &keys)) ||
+ (NULL == keys) ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_absolute_time (cmd,
+ 0,
+ &request_time)) ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_absolute_time (cmd,
+ 1,
+ &reply_time)) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Command `%s' did not download /keys\n",
+ label);
+ return GNUNET_SYSERR;
+ }
+ ks->label = label;
+ ks->keys = keys;
+ ks->request_time = *request_time;
+ ks->reply_time = *reply_time;
+ return GNUNET_OK;
+}
+
+
+/**
+ * Find the denomination @a dk in @a keys.
+ *
+ * @param keys keys to search
+ * @param dk denomination to find (by its hash)
+ * @return NULL if @a keys does not contain @a dk
+ */
+static const struct TALER_EXCHANGE_DenomPublicKey *
+find_denom (const struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_EXCHANGE_DenomPublicKey *dk)
+{
+ for (unsigned int i = 0; i<keys->num_denom_keys; i++)
+ if (0 ==
+ GNUNET_memcmp (&dk->h_key,
+ &keys->denom_keys[i].h_key))
+ return &keys->denom_keys[i];
+ return NULL;
+}
+
+
+/**
+ * Check if @a a and @a b are the same amount.
+ *
+ * @param a an amount
+ * @param b another amount
+ * @return true if they are equal
+ */
+static bool
+amount_equal (const struct TALER_Amount *a,
+ const struct TALER_Amount *b)
+{
+ return (GNUNET_YES ==
+ TALER_amount_cmp_currency (a,
+ b)) &&
+ (0 == TALER_amount_cmp (a,
+ b));
+}
+
+
+/**
+ * Check if @a a and @a b describe the same denomination with the
+ * same meta data.
+ *
+ * @param a a denomination
+ * @param b another denomination
+ * @return true if they are the same
+ */
+static bool
+denom_equal (const struct TALER_EXCHANGE_DenomPublicKey *a,
+ const struct TALER_EXCHANGE_DenomPublicKey *b)
+{
+ return (0 == GNUNET_memcmp (&a->h_key,
+ &b->h_key)) &&
+ (0 == TALER_denom_pub_cmp (&a->key,
+ &b->key)) &&
+ (0 == GNUNET_memcmp (&a->master_sig,
+ &b->master_sig)) &&
+ (GNUNET_TIME_timestamp_cmp (a->valid_from,
+ ==,
+ b->valid_from)) &&
+ (GNUNET_TIME_timestamp_cmp (a->withdraw_valid_until,
+ ==,
+ b->withdraw_valid_until)) &&
+ (GNUNET_TIME_timestamp_cmp (a->expire_deposit,
+ ==,
+ b->expire_deposit)) &&
+ (GNUNET_TIME_timestamp_cmp (a->expire_legal,
+ ==,
+ b->expire_legal)) &&
+ amount_equal (&a->value,
+ &b->value) &&
+ amount_equal (&a->fees.withdraw,
+ &b->fees.withdraw) &&
+ amount_equal (&a->fees.deposit,
+ &b->fees.deposit) &&
+ amount_equal (&a->fees.refresh,
+ &b->fees.refresh) &&
+ amount_equal (&a->fees.refund,
+ &b->fees.refund) &&
+ (a->revoked == b->revoked);
+}
+
+
+/**
+ * Check that every denomination of @a a that is still valid after
+ * @a valid_after is also in @a b, with the same meta data.
+ *
+ * @param a keys with the denominations to look for
+ * @param b keys to search
+ * @param valid_after denominations of @a a that expire before this
+ * time are not checked
+ * @return true if all of them were found
+ */
+static bool
+denoms_included (const struct KeysSnapshot *a,
+ const struct KeysSnapshot *b,
+ struct GNUNET_TIME_Absolute valid_after)
+{
+ bool ok = true;
+
+ for (unsigned int i = 0; i<a->keys->num_denom_keys; i++)
+ {
+ const struct TALER_EXCHANGE_DenomPublicKey *dk
+ = &a->keys->denom_keys[i];
+ const struct TALER_EXCHANGE_DenomPublicKey *dkb;
+
+ if (GNUNET_TIME_absolute_cmp (dk->expire_deposit.abs_time,
+ <=,
+ valid_after))
+ continue;
+ dkb = find_denom (b->keys,
+ dk);
+ if (NULL == dkb)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Denomination %s (valid until %s) of `%s' missing in `%s'\n",
+ GNUNET_h2s (&dk->h_key.hash),
+ GNUNET_TIME_timestamp2s (dk->expire_deposit),
+ a->label,
+ b->label);
+ ok = false;
+ continue;
+ }
+ if (! denom_equal (dk,
+ dkb))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Denomination %s differs between `%s' and `%s'\n",
+ GNUNET_h2s (&dk->h_key.hash),
+ a->label,
+ b->label);
+ ok = false;
+ }
+ }
+ return ok;
+}
+
+
+/**
+ * Check that @a ks lists no denomination that had expired
+ * when its /keys were requested.
+ *
+ * @param ks keys to check
+ * @return true if no expired denomination is listed
+ */
+static bool
+no_expired_denoms (const struct KeysSnapshot *ks)
+{
+ bool ok = true;
+
+ for (unsigned int i = 0; i<ks->keys->num_denom_keys; i++)
+ {
+ const struct TALER_EXCHANGE_DenomPublicKey *dk
+ = &ks->keys->denom_keys[i];
+
+ if (GNUNET_TIME_absolute_cmp (dk->expire_deposit.abs_time,
+ <,
+ ks->request_time))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "`%s' lists denomination %s that expired at %s\n",
+ ks->label,
+ GNUNET_h2s (&dk->h_key.hash),
+ GNUNET_TIME_timestamp2s (dk->expire_deposit));
+ ok = false;
+ }
+ }
+ return ok;
+}
+
+
+/**
+ * Check that @a ks lists each denomination only once.
+ *
+ * @param ks keys to check
+ * @return true if there are no duplicates
+ */
+static bool
+no_duplicate_denoms (const struct KeysSnapshot *ks)
+{
+ for (unsigned int i = 0; i<ks->keys->num_denom_keys; i++)
+ for (unsigned int j = i + 1; j<ks->keys->num_denom_keys; j++)
+ if (0 ==
+ GNUNET_memcmp (&ks->keys->denom_keys[i].h_key,
+ &ks->keys->denom_keys[j].h_key))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "`%s' lists denomination %s twice\n",
+ ks->label,
+ GNUNET_h2s (&ks->keys->denom_keys[i].h_key.hash));
+ return false;
+ }
+ return true;
+}
+
+
+/**
+ * Check that @a later is a valid successor of @a earlier: same master
+ * public key and currency, all denominations of @a earlier that are
+ * still valid are kept, none that expired, and none twice.
+ *
+ * @param earlier earlier /keys
+ * @param later later /keys
+ * @return true if @a later is a valid successor of @a earlier
+ */
+static bool
+check_successor (const struct KeysSnapshot *earlier,
+ const struct KeysSnapshot *later)
+{
+ bool ok = true;
+
+ if (0 !=
+ GNUNET_memcmp (&earlier->keys->master_pub,
+ &later->keys->master_pub))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Master public key changed between `%s' and `%s'\n",
+ earlier->label,
+ later->label);
+ ok = false;
+ }
+ if (0 != strcmp (earlier->keys->currency,
+ later->keys->currency))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Currency changed between `%s' and `%s'\n",
+ earlier->label,
+ later->label);
+ ok = false;
+ }
+ /* Denominations that were still valid when the later reply was
+ received must have been kept. */
+ if (! denoms_included (earlier,
+ later,
+ later->reply_time))
+ ok = false;
+ if (! no_expired_denoms (later))
+ ok = false;
+ if (! no_duplicate_denoms (later))
+ ok = false;
+ return ok;
+}
+
+
+/**
+ * Check that the auditors of @a a are also auditors in @a b, auditing
+ * at least the same denominations, ignoring denominations that expire
+ * before @a valid_after.
+ *
+ * @param a keys with the auditors to look for
+ * @param b keys to search
+ * @param valid_after ignore denominations that expire before this time
+ * @return true if all of them were found
+ */
+static bool
+auditors_included (const struct KeysSnapshot *a,
+ const struct KeysSnapshot *b,
+ struct GNUNET_TIME_Absolute valid_after)
+{
+ for (unsigned int i = 0; i<a->keys->num_auditors; i++)
+ {
+ const struct TALER_EXCHANGE_AuditorInformation *ai
+ = &a->keys->auditors[i];
+ const struct TALER_EXCHANGE_AuditorInformation *bi = NULL;
+
+ for (unsigned int j = 0; j<b->keys->num_auditors; j++)
+ if ( (0 ==
+ GNUNET_memcmp (&ai->auditor_pub,
+ &b->keys->auditors[j].auditor_pub)) &&
+ (0 == strcmp (ai->auditor_url,
+ b->keys->auditors[j].auditor_url)) )
+ bi = &b->keys->auditors[j];
+ if (NULL == bi)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Auditor %s of `%s' missing in `%s'\n",
+ ai->auditor_url,
+ a->label,
+ b->label);
+ return false;
+ }
+ for (unsigned int k = 0; k<ai->num_denom_keys; k++)
+ {
+ const struct TALER_EXCHANGE_DenomPublicKey *dk
+ = &a->keys->denom_keys[ai->denom_keys[k].denom_key_offset];
+ bool found = false;
+
+ if (GNUNET_TIME_absolute_cmp (dk->expire_deposit.abs_time,
+ <=,
+ valid_after))
+ continue;
+ for (unsigned int l = 0; l<bi->num_denom_keys; l++)
+ if (0 ==
+ GNUNET_memcmp (
+ &dk->h_key,
+ &b->keys->denom_keys[bi->denom_keys[l].denom_key_offset].h_key))
+ found = true;
+ if (! found)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Auditor %s does not audit denomination %s in `%s'\n",
+ ai->auditor_url,
+ GNUNET_h2s (&dk->h_key.hash),
+ b->label);
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+
+/**
+ * Check that the signing keys of @a a are also in @a b.
+ *
+ * @param a keys with the signing keys to look for
+ * @param b keys to search
+ * @return true if all of them were found
+ */
+static bool
+signkeys_included (const struct KeysSnapshot *a,
+ const struct KeysSnapshot *b)
+{
+ for (unsigned int i = 0; i<a->keys->num_sign_keys; i++)
+ {
+ const struct TALER_EXCHANGE_SigningPublicKey *sk
+ = &a->keys->sign_keys[i];
+ bool found = false;
+
+ for (unsigned int j = 0; j<b->keys->num_sign_keys; j++)
+ if ( (0 ==
+ GNUNET_memcmp (&sk->key,
+ &b->keys->sign_keys[j].key)) &&
+ (0 ==
+ GNUNET_memcmp (&sk->master_sig,
+ &b->keys->sign_keys[j].master_sig)) )
+ found = true;
+ if (! found)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Signing key %s of `%s' missing in `%s'\n",
+ TALER_B2S (&sk->key),
+ a->label,
+ b->label);
+ return false;
+ }
+ }
+ return true;
+}
+
+
+/**
+ * Check that @a later (cherry-picked and merged by the client) equals
+ * @a full, downloaded in full right after it. Denominations that
+ * expire between the two downloads may legitimately differ and are
+ * ignored.
+ *
+ * @param later cherry-picked /keys
+ * @param full full /keys
+ * @return true if they are the same
+ */
+static bool
+check_equal (const struct KeysSnapshot *later,
+ const struct KeysSnapshot *full)
+{
+ bool ok = true;
+
+ if (0 !=
+ GNUNET_memcmp (&later->keys->master_pub,
+ &full->keys->master_pub))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Master public key differs between `%s' and `%s'\n",
+ later->label,
+ full->label);
+ ok = false;
+ }
+ if (0 != strcmp (later->keys->currency,
+ full->keys->currency))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Currency differs between `%s' and `%s'\n",
+ later->label,
+ full->label);
+ ok = false;
+ }
+ if (! denoms_included (later,
+ full,
+ full->reply_time))
+ ok = false;
+ if (! denoms_included (full,
+ later,
+ full->reply_time))
+ ok = false;
+ if (! auditors_included (later,
+ full,
+ full->reply_time))
+ ok = false;
+ if (! auditors_included (full,
+ later,
+ full->reply_time))
+ ok = false;
+ if (! signkeys_included (later,
+ full))
+ ok = false;
+ if (! signkeys_included (full,
+ later))
+ ok = false;
+ if (! no_expired_denoms (full))
+ ok = false;
+ if (! no_duplicate_denoms (full))
+ ok = false;
+ return ok;
+}
+
+
+/**
+ * Run the "check keys" command.
+ *
+ * @param cls closure.
+ * @param cmd the command currently being executed.
+ * @param is the interpreter state.
+ */
+static void
+check_keys_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct CheckKeysState *cks = cls;
+ struct KeysSnapshot earlier;
+ struct KeysSnapshot later;
+
+ (void) cmd;
+ if ( (GNUNET_OK !=
+ get_snapshot (is,
+ cks->earlier_ref,
+ &earlier)) ||
+ (GNUNET_OK !=
+ get_snapshot (is,
+ cks->later_ref,
+ &later)) )
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (! check_successor (&earlier,
+ &later))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (NULL != cks->full_ref)
+ {
+ struct KeysSnapshot full;
+
+ if (GNUNET_OK !=
+ get_snapshot (is,
+ cks->full_ref,
+ &full))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (GNUNET_TIME_absolute_cmp (full.request_time,
+ <,
+ later.reply_time))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "`%s' must download /keys after `%s'\n",
+ cks->full_ref,
+ cks->later_ref);
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (! check_equal (&later,
+ &full))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ }
+ TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Cleanup the state.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+check_keys_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct CheckKeysState *cks = cls;
+
+ (void) cmd;
+ GNUNET_free (cks);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_check_keys (const char *label,
+ const char *earlier_ref,
+ const char *later_ref,
+ const char *full_ref)
+{
+ struct CheckKeysState *cks;
+
+ GNUNET_assert (NULL != earlier_ref);
+ GNUNET_assert (NULL != later_ref);
+ cks = GNUNET_new (struct CheckKeysState);
+ cks->earlier_ref = earlier_ref;
+ cks->later_ref = later_ref;
+ cks->full_ref = full_ref;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = cks,
+ .label = label,
+ .run = &check_keys_run,
+ .cleanup = &check_keys_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_check_keys.c */
diff --git a/src/testing/testing_api_cmd_get_exchange.c b/src/testing/testing_api_cmd_get_exchange.c
@@ -76,6 +76,16 @@ struct GetExchangeState
struct GNUNET_TIME_Timestamp my_denom_date;
/**
+ * When did we send the /keys request?
+ */
+ struct GNUNET_TIME_Absolute request_time;
+
+ /**
+ * When did we receive the reply to the /keys request?
+ */
+ struct GNUNET_TIME_Absolute reply_time;
+
+ /**
* Are we waiting for /keys before continuing?
*/
bool wait_for_keys;
@@ -99,6 +109,7 @@ cert_cb (struct GetExchangeState *ges,
struct TALER_TESTING_Interpreter *is = ges->is;
ges->exchange = NULL;
+ ges->reply_time = GNUNET_TIME_absolute_get ();
if (NULL != ges->keys)
TALER_EXCHANGE_keys_decref (ges->keys);
ges->keys = keys;
@@ -249,6 +260,7 @@ get_exchange_run (void *cls,
TALER_EXCHANGE_get_keys_option_last_keys (xkeys));
}
TALER_EXCHANGE_keys_decref (xkeys);
+ ges->request_time = GNUNET_TIME_absolute_get ();
if (TALER_EC_NONE !=
TALER_EXCHANGE_get_keys_start (ges->exchange,
&cert_cb,
@@ -317,6 +329,10 @@ get_exchange_traits (void *cls,
TALER_TESTING_make_trait_exchange_url (ges->exchange_url),
TALER_TESTING_make_trait_timestamp (0,
&ges->my_denom_date),
+ TALER_TESTING_make_trait_absolute_time (0,
+ &ges->request_time),
+ TALER_TESTING_make_trait_absolute_time (1,
+ &ges->reply_time),
TALER_TESTING_trait_end ()
};
@@ -332,6 +348,10 @@ get_exchange_traits (void *cls,
TALER_TESTING_make_trait_exchange_url (ges->exchange_url),
TALER_TESTING_make_trait_timestamp (0,
&ges->my_denom_date),
+ TALER_TESTING_make_trait_absolute_time (0,
+ &ges->request_time),
+ TALER_TESTING_make_trait_absolute_time (1,
+ &ges->reply_time),
TALER_TESTING_trait_end ()
};