summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/include/taler_testing_lib.h414
-rw-r--r--src/testing/.gitignore2
-rw-r--r--src/testing/Makefile.am33
-rw-r--r--src/testing/test_exchange_api_conflicts-cs.conf4
-rw-r--r--src/testing/test_exchange_api_conflicts-rsa.conf4
-rw-r--r--src/testing/test_exchange_api_conflicts.c220
-rw-r--r--src/testing/test_exchange_api_conflicts.conf81
-rw-r--r--src/testing/testing_api_cmd_batch_withdraw.c72
8 files changed, 623 insertions, 207 deletions
diff --git a/src/include/taler_testing_lib.h b/src/include/taler_testing_lib.h
index 90f6ade88..e89779614 100644
--- a/src/include/taler_testing_lib.h
+++ b/src/include/taler_testing_lib.h
@@ -43,12 +43,12 @@
* quite any time after the command "run" method has been called.
*/
#define TALER_TESTING_FAIL(is) \
- do \
- { \
- GNUNET_break (0); \
- TALER_TESTING_interpreter_fail (is); \
- return; \
- } while (0)
+ do \
+ { \
+ GNUNET_break (0); \
+ TALER_TESTING_interpreter_fail (is); \
+ return; \
+ } while (0)
/**
@@ -60,16 +60,16 @@
* @param expected expected HTTP status code
*/
#define TALER_TESTING_unexpected_status(is,status,expected) \
- do { \
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR, \
- "Unexpected response code %u (expected: %u) to command %s in %s:%u\n", \
- status, \
- expected, \
- TALER_TESTING_interpreter_get_current_label (is), \
- __FILE__, \
- __LINE__); \
- TALER_TESTING_interpreter_fail (is); \
- } while (0)
+ do { \
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, \
+ "Unexpected response code %u (expected: %u) to command %s in %s:%u\n", \
+ status, \
+ expected, \
+ TALER_TESTING_interpreter_get_current_label (is), \
+ __FILE__, \
+ __LINE__); \
+ TALER_TESTING_interpreter_fail (is); \
+ } while (0)
/**
* Log an error message about us receiving an unexpected HTTP
@@ -82,18 +82,18 @@
* @param body received JSON-reply
*/
#define TALER_TESTING_unexpected_status_with_body(is,status,expected,body) \
- do { \
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR, \
- "Unexpected response code %u (expected: %u) to " \
- "command %s in %s:%u\nwith body:\n>>%s<<\n", \
- status, \
- expected, \
- TALER_TESTING_interpreter_get_current_label (is), \
- __FILE__, \
- __LINE__, \
- json_dumps (body, JSON_INDENT (2))); \
- TALER_TESTING_interpreter_fail (is); \
- } while (0)
+ do { \
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, \
+ "Unexpected response code %u (expected: %u) to " \
+ "command %s in %s:%u\nwith body:\n>>%s<<\n", \
+ status, \
+ expected, \
+ TALER_TESTING_interpreter_get_current_label (is), \
+ __FILE__, \
+ __LINE__, \
+ json_dumps (body, JSON_INDENT (2))); \
+ TALER_TESTING_interpreter_fail (is); \
+ } while (0)
/**
@@ -104,14 +104,14 @@
* @param label command label of the incomplete command
*/
#define TALER_TESTING_command_incomplete(is,label) \
- do { \
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR, \
- "Command %s (%s:%u) did not complete (at %s)\n", \
- label, \
- __FILE__, \
- __LINE__, \
- TALER_TESTING_interpreter_get_current_label (is)); \
- } while (0)
+ do { \
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, \
+ "Command %s (%s:%u) did not complete (at %s)\n", \
+ label, \
+ __FILE__, \
+ __LINE__, \
+ TALER_TESTING_interpreter_get_current_label (is)); \
+ } while (0)
/**
@@ -311,10 +311,10 @@ struct TALER_TESTING_Command
* @return #GNUNET_OK on success
*/
enum GNUNET_GenericReturnValue
- (*traits)(void *cls,
- const void **ret,
- const char *trait,
- unsigned int index);
+ (*traits)(void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index);
/**
* When did the execution of this command start?
@@ -1079,12 +1079,53 @@ TALER_TESTING_cmd_withdraw_amount (const char *label,
uint8_t age,
unsigned int expected_response_code);
+/**
+ * Force a conflict
+ * 0: no conflict
+ * 1: multiple coins with same private key, different denominations
+ * 2: multiple coins with same private key and different age restriction
+ */
+enum TALER_TESTING_CoinConflictType
+{
+ Conflict_None = 0,
+ Conflict_Denom = 1,
+ Conflict_Age = 2
+};
+
+/**
+ * Create a batch withdraw command, letting the caller specify the type of
+ * conflict between the coins and the desired amounts as string.
+ *
+ * Takes a variable, non-empty list of the denomination amounts via VARARGS,
+ * similar to #TALER_TESTING_cmd_withdraw_amount(), just using a batch
+ * withdraw.
+ *
+ * @param label command label.
+ * @param reserve_reference command providing us with a reserve to withdraw from
+ * @param conflict type of conflict for the coins
+ * @param age if > 0, age restriction applies (same for all coins)
+ * @param expected_response_code which HTTP response code
+ * we expect from the exchange.
+ * @param amount how much we withdraw for the first coin
+ * @param ... NULL-terminated list of additional amounts to withdraw (one per coin)
+ * @return the withdraw command to be executed by the interpreter.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_batch_withdraw_with_conflict (
+ const char *label,
+ const char *reserve_reference,
+ enum TALER_TESTING_CoinConflictType conflict,
+ uint8_t age,
+ unsigned int expected_response_code,
+ const char *amount,
+ ...);
/**
* Create a batch withdraw command, letting the caller specify
* the desired amounts as string. Takes a variable, non-empty
* list of the denomination amounts via VARARGS, similar to
* #TALER_TESTING_cmd_withdraw_amount(), just using a batch withdraw.
+ * The coins are generated without a conflict (different private keys).
*
* @param label command label.
* @param reserve_reference command providing us with a reserve to withdraw from
@@ -1095,13 +1136,20 @@ TALER_TESTING_cmd_withdraw_amount (const char *label,
* @param ... NULL-terminated list of additional amounts to withdraw (one per coin)
* @return the withdraw command to be executed by the interpreter.
*/
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_batch_withdraw (const char *label,
- const char *reserve_reference,
- uint8_t age,
- unsigned int expected_response_code,
- const char *amount,
- ...);
+#define TALER_TESTING_cmd_batch_withdraw(label, \
+ reserve_reference, \
+ age, \
+ expected_response_code, \
+ amount, \
+ ...) \
+ TALER_TESTING_cmd_batch_withdraw_with_conflict ( \
+ (label), \
+ (reserve_reference), \
+ Conflict_None, \
+ (age), \
+ (expected_response_code), \
+ (amount), \
+ __VA_ARGS__)
/**
* Create an age-withdraw command, letting the caller specify
@@ -2231,7 +2279,7 @@ TALER_TESTING_cmd_oauth_with_birthdate (const char *label,
* @param port the TCP port to listen on
*/
#define TALER_TESTING_cmd_oauth(label, port) \
- TALER_TESTING_cmd_oauth_with_birthdate ((label), NULL, (port))
+ TALER_TESTING_cmd_oauth_with_birthdate ((label), NULL, (port))
/* ****************** P2P payment commands ****************** */
@@ -2560,13 +2608,13 @@ TALER_TESTING_get_trait (const struct TALER_TESTING_Trait *traits,
* statically allocated data of type @a type.
*/
#define TALER_TESTING_MAKE_DECL_SIMPLE_TRAIT(name,type) \
- enum GNUNET_GenericReturnValue \
- TALER_TESTING_get_trait_ ## name ( \
- const struct TALER_TESTING_Command *cmd, \
- type **ret); \
- struct TALER_TESTING_Trait \
- TALER_TESTING_make_trait_ ## name ( \
- type * value);
+ enum GNUNET_GenericReturnValue \
+ TALER_TESTING_get_trait_ ## name ( \
+ const struct TALER_TESTING_Command *cmd, \
+ type **ret); \
+ struct TALER_TESTING_Trait \
+ TALER_TESTING_make_trait_ ## name ( \
+ type * value);
/**
@@ -2574,27 +2622,27 @@ TALER_TESTING_get_trait (const struct TALER_TESTING_Trait *traits,
* allocated data of type @a type.
*/
#define TALER_TESTING_MAKE_IMPL_SIMPLE_TRAIT(name,type) \
- enum GNUNET_GenericReturnValue \
- TALER_TESTING_get_trait_ ## name ( \
- const struct TALER_TESTING_Command *cmd, \
- type **ret) \
- { \
- if (NULL == cmd->traits) return GNUNET_SYSERR; \
- return cmd->traits (cmd->cls, \
- (const void **) ret, \
- TALER_S (name), \
- 0); \
- } \
- struct TALER_TESTING_Trait \
- TALER_TESTING_make_trait_ ## name ( \
- type * value) \
- { \
- struct TALER_TESTING_Trait ret = { \
- .trait_name = TALER_S (name), \
- .ptr = (const void *) value \
- }; \
- return ret; \
- }
+ enum GNUNET_GenericReturnValue \
+ TALER_TESTING_get_trait_ ## name ( \
+ const struct TALER_TESTING_Command *cmd, \
+ type * *ret) \
+ { \
+ if (NULL == cmd->traits) return GNUNET_SYSERR; \
+ return cmd->traits (cmd->cls, \
+ (const void **) ret, \
+ TALER_S (name), \
+ 0); \
+ } \
+ struct TALER_TESTING_Trait \
+ TALER_TESTING_make_trait_ ## name ( \
+ type * value) \
+ { \
+ struct TALER_TESTING_Trait ret = { \
+ .trait_name = TALER_S (name), \
+ .ptr = (const void *) value \
+ }; \
+ return ret; \
+ }
/**
@@ -2602,15 +2650,15 @@ TALER_TESTING_get_trait (const struct TALER_TESTING_Trait *traits,
* statically allocated data of type @a type.
*/
#define TALER_TESTING_MAKE_DECL_INDEXED_TRAIT(name,type) \
- enum GNUNET_GenericReturnValue \
- TALER_TESTING_get_trait_ ## name ( \
- const struct TALER_TESTING_Command *cmd, \
- unsigned int index, \
- type **ret); \
- struct TALER_TESTING_Trait \
- TALER_TESTING_make_trait_ ## name ( \
- unsigned int index, \
- type * value);
+ enum GNUNET_GenericReturnValue \
+ TALER_TESTING_get_trait_ ## name ( \
+ const struct TALER_TESTING_Command *cmd, \
+ unsigned int index, \
+ type **ret); \
+ struct TALER_TESTING_Trait \
+ TALER_TESTING_make_trait_ ## name ( \
+ unsigned int index, \
+ type *value);
/**
@@ -2618,116 +2666,116 @@ TALER_TESTING_get_trait (const struct TALER_TESTING_Trait *traits,
* allocated data of type @a type.
*/
#define TALER_TESTING_MAKE_IMPL_INDEXED_TRAIT(name,type) \
- enum GNUNET_GenericReturnValue \
- TALER_TESTING_get_trait_ ## name ( \
- const struct TALER_TESTING_Command *cmd, \
- unsigned int index, \
- type **ret) \
- { \
- if (NULL == cmd->traits) return GNUNET_SYSERR; \
- return cmd->traits (cmd->cls, \
- (const void **) ret, \
- TALER_S (name), \
- index); \
- } \
- struct TALER_TESTING_Trait \
- TALER_TESTING_make_trait_ ## name ( \
- unsigned int index, \
- type * value) \
- { \
- struct TALER_TESTING_Trait ret = { \
- .index = index, \
- .trait_name = TALER_S (name), \
- .ptr = (const void *) value \
- }; \
- return ret; \
- }
+ enum GNUNET_GenericReturnValue \
+ TALER_TESTING_get_trait_ ## name ( \
+ const struct TALER_TESTING_Command *cmd, \
+ unsigned int index, \
+ type * *ret) \
+ { \
+ if (NULL == cmd->traits) return GNUNET_SYSERR; \
+ return cmd->traits (cmd->cls, \
+ (const void **) ret, \
+ TALER_S (name), \
+ index); \
+ } \
+ struct TALER_TESTING_Trait \
+ TALER_TESTING_make_trait_ ## name ( \
+ unsigned int index, \
+ type * value) \
+ { \
+ struct TALER_TESTING_Trait ret = { \
+ .index = index, \
+ .trait_name = TALER_S (name), \
+ .ptr = (const void *) value \
+ }; \
+ return ret; \
+ }
/**
* Call #op on all simple traits.
*/
#define TALER_TESTING_SIMPLE_TRAITS(op) \
- op (bank_row, const uint64_t) \
- op (officer_pub, const struct TALER_AmlOfficerPublicKeyP) \
- op (officer_priv, const struct TALER_AmlOfficerPrivateKeyP) \
- op (officer_name, const char) \
- op (aml_decision, enum TALER_AmlDecisionState) \
- op (aml_justification, const char) \
- op (auditor_priv, const struct TALER_AuditorPrivateKeyP) \
- op (auditor_pub, const struct TALER_AuditorPublicKeyP) \
- op (master_priv, const struct TALER_MasterPrivateKeyP) \
- op (master_pub, const struct TALER_MasterPublicKeyP) \
- op (purse_priv, const struct TALER_PurseContractPrivateKeyP) \
- op (purse_pub, const struct TALER_PurseContractPublicKeyP) \
- op (merge_priv, const struct TALER_PurseMergePrivateKeyP) \
- op (merge_pub, const struct TALER_PurseMergePublicKeyP) \
- op (contract_priv, const struct TALER_ContractDiffiePrivateP) \
- op (reserve_priv, const struct TALER_ReservePrivateKeyP) \
- op (reserve_sig, const struct TALER_ReserveSignatureP) \
- op (h_payto, const struct TALER_PaytoHashP) \
- op (planchet_secret, const struct TALER_PlanchetMasterSecretP) \
- op (refresh_secret, const struct TALER_RefreshMasterSecretP) \
- op (reserve_pub, const struct TALER_ReservePublicKeyP) \
- op (merchant_priv, const struct TALER_MerchantPrivateKeyP) \
- op (merchant_pub, const struct TALER_MerchantPublicKeyP) \
- op (merchant_sig, const struct TALER_MerchantSignatureP) \
- op (wtid, const struct TALER_WireTransferIdentifierRawP) \
- op (bank_auth_data, const struct TALER_BANK_AuthenticationData) \
- op (contract_terms, const json_t) \
- op (wire_details, const json_t) \
- op (exchange_url, const char) \
- op (auditor_url, const char) \
- op (exchange_bank_account_url, const char) \
- op (taler_uri, const char) \
- op (payto_uri, const char) \
- op (kyc_url, const char) \
- op (web_url, const char) \
- op (row, const uint64_t) \
- op (legi_requirement_row, const uint64_t) \
- op (array_length, const unsigned int) \
- op (credit_payto_uri, const char) \
- op (debit_payto_uri, const char) \
- op (order_id, const char) \
- op (amount, const struct TALER_Amount) \
- op (amount_with_fee, const struct TALER_Amount) \
- op (batch_cmds, struct TALER_TESTING_Command) \
- op (uuid, const struct GNUNET_Uuid) \
- op (fresh_coins, const struct TALER_TESTING_FreshCoinData *) \
- op (claim_token, const struct TALER_ClaimTokenP) \
- op (relative_time, const struct GNUNET_TIME_Relative) \
- op (fakebank, struct TALER_FAKEBANK_Handle) \
- op (keys, struct TALER_EXCHANGE_Keys) \
- op (process, struct GNUNET_OS_Process *)
+ op (bank_row, const uint64_t) \
+ op (officer_pub, const struct TALER_AmlOfficerPublicKeyP) \
+ op (officer_priv, const struct TALER_AmlOfficerPrivateKeyP) \
+ op (officer_name, const char) \
+ op (aml_decision, enum TALER_AmlDecisionState) \
+ op (aml_justification, const char) \
+ op (auditor_priv, const struct TALER_AuditorPrivateKeyP) \
+ op (auditor_pub, const struct TALER_AuditorPublicKeyP) \
+ op (master_priv, const struct TALER_MasterPrivateKeyP) \
+ op (master_pub, const struct TALER_MasterPublicKeyP) \
+ op (purse_priv, const struct TALER_PurseContractPrivateKeyP) \
+ op (purse_pub, const struct TALER_PurseContractPublicKeyP) \
+ op (merge_priv, const struct TALER_PurseMergePrivateKeyP) \
+ op (merge_pub, const struct TALER_PurseMergePublicKeyP) \
+ op (contract_priv, const struct TALER_ContractDiffiePrivateP) \
+ op (reserve_priv, const struct TALER_ReservePrivateKeyP) \
+ op (reserve_sig, const struct TALER_ReserveSignatureP) \
+ op (h_payto, const struct TALER_PaytoHashP) \
+ op (planchet_secret, const struct TALER_PlanchetMasterSecretP) \
+ op (refresh_secret, const struct TALER_RefreshMasterSecretP) \
+ op (reserve_pub, const struct TALER_ReservePublicKeyP) \
+ op (merchant_priv, const struct TALER_MerchantPrivateKeyP) \
+ op (merchant_pub, const struct TALER_MerchantPublicKeyP) \
+ op (merchant_sig, const struct TALER_MerchantSignatureP) \
+ op (wtid, const struct TALER_WireTransferIdentifierRawP) \
+ op (bank_auth_data, const struct TALER_BANK_AuthenticationData) \
+ op (contract_terms, const json_t) \
+ op (wire_details, const json_t) \
+ op (exchange_url, const char) \
+ op (auditor_url, const char) \
+ op (exchange_bank_account_url, const char) \
+ op (taler_uri, const char) \
+ op (payto_uri, const char) \
+ op (kyc_url, const char) \
+ op (web_url, const char) \
+ op (row, const uint64_t) \
+ op (legi_requirement_row, const uint64_t) \
+ op (array_length, const unsigned int) \
+ op (credit_payto_uri, const char) \
+ op (debit_payto_uri, const char) \
+ op (order_id, const char) \
+ op (amount, const struct TALER_Amount) \
+ op (amount_with_fee, const struct TALER_Amount) \
+ op (batch_cmds, struct TALER_TESTING_Command) \
+ op (uuid, const struct GNUNET_Uuid) \
+ op (fresh_coins, const struct TALER_TESTING_FreshCoinData *) \
+ op (claim_token, const struct TALER_ClaimTokenP) \
+ op (relative_time, const struct GNUNET_TIME_Relative) \
+ op (fakebank, struct TALER_FAKEBANK_Handle) \
+ op (keys, struct TALER_EXCHANGE_Keys) \
+ op (process, struct GNUNET_OS_Process *)
/**
* Call #op on all indexed traits.
*/
#define TALER_TESTING_INDEXED_TRAITS(op) \
- op (denom_pub, const struct TALER_EXCHANGE_DenomPublicKey) \
- op (denom_sig, const struct TALER_DenominationSignature) \
- op (amounts, const struct TALER_Amount) \
- op (deposit_amount, const struct TALER_Amount) \
- op (deposit_fee_amount, const struct TALER_Amount) \
- op (age_commitment, const struct TALER_AgeCommitment) \
- op (age_commitment_proof, const struct TALER_AgeCommitmentProof) \
- op (h_age_commitment, const struct TALER_AgeCommitmentHash) \
- op (reserve_history, const struct TALER_EXCHANGE_ReserveHistoryEntry) \
- op (coin_history, const struct TALER_EXCHANGE_CoinHistoryEntry) \
- op (planchet_secrets, const struct TALER_PlanchetMasterSecretP) \
- op (exchange_wd_value, const struct TALER_ExchangeWithdrawValues) \
- op (coin_priv, const struct TALER_CoinSpendPrivateKeyP) \
- op (coin_pub, const struct TALER_CoinSpendPublicKeyP) \
- op (coin_sig, const struct TALER_CoinSpendSignatureP) \
- op (absolute_time, const struct GNUNET_TIME_Absolute) \
- op (timestamp, const struct GNUNET_TIME_Timestamp) \
- op (wire_deadline, const struct GNUNET_TIME_Timestamp) \
- op (refund_deadline, const struct GNUNET_TIME_Timestamp) \
- op (exchange_pub, const struct TALER_ExchangePublicKeyP) \
- op (exchange_sig, const struct TALER_ExchangeSignatureP) \
- op (blinding_key, const union GNUNET_CRYPTO_BlindingSecretP) \
- op (h_blinded_coin, const struct TALER_BlindedCoinHashP)
+ op (denom_pub, const struct TALER_EXCHANGE_DenomPublicKey) \
+ op (denom_sig, const struct TALER_DenominationSignature) \
+ op (amounts, const struct TALER_Amount) \
+ op (deposit_amount, const struct TALER_Amount) \
+ op (deposit_fee_amount, const struct TALER_Amount) \
+ op (age_commitment, const struct TALER_AgeCommitment) \
+ op (age_commitment_proof, const struct TALER_AgeCommitmentProof) \
+ op (h_age_commitment, const struct TALER_AgeCommitmentHash) \
+ op (reserve_history, const struct TALER_EXCHANGE_ReserveHistoryEntry) \
+ op (coin_history, const struct TALER_EXCHANGE_CoinHistoryEntry) \
+ op (planchet_secrets, const struct TALER_PlanchetMasterSecretP) \
+ op (exchange_wd_value, const struct TALER_ExchangeWithdrawValues) \
+ op (coin_priv, const struct TALER_CoinSpendPrivateKeyP) \
+ op (coin_pub, const struct TALER_CoinSpendPublicKeyP) \
+ op (coin_sig, const struct TALER_CoinSpendSignatureP) \
+ op (absolute_time, const struct GNUNET_TIME_Absolute) \
+ op (timestamp, const struct GNUNET_TIME_Timestamp) \
+ op (wire_deadline, const struct GNUNET_TIME_Timestamp) \
+ op (refund_deadline, const struct GNUNET_TIME_Timestamp) \
+ op (exchange_pub, const struct TALER_ExchangePublicKeyP) \
+ op (exchange_sig, const struct TALER_ExchangeSignatureP) \
+ op (blinding_key, const union GNUNET_CRYPTO_BlindingSecretP) \
+ op (h_blinded_coin, const struct TALER_BlindedCoinHashP)
TALER_TESTING_SIMPLE_TRAITS (TALER_TESTING_MAKE_DECL_SIMPLE_TRAIT)
diff --git a/src/testing/.gitignore b/src/testing/.gitignore
index 6cac81c82..e1075ab16 100644
--- a/src/testing/.gitignore
+++ b/src/testing/.gitignore
@@ -10,6 +10,8 @@ test_exchange_api_revocation_cs
test_exchange_api_revocation_rsa
test_exchange_api_age_restriction_cs
test_exchange_api_age_restriction_rsa
+test_exchange_api_conflicts_cs
+test_exchange_api_conflicts_rsa
report*
test_exchange_management_api_cs
test_exchange_management_api_rsa
diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am
index 6a07c933d..03118d77a 100644
--- a/src/testing/Makefile.am
+++ b/src/testing/Makefile.am
@@ -155,6 +155,7 @@ check_PROGRAMS = \
test_exchange_api_rsa \
test_exchange_api_age_restriction_cs \
test_exchange_api_age_restriction_rsa \
+ test_exchange_api_conflicts_rsa \
test_exchange_api_keys_cherry_picking_cs \
test_exchange_api_keys_cherry_picking_rsa \
test_exchange_api_revocation_cs \
@@ -307,6 +308,38 @@ test_exchange_api_age_restriction_rsa_LDADD = \
-ljansson \
$(XLIB)
+test_exchange_api_conflicts_cs_SOURCES = \
+ test_exchange_api_conflicts.c
+test_exchange_api_conflicts_cs_LDADD = \
+ libtalertesting.la \
+ $(top_builddir)/src/lib/libtalerexchange.la \
+ $(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/bank-lib/libtalerfakebank.la \
+ $(top_builddir)/src/bank-lib/libtalerbank.la \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/extensions/libtalerextensions.la \
+ -lgnunetcurl \
+ -lgnunetutil \
+ -ljansson \
+ $(XLIB)
+
+test_exchange_api_conflicts_rsa_SOURCES = \
+ test_exchange_api_conflicts.c
+test_exchange_api_conflicts_rsa_LDADD = \
+ libtalertesting.la \
+ $(top_builddir)/src/lib/libtalerexchange.la \
+ $(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/bank-lib/libtalerfakebank.la \
+ $(top_builddir)/src/bank-lib/libtalerbank.la \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/extensions/libtalerextensions.la \
+ -lgnunetcurl \
+ -lgnunetutil \
+ -ljansson \
+ $(XLIB)
+
test_exchange_p2p_cs_SOURCES = \
test_exchange_p2p.c
test_exchange_p2p_cs_LDADD = \
diff --git a/src/testing/test_exchange_api_conflicts-cs.conf b/src/testing/test_exchange_api_conflicts-cs.conf
new file mode 100644
index 000000000..c15d55490
--- /dev/null
+++ b/src/testing/test_exchange_api_conflicts-cs.conf
@@ -0,0 +1,4 @@
+# This file is in the public domain.
+#
+@INLINE@ test_exchange_api_conflicts.conf
+@INLINE@ coins-cs.conf
diff --git a/src/testing/test_exchange_api_conflicts-rsa.conf b/src/testing/test_exchange_api_conflicts-rsa.conf
new file mode 100644
index 000000000..f56111eee
--- /dev/null
+++ b/src/testing/test_exchange_api_conflicts-rsa.conf
@@ -0,0 +1,4 @@
+# This file is in the public domain.
+#
+@INLINE@ test_exchange_api_conflicts.conf
+@INLINE@ coins-rsa.conf
diff --git a/src/testing/test_exchange_api_conflicts.c b/src/testing/test_exchange_api_conflicts.c
new file mode 100644
index 000000000..89851446f
--- /dev/null
+++ b/src/testing/test_exchange_api_conflicts.c
@@ -0,0 +1,220 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 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/test_exchange_api_conflicts.c
+ * @brief testcase to test exchange's handling of coin conflicts: same private
+ * keys but different denominations or age restrictions
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_signatures.h"
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_testing_lib.h>
+#include <microhttpd.h>
+#include "taler_bank_service.h"
+#include "taler_fakebank_lib.h"
+#include "taler_testing_lib.h"
+#include "taler_extensions.h"
+
+/**
+ * Configuration file we use. One (big) configuration is used
+ * for the various components for this test.
+ */
+static char *config_file;
+
+/**
+ * Our credentials.
+ */
+static struct TALER_TESTING_Credentials cred;
+
+/**
+ * Some tests behave differently when using CS as we cannot
+ * reuse the coin private key for different denominations
+ * due to the derivation of it with the /csr values. Hence
+ * some tests behave differently in CS mode, hence this
+ * flag.
+ */
+static bool uses_cs;
+
+/**
+ * Execute the taler-exchange-wirewatch command with
+ * our configuration file.
+ *
+ * @param label label to use for the command.
+ */
+#define CMD_EXEC_WIREWATCH(label) \
+ TALER_TESTING_cmd_exec_wirewatch2 (label, config_file, \
+ "exchange-account-2")
+
+/**
+ * Execute the taler-exchange-aggregator, closer and transfer commands with
+ * our configuration file.
+ *
+ * @param label label to use for the command.
+ */
+#define CMD_EXEC_AGGREGATOR(label) \
+ TALER_TESTING_cmd_sleep ("sleep-before-aggregator", 2), \
+ TALER_TESTING_cmd_exec_aggregator (label "-aggregator", config_file), \
+ TALER_TESTING_cmd_exec_transfer (label "-transfer", config_file)
+
+
+/**
+ * Run wire transfer of funds from some user's account to the
+ * exchange.
+ *
+ * @param label label to use for the command.
+ * @param amount amount to transfer, i.e. "EUR:1"
+ */
+#define CMD_TRANSFER_TO_EXCHANGE(label,amount) \
+ TALER_TESTING_cmd_admin_add_incoming (label, amount, \
+ &cred.ba, \
+ cred.user42_payto)
+
+/**
+ * Main function that will tell the interpreter what commands to
+ * run.
+ *
+ * @param cls closure
+ * @param is interpreter we use to run commands
+ */
+static void
+run (void *cls,
+ struct TALER_TESTING_Interpreter *is)
+{
+ (void) cls;
+ /**
+ * Test withdrawal with conflicting coins.
+ */
+ struct TALER_TESTING_Command withdraw_conflict_denom[] = {
+ /**
+ * Move money to the exchange's bank account.
+ */
+ CMD_TRANSFER_TO_EXCHANGE ("create-reserve-denom",
+ "EUR:21.03"),
+ TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-denom",
+ "EUR:21.03",
+ cred.user42_payto,
+ cred.exchange_payto,
+ "create-reserve-denom"),
+ /**
+ * Make a reserve exist, according to the previous
+ * transfer.
+ */
+ CMD_EXEC_WIREWATCH ("wirewatch-conflict-denom"),
+ /**
+ * Withdraw EUR:1, EUR:5, EUR:15, but using the same private key each time.
+ */
+ TALER_TESTING_cmd_batch_withdraw_with_conflict ("withdraw-coin-denom-1",
+ "create-reserve-denom",
+ Conflict_Denom,
+ 0, /* age */
+ MHD_HTTP_OK,
+ "EUR:1",
+ "EUR:5",
+ "EUR:10",
+ NULL),
+
+ TALER_TESTING_cmd_end ()
+ };
+
+ struct TALER_TESTING_Command spend_conflict_denom[] = {
+ /**
+ * Spend the coin.
+ */
+ TALER_TESTING_cmd_deposit ("deposit",
+ "withdraw-coin-denom-1",
+ 0,
+ cred.user42_payto,
+ "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
+ GNUNET_TIME_UNIT_ZERO,
+ "EUR:0.99",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_deposit ("deposit-denom-conflict",
+ "withdraw-coin-denom-1",
+ 1,
+ cred.user42_payto,
+ "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
+ GNUNET_TIME_UNIT_ZERO,
+ "EUR:4.99",
+ /* FIXME: this fails for cs denominations! */
+ MHD_HTTP_CONFLICT),
+ TALER_TESTING_cmd_end ()
+ };
+
+
+ {
+ struct TALER_TESTING_Command commands[] = {
+ TALER_TESTING_cmd_run_fakebank ("run-fakebank",
+ cred.cfg,
+ "exchange-account-2"),
+ TALER_TESTING_cmd_system_start ("start-taler",
+ config_file,
+ "-e",
+ NULL),
+ TALER_TESTING_cmd_get_exchange ("get-exchange",
+ cred.cfg,
+ NULL,
+ true,
+ true),
+ TALER_TESTING_cmd_batch ("withdraw-conflict-denom",
+ withdraw_conflict_denom),
+ TALER_TESTING_cmd_batch ("spend-conflict-denom",
+ spend_conflict_denom),
+ /* End the suite. */
+ TALER_TESTING_cmd_end ()
+ };
+
+ TALER_TESTING_run (is,
+ commands);
+ }
+}
+
+
+int
+main (int argc,
+ char *const *argv)
+{
+ (void) argc;
+ {
+ char *cipher;
+
+ cipher = GNUNET_STRINGS_get_suffix_from_binary_name (argv[0]);
+ GNUNET_assert (NULL != cipher);
+ uses_cs = (0 == strcmp (cipher,
+ "cs"));
+ GNUNET_asprintf (&config_file,
+ "test_exchange_api_conflicts-%s.conf",
+ cipher);
+ GNUNET_free (cipher);
+ }
+ return TALER_TESTING_main (argv,
+ "INFO",
+ config_file,
+ "exchange-account-2",
+ TALER_TESTING_BS_FAKEBANK,
+ &cred,
+ &run,
+ NULL);
+}
+
+
+/* end of test_exchange_api_conflicts.c */
diff --git a/src/testing/test_exchange_api_conflicts.conf b/src/testing/test_exchange_api_conflicts.conf
new file mode 100644
index 000000000..d04379f05
--- /dev/null
+++ b/src/testing/test_exchange_api_conflicts.conf
@@ -0,0 +1,81 @@
+# This file is in the public domain.
+#
+
+[PATHS]
+TALER_TEST_HOME = test_exchange_api_home/
+
+[taler]
+CURRENCY = EUR
+CURRENCY_ROUND_UNIT = EUR:0.01
+
+[auditor]
+BASE_URL = "http://localhost:8083/"
+PORT = 8083
+PUBLIC_KEY = T0XJ9QZ59YDN7QG3RE40SB2HY7W0ASR1EKF4WZDGZ1G159RSQC80
+TINY_AMOUNT = EUR:0.01
+
+[auditordb-postgres]
+CONFIG = "postgres:///talercheck"
+
+[bank]
+HTTP_PORT = 8082
+
+[exchange]
+TERMS_ETAG = tos
+PRIVACY_ETAG = 0
+PORT = 8081
+AML_THRESHOLD = "EUR:99999999"
+MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
+DB = postgres
+BASE_URL = "http://localhost:8081/"
+EXPIRE_SHARD_SIZE ="300 ms"
+EXPIRE_IDLE_SLEEP_INTERVAL ="1 s"
+
+[exchangedb-postgres]
+CONFIG = "postgres:///talercheck"
+
+[taler-exchange-secmod-cs]
+LOOKAHEAD_SIGN = "24 days"
+
+[taler-exchange-secmod-rsa]
+LOOKAHEAD_SIGN = "24 days"
+
+[taler-exchange-secmod-eddsa]
+LOOKAHEAD_SIGN = "24 days"
+DURATION = "14 days"
+
+
+[exchange-account-1]
+PAYTO_URI = "payto://x-taler-bank/localhost/42?receiver-name=42"
+ENABLE_DEBIT = YES
+ENABLE_CREDIT = YES
+
+[exchange-accountcredentials-1]
+WIRE_GATEWAY_AUTH_METHOD = none
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/42/taler-wire-gateway/"
+
+[admin-accountcredentials-1]
+WIRE_GATEWAY_AUTH_METHOD = none
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/42/taler-wire-gateway/"
+
+[exchange-account-2]
+PAYTO_URI = "payto://x-taler-bank/localhost/2?receiver-name=2"
+ENABLE_DEBIT = YES
+ENABLE_CREDIT = YES
+
+[exchange-accountcredentials-2]
+WIRE_GATEWAY_AUTH_METHOD = basic
+USERNAME = Exchange
+PASSWORD = x
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/"
+
+[admin-accountcredentials-2]
+WIRE_GATEWAY_AUTH_METHOD = basic
+USERNAME = Exchange
+PASSWORD = x
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/"
+
+
+[exchange-extension-age_restriction]
+ENABLED = YES
+#AGE_GROUPS = "8:10:12:14:16:18:21"
diff --git a/src/testing/testing_api_cmd_batch_withdraw.c b/src/testing/testing_api_cmd_batch_withdraw.c
index 75311e7dc..a93fadace 100644
--- a/src/testing/testing_api_cmd_batch_withdraw.c
+++ b/src/testing/testing_api_cmd_batch_withdraw.c
@@ -173,6 +173,11 @@ struct BatchWithdrawState
* Same for all coins in the batch.
*/
uint8_t age;
+
+ /**
+ * Force a conflict:
+ */
+ enum TALER_TESTING_CoinConflictType force_conflict;
};
@@ -264,6 +269,9 @@ batch_withdraw_run (void *cls,
const struct TALER_TESTING_Command *create_reserve;
const struct TALER_EXCHANGE_DenomPublicKey *dpk;
struct TALER_EXCHANGE_WithdrawCoinInput wcis[ws->num_coins];
+ struct TALER_PlanchetMasterSecretP conflict_ps = {0};
+ struct TALER_AgeMask mask = {0};
+ struct GNUNET_HashCode seed = {0};
(void) cmd;
ws->is = is;
@@ -296,12 +304,42 @@ batch_withdraw_run (void *cls,
= TALER_reserve_make_payto (ws->exchange_url,
&ws->reserve_pub);
+ if (0 < ws->age)
+ mask = TALER_extensions_get_age_restriction_mask ();
+
+ if (Conflict_Denom == ws->force_conflict)
+ TALER_planchet_master_setup_random (&conflict_ps);
+
+ if (Conflict_Age == ws->force_conflict)
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &seed,
+ sizeof(seed));
+
for (unsigned int i = 0; i<ws->num_coins; i++)
{
struct CoinState *cs = &ws->coins[i];
struct TALER_EXCHANGE_WithdrawCoinInput *wci = &wcis[i];
- TALER_planchet_master_setup_random (&cs->ps);
+ if (Conflict_Denom == ws->force_conflict)
+ cs->ps = conflict_ps;
+ else
+ TALER_planchet_master_setup_random (&cs->ps);
+
+ if (0 < ws->age)
+ {
+ if (Conflict_Age != ws->force_conflict)
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &seed,
+ sizeof(seed));
+ TALER_age_restriction_commit (&mask,
+ ws->age,
+ &seed,
+ &cs->age_commitment_proof);
+ TALER_age_commitment_hash (&cs->age_commitment_proof.commitment,
+ &cs->h_age_commitment);
+ }
+
+
dpk = TALER_TESTING_find_pk (keys,
&cs->amount,
ws->age > 0);
@@ -453,12 +491,14 @@ batch_withdraw_traits (void *cls,
struct TALER_TESTING_Command
-TALER_TESTING_cmd_batch_withdraw (const char *label,
- const char *reserve_reference,
- uint8_t age,
- unsigned int expected_response_code,
- const char *amount,
- ...)
+TALER_TESTING_cmd_batch_withdraw_with_conflict (
+ const char *label,
+ const char *reserve_reference,
+ enum TALER_TESTING_CoinConflictType conflict,
+ uint8_t age,
+ unsigned int expected_response_code,
+ const char *amount,
+ ...)
{
struct BatchWithdrawState *ws;
unsigned int cnt;
@@ -468,6 +508,7 @@ TALER_TESTING_cmd_batch_withdraw (const char *label,
ws->age = age;
ws->reserve_reference = reserve_reference;
ws->expected_response_code = expected_response_code;
+ ws->force_conflict = conflict;
cnt = 1;
va_start (ap,
@@ -485,23 +526,6 @@ TALER_TESTING_cmd_batch_withdraw (const char *label,
{
struct CoinState *cs = &ws->coins[i];
- if (0 < age)
- {
- struct GNUNET_HashCode seed;
- struct TALER_AgeMask mask;
-
- mask = TALER_extensions_get_age_restriction_mask ();
- GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
- &seed,
- sizeof(seed));
- TALER_age_restriction_commit (&mask,
- age,
- &seed,
- &cs->age_commitment_proof);
- TALER_age_commitment_hash (&cs->age_commitment_proof.commitment,
- &cs->h_age_commitment);
- }
-
if (GNUNET_OK !=
TALER_string_to_amount (amount,
&cs->amount))