summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorÖzgür Kesim <oec-taler@kesim.org>2023-12-22 22:14:20 +0100
committerÖzgür Kesim <oec-taler@kesim.org>2023-12-22 22:14:20 +0100
commitf7745020a3eb7f63194956ada075862bd1d0a6d6 (patch)
treedbf6cc5f60448650cf290a4c024d790ebaadd6b4
parent1217a57e3e511f5749853d2362162348e542dc8d (diff)
downloadexchange-f7745020a3eb7f63194956ada075862bd1d0a6d6.tar.gz
exchange-f7745020a3eb7f63194956ada075862bd1d0a6d6.tar.bz2
exchange-f7745020a3eb7f63194956ada075862bd1d0a6d6.zip
[wip, #7267] added first test to detect denom-conflict on deposit
The tests have been extended to include conflict tests, see test_exchange_api_conflicts.c The first implemented test withdraws three coins with different denominations, but using the same private key. It deposits the first coin successfully, but on deposit of the second coin it receives 409 and the correct response details. However, the test only seems to work for RSA, not CS! I've disabled test_exchange_conflicts_cs in the Makefile for now so that the builder passes. TBC...
-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))