commit 15d64a09f8bd4493bf1782506efde01387b06b31 parent 247e96cff1204cebd87e965cbf698e3506efdc52 Author: Christian Blättler <blatc2@bfh.ch> Date: Mon, 13 Nov 2023 11:31:24 +0100 Merge branch 'master' into feature/tokens Diffstat:
50 files changed, 1304 insertions(+), 426 deletions(-)
diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c @@ -46,6 +46,7 @@ #include "taler-exchange-httpd_kyc-check.h" #include "taler-exchange-httpd_kyc-proof.h" #include "taler-exchange-httpd_kyc-wallet.h" +#include "taler-exchange-httpd_kyc-webhook.h" #include "taler-exchange-httpd_link.h" #include "taler-exchange-httpd_management.h" #include "taler-exchange-httpd_melt.h" @@ -1021,6 +1022,11 @@ handle_mhd_completion_callback (void *cls, TEH_check_invariants (); if (NULL != rc->rh_cleaner) rc->rh_cleaner (rc); + if (NULL != rc->root) + { + json_decref (rc->root); + rc->root = NULL; + } TEH_check_invariants (); { #if MHD_VERSION >= 0x00097304 @@ -1087,7 +1093,6 @@ proceed_with_handler (struct TEH_RequestContext *rc, const struct TEH_RequestHandler *rh = rc->rh; const char *args[rh->nargs + 2]; size_t ulen = strlen (url) + 1; - json_t *root = NULL; MHD_RESULT ret; /* We do check for "ulen" here, because we'll later stack-allocate a buffer @@ -1108,8 +1113,9 @@ proceed_with_handler (struct TEH_RequestContext *rc, /* All POST endpoints come with a body in JSON format. So we parse the JSON here. */ - if (0 == strcasecmp (rh->method, - MHD_HTTP_METHOD_POST)) + if ( (0 == strcasecmp (rh->method, + MHD_HTTP_METHOD_POST)) && + (NULL == rc->root) ) { enum GNUNET_GenericReturnValue res; @@ -1117,16 +1123,16 @@ proceed_with_handler (struct TEH_RequestContext *rc, &rc->opaque_post_parsing_context, upload_data, upload_data_size, - &root); + &rc->root); if (GNUNET_SYSERR == res) { - GNUNET_assert (NULL == root); + GNUNET_assert (NULL == rc->root); return MHD_NO; /* bad upload, could not even generate error */ } if ( (GNUNET_NO == res) || - (NULL == root) ) + (NULL == rc->root) ) { - GNUNET_assert (NULL == root); + GNUNET_assert (NULL == rc->root); return MHD_YES; /* so far incomplete upload or parser error */ } } @@ -1163,7 +1169,6 @@ proceed_with_handler (struct TEH_RequestContext *rc, rh->url, url); GNUNET_break_op (0); - json_decref (root); return TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_NOT_FOUND, TALER_EC_EXCHANGE_GENERIC_WRONG_NUMBER_OF_SEGMENTS, @@ -1176,7 +1181,7 @@ proceed_with_handler (struct TEH_RequestContext *rc, if (0 == strcasecmp (rh->method, MHD_HTTP_METHOD_POST)) ret = rh->handler.post (rc, - root, + rc->root, args); else if (0 == strcasecmp (rh->method, MHD_HTTP_METHOD_DELETE)) @@ -1186,7 +1191,6 @@ proceed_with_handler (struct TEH_RequestContext *rc, ret = rh->handler.get (rc, args); } - json_decref (root); return ret; } @@ -1723,6 +1727,20 @@ handle_mhd_request (void *cls, .handler.post = &TEH_handler_kyc_wallet, .nargs = 0 }, + { + .url = "kyc-webhook", + .method = MHD_HTTP_METHOD_GET, + .handler.get = &TEH_handler_kyc_webhook_get, + .nargs = 16, /* more is not plausible */ + .nargs_is_upper_bound = true + }, + { + .url = "kyc-webhook", + .method = MHD_HTTP_METHOD_POST, + .handler.post = &TEH_handler_kyc_webhook_post, + .nargs = 16, /* more is not plausible */ + .nargs_is_upper_bound = true + }, /* POST management endpoints */ { .url = "management", diff --git a/src/exchange/taler-exchange-httpd.h b/src/exchange/taler-exchange-httpd.h @@ -199,6 +199,11 @@ struct TEH_RequestContext struct MHD_Connection *connection; /** + * JSON root of uploaded data (or NULL, if none). + */ + json_t *root; + + /** * @e rh-specific cleanup routine. Function called * upon completion of the request that should * clean up @a rh_ctx. Can be NULL. diff --git a/src/exchange/taler-exchange-httpd_age-withdraw.c b/src/exchange/taler-exchange-httpd_age-withdraw.c @@ -69,10 +69,9 @@ struct AgeWithdrawContext uint32_t num_coins; /** - * kappa * #num_coins hashes of blinded coin planchets. - * FIXME[oec]: Make the [][] structure more explicit. + * #num_coins * #kappa hashes of blinded coin planchets. */ - struct TALER_BlindedPlanchet *coin_evs; + struct TALER_BlindedPlanchet (*coin_evs)[TALER_CNC_KAPPA]; /** * #num_coins hashes of the denominations from which the coins are withdrawn. @@ -189,13 +188,13 @@ parse_age_withdraw_json ( } }; - /* no overflow because - * num_coins <= TALER_MAX_FRESH_COINS - * and - * TALER_MAX_FRESH_COINS * TALER_CNC_KAPPA < INT_MAX - */ - awc->coin_evs = GNUNET_new_array (awc->num_coins * TALER_CNC_KAPPA, - struct TALER_BlindedPlanchet); + { + typedef struct TALER_BlindedPlanchet + _array_of_kappa_planchets[TALER_CNC_KAPPA]; + + awc->coin_evs = GNUNET_new_array (awc->num_coins, + _array_of_kappa_planchets); + } hash_context = GNUNET_CRYPTO_hash_context_start (); GNUNET_assert (NULL != hash_context); @@ -225,15 +224,13 @@ parse_age_withdraw_json ( /* Now parse the individual kappa envelopes and calculate the hash of * the commitment along the way. */ { - size_t off = idx * TALER_CNC_KAPPA; unsigned int kappa = 0; enum GNUNET_GenericReturnValue ret; json_array_foreach (j_kappa_coin_evs, kappa, value) { struct GNUNET_JSON_Specification spec[] = { - /* FIXME-Oec: This allocation is never freed! */ TALER_JSON_spec_blinded_planchet (NULL, - &awc->coin_evs[off + kappa]), + &awc->coin_evs[idx][kappa]), GNUNET_JSON_spec_end () }; @@ -256,7 +253,7 @@ parse_age_withdraw_json ( { struct TALER_BlindedCoinHashP bch; - ret = TALER_coin_ev_hash (&awc->coin_evs[off + kappa], + ret = TALER_coin_ev_hash (&awc->coin_evs[idx][kappa], &awc->denom_hs[idx], &bch); GNUNET_assert (GNUNET_OK == ret); @@ -268,17 +265,17 @@ parse_age_withdraw_json ( /* Check for duplicate planchets. Technically a bug on * the client side that is harmless for us, but still * not allowed per protocol */ - for (unsigned int i = 0; i < off + kappa; i++) + for (unsigned int i = 0; i < idx; i++) { - if (0 == TALER_blinded_planchet_cmp (&awc->coin_evs[off + kappa], - &awc->coin_evs[i])) + if (0 == TALER_blinded_planchet_cmp (&awc->coin_evs[idx][kappa], + &awc->coin_evs[i][kappa])) { + GNUNET_JSON_parse_free (spec); error = "duplicate planchet"; goto EXIT; } } } - } }; /* json_array_foreach over j_blinded_coin_evs */ @@ -400,7 +397,7 @@ denomination_is_valid ( * @param connection The HTTP connection to the client * @param len The lengths of the array @a denoms_h * @param denom_hs array of hashes of denomination public keys - * @param coin_evs array of blinded coin planchets + * @param coin_evs array of blinded coin planchet candidates * @param[out] denom_serials On success, will be filled with the serial-id's of the denomination keys. Caller must deallocate. * @param[out] amount_with_fee On success, will contain the committed amount including fees * @param[out] result In the error cases, a response will be queued with MHD and this will be the result. @@ -412,7 +409,7 @@ are_denominations_valid ( struct MHD_Connection *connection, uint32_t len, const struct TALER_DenominationHashP *denom_hs, - const struct TALER_BlindedPlanchet *coin_evs, + const struct TALER_BlindedPlanchet (*coin_evs)[TALER_CNC_KAPPA], uint64_t **denom_serials, struct TALER_Amount *amount_with_fee, MHD_RESULT *result) @@ -453,14 +450,17 @@ are_denominations_valid ( return GNUNET_SYSERR; /* Ensure the ciphers from the planchets match the denominations' */ - if (dk->denom_pub.bsign_pub_key->cipher != - coin_evs[i].blinded_message->cipher) + for (uint8_t k=0; k < TALER_CNC_KAPPA; k++) { - GNUNET_break_op (0); - *result = TALER_MHD_reply_with_ec (connection, - TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH, - NULL); - return GNUNET_SYSERR; + if (dk->denom_pub.bsign_pub_key->cipher != + coin_evs[i][k].blinded_message->cipher) + { + GNUNET_break_op (0); + *result = TALER_MHD_reply_with_ec (connection, + TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH, + NULL); + return GNUNET_SYSERR; + } } /* Accumulate the values */ @@ -865,7 +865,7 @@ sign_and_do_age_withdraw ( /* Pick the chosen blinded coins */ for (uint32_t i = 0; i<awc->num_coins; i++) { - csds[i].bp = &awc->coin_evs[TALER_CNC_KAPPA * i + noreveal_index]; + csds[i].bp = &awc->coin_evs[i][noreveal_index]; csds[i].h_denom_pub = &awc->denom_hs[i]; } @@ -889,7 +889,7 @@ sign_and_do_age_withdraw ( /* Prepare the hashes of the coins for insertion */ for (uint32_t i = 0; i<awc->num_coins; i++) { - TALER_coin_ev_hash (&awc->coin_evs[TALER_CNC_KAPPA * i + noreveal_index], + TALER_coin_ev_hash (&awc->coin_evs[i][noreveal_index], &awc->denom_hs[i], &h_coin_evs[i]); } @@ -1009,7 +1009,7 @@ TEH_handler_age_withdraw (struct TEH_RequestContext *rc, &awc.commitment.h_commitment, awc.commitment.noreveal_index); - } while(0); + } while (0); GNUNET_JSON_parse_free (spec); free_age_withdraw_context_resources (&awc); diff --git a/src/exchange/taler-exchange-httpd_kyc-check.c b/src/exchange/taler-exchange-httpd_kyc-check.c @@ -238,7 +238,8 @@ initiate_cb ( kyp->ih = NULL; kyp->ih_done = true; GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "KYC initiation completed with ec=%d (%s)\n", + "KYC initiation `%s' completed with ec=%d (%s)\n", + provider_legitimization_id, ec, (TALER_EC_NONE == ec) ? redirect_url @@ -259,8 +260,9 @@ initiate_cb ( &kyp->h_payto, provider_user_id, provider_legitimization_id, + redirect_url, GNUNET_TIME_UNIT_ZERO_ABS); - if (qs < 0) + if (qs <= 0) GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "KYC requirement update failed for %s with status %d at %s:%u\n", TALER_B2S (&kyp->h_payto), @@ -302,6 +304,7 @@ kyc_check (void *cls, enum GNUNET_GenericReturnValue ret; struct TALER_PaytoHashP h_payto; char *requirements; + char *redirect_url; bool satisfied; qs = TEH_plugin->lookup_kyc_requirement_by_row ( @@ -387,7 +390,27 @@ kyc_check (void *cls, if (kyp->ih_done) return qs; - + qs = TEH_plugin->get_pending_kyc_requirement_process ( + TEH_plugin->cls, + &h_payto, + kyp->section_name, + &redirect_url); + if (qs < 0) + { + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + return qs; + GNUNET_break (0); + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_STORE_FAILED, + "insert_kyc_requirement_process"); + return GNUNET_DB_STATUS_HARD_ERROR; + } + if (qs > 0) + { + kyp->kyc_url = redirect_url; + return qs; + } qs = TEH_plugin->insert_kyc_requirement_process ( TEH_plugin->cls, &h_payto, @@ -524,6 +547,17 @@ TEH_handler_kyc_check ( TALER_MHD_parse_request_timeout (rc->connection, &kyp->timeout); } + /* KYC plugin generated reply? */ + if (NULL != kyp->kyc_url) + { + return TALER_MHD_REPLY_JSON_PACK ( + rc->connection, + MHD_HTTP_ACCEPTED, + GNUNET_JSON_pack_uint64 ("aml_status", + kyp->aml_status), + GNUNET_JSON_pack_string ("kyc_url", + kyp->kyc_url)); + } if ( (NULL == kyp->eh) && GNUNET_TIME_absolute_is_future (kyp->timeout) ) @@ -557,6 +591,17 @@ TEH_handler_kyc_check ( "Transaction failed.\n"); return res; } + /* KYC plugin generated reply? */ + if (NULL != kyp->kyc_url) + { + return TALER_MHD_REPLY_JSON_PACK ( + rc->connection, + MHD_HTTP_ACCEPTED, + GNUNET_JSON_pack_uint64 ("aml_status", + kyp->aml_status), + GNUNET_JSON_pack_string ("kyc_url", + kyp->kyc_url)); + } if ( (NULL == kyp->ih) && (! kyp->kyc_required) ) @@ -615,18 +660,6 @@ TEH_handler_kyc_check ( return MHD_YES; } - /* KYC plugin generated reply? */ - if (NULL != kyp->kyc_url) - { - return TALER_MHD_REPLY_JSON_PACK ( - rc->connection, - MHD_HTTP_ACCEPTED, - GNUNET_JSON_pack_uint64 ("aml_status", - kyp->aml_status), - GNUNET_JSON_pack_string ("kyc_url", - kyp->kyc_url)); - } - if (TALER_EC_NONE != kyp->ec) { return TALER_MHD_reply_with_ec (rc->connection, diff --git a/src/exchange/taler-exchange-httpd_kyc-webhook.c b/src/exchange/taler-exchange-httpd_kyc-webhook.c @@ -227,8 +227,9 @@ webhook_finished_cb ( response = TALER_MHD_make_error ( TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION, "[exchange] AML_KYC_TRIGGER"); + break; } - break; + return; default: GNUNET_log (GNUNET_ERROR_TYPE_INFO, "KYC status of %s/%s (Row #%llu) is %d\n", @@ -238,10 +239,10 @@ webhook_finished_cb ( status); break; } - if (NULL == kwh->kat) - kyc_aml_webhook_finished (kwh, - http_status, - response); + GNUNET_break (NULL == kwh->kat); + kyc_aml_webhook_finished (kwh, + http_status, + response); } @@ -314,6 +315,10 @@ handler_kyc_webhook_generic ( TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN, args[0]); } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "KYC logic `%s' mapped to section %s\n", + args[0], + kwh->provider_section); kwh->wh = kwh->plugin->webhook (kwh->plugin->cls, kwh->pd, TEH_plugin->kyc_provider_account_lookup, @@ -339,13 +344,17 @@ handler_kyc_webhook_generic ( MHD_suspend_connection (rc->connection); return MHD_YES; } + GNUNET_break (GNUNET_NO == kwh->suspended); if (NULL != kwh->response) { - /* handle _failed_ resumed cases */ - return MHD_queue_response (rc->connection, - kwh->response_code, - kwh->response); + MHD_RESULT res; + + res = MHD_queue_response (rc->connection, + kwh->response_code, + kwh->response); + GNUNET_break (MHD_YES == res); + return res; } /* We resumed, but got no response? This should diff --git a/src/exchangedb/0002-legitimization_processes.sql b/src/exchangedb/0002-legitimization_processes.sql @@ -25,12 +25,13 @@ BEGIN 'CREATE TABLE %I' '(legitimization_process_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY' ',h_payto BYTEA NOT NULL CHECK (LENGTH(h_payto)=32)' + ',start_time INT8 NOT NULL' ',expiration_time INT8 NOT NULL DEFAULT (0)' ',provider_section TEXT NOT NULL' ',provider_user_id TEXT DEFAULT NULL' ',provider_legitimization_id TEXT DEFAULT NULL' + ',redirect_url TEXT DEFAULT NULL' ',finished BOOLEAN DEFAULT (FALSE)' - ',UNIQUE (h_payto, provider_section)' ') %s ;' ,'legitimization_processes' ,'PARTITION BY HASH (h_payto)' @@ -54,6 +55,18 @@ BEGIN ,shard_suffix ); PERFORM comment_partitioned_column( + 'time when the KYC check was initiated, useful for garbage collection' + ,'expiration_time' + ,'legitimization_processes' + ,shard_suffix + ); + PERFORM comment_partitioned_column( + 'URL where the user should go to begin the KYC process' + ,'redirect_url' + ,'legitimization_processes' + ,shard_suffix + ); + PERFORM comment_partitioned_column( 'in the future if the respective KYC check was passed successfully' ,'expiration_time' ,'legitimization_processes' diff --git a/src/exchangedb/Makefile.am b/src/exchangedb/Makefile.am @@ -77,6 +77,7 @@ libtaler_plugin_exchangedb_postgres_la_SOURCES = \ pg_select_aggregation_amounts_for_kyc_check.h pg_select_aggregation_amounts_for_kyc_check.c \ pg_lookup_wire_fee_by_time.h pg_lookup_wire_fee_by_time.c \ pg_select_satisfied_kyc_processes.h pg_select_satisfied_kyc_processes.c \ + pg_get_pending_kyc_requirement_process.h pg_get_pending_kyc_requirement_process.c \ pg_kyc_provider_account_lookup.h pg_kyc_provider_account_lookup.c \ pg_lookup_kyc_requirement_by_row.h pg_lookup_kyc_requirement_by_row.c \ pg_insert_kyc_requirement_for_account.h pg_insert_kyc_requirement_for_account.c \ diff --git a/src/exchangedb/pg_get_pending_kyc_requirement_process.c b/src/exchangedb/pg_get_pending_kyc_requirement_process.c @@ -0,0 +1,63 @@ +/* + 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 exchangedb/pg_get_pending_kyc_requirement_process.c + * @brief Implementation of the get_pending_kyc_requirement_process function for Postgres + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_error_codes.h" +#include "taler_dbevents.h" +#include "taler_pq_lib.h" +#include "pg_get_pending_kyc_requirement_process.h" +#include "pg_helper.h" + + +enum GNUNET_DB_QueryStatus +TEH_PG_get_pending_kyc_requirement_process ( + void *cls, + const struct TALER_PaytoHashP *h_payto, + const char *provider_section, + char **redirect_url) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (provider_section), + GNUNET_PQ_query_param_auto_from_type (h_payto), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_string ("redirect_url", + redirect_url), + GNUNET_PQ_result_spec_end + }; + + PREPARE (pg, + "get_pending_kyc_requirement_process", + "SELECT" + " redirect_url" + " FROM legitimization_processes" + " WHERE provider_section=$1" + " AND h_payto=$2" + " AND NOT finished" + " ORDER BY start_time DESC" + " LIMIT 1"); + return GNUNET_PQ_eval_prepared_singleton_select ( + pg->conn, + "get_pending_kyc_requirement_process", + params, + rs); +} diff --git a/src/exchangedb/pg_get_pending_kyc_requirement_process.h b/src/exchangedb/pg_get_pending_kyc_requirement_process.h @@ -0,0 +1,45 @@ +/* + 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 exchangedb/pg_get_pending_kyc_requirement_process.h + * @brief implementation of the get_pending_kyc_requirement_process function for Postgres + * @author Christian Grothoff + */ +#ifndef PG_GET_PENDING_KYC_REQUIREMENT_PROCESS_H +#define PG_GET_PENDING_KYC_REQUIREMENT_PROCESS_H + +#include "taler_util.h" +#include "taler_json_lib.h" +#include "taler_exchangedb_plugin.h" + + +/** + * Fetch information about pending KYC requirement process. + * + * @param cls closure + * @param h_payto account that must be KYC'ed + * @param provider_section provider that must be checked + * @param[out] redirect_url set to redirect URL for the process + * @return database transaction status + */ +enum GNUNET_DB_QueryStatus +TEH_PG_get_pending_kyc_requirement_process ( + void *cls, + const struct TALER_PaytoHashP *h_payto, + const char *provider_section, + char **redirect_url); + +#endif diff --git a/src/exchangedb/pg_insert_kyc_requirement_process.c b/src/exchangedb/pg_insert_kyc_requirement_process.c @@ -36,8 +36,11 @@ TEH_PG_insert_kyc_requirement_process ( uint64_t *process_row) { struct PostgresClosure *pg = cls; + struct GNUNET_TIME_Absolute now + = GNUNET_TIME_absolute_get (); struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (h_payto), + GNUNET_PQ_query_param_absolute_time (&now), GNUNET_PQ_query_param_string (provider_section), (NULL != provider_account_id) ? GNUNET_PQ_query_param_string (provider_account_id) @@ -53,20 +56,16 @@ TEH_PG_insert_kyc_requirement_process ( GNUNET_PQ_result_spec_end }; - /* Used in #postgres_insert_kyc_requirement_process() */ PREPARE (pg, "insert_legitimization_process", "INSERT INTO legitimization_processes" " (h_payto" + " ,start_time" " ,provider_section" " ,provider_user_id" " ,provider_legitimization_id" " ) VALUES " - " ($1, $2, $3, $4)" - " ON CONFLICT (h_payto,provider_section) " - " DO UPDATE SET" - " provider_user_id=$3" - " ,provider_legitimization_id=$4" + " ($1, $2, $3, $4, $5)" " RETURNING legitimization_process_serial_id"); return GNUNET_PQ_eval_prepared_singleton_select ( pg->conn, diff --git a/src/exchangedb/pg_kyc_provider_account_lookup.c b/src/exchangedb/pg_kyc_provider_account_lookup.c @@ -36,8 +36,8 @@ TEH_PG_kyc_provider_account_lookup ( { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (provider_section), GNUNET_PQ_query_param_string (provider_legitimization_id), + GNUNET_PQ_query_param_string (provider_section), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { @@ -47,7 +47,7 @@ TEH_PG_kyc_provider_account_lookup ( process_row), GNUNET_PQ_result_spec_end }; - /* Used in #postgres_kyc_provider_account_lookup() */ + PREPARE (pg, "get_wire_target_by_legitimization_id", "SELECT " diff --git a/src/exchangedb/pg_lookup_records_by_table.c b/src/exchangedb/pg_lookup_records_by_table.c @@ -1147,7 +1147,6 @@ lrbt_cb_table_batch_deposits (void *cls, unsigned int num_results) { struct LookupRecordsByTableContext *ctx = cls; - struct PostgresClosure *pg = ctx->pg; struct TALER_EXCHANGEDB_TableData td = { .table = TALER_EXCHANGEDB_RT_BATCH_DEPOSITS }; diff --git a/src/exchangedb/pg_update_kyc_process_by_row.c b/src/exchangedb/pg_update_kyc_process_by_row.c @@ -25,6 +25,7 @@ #include "pg_update_kyc_process_by_row.h" #include "pg_helper.h" + enum GNUNET_DB_QueryStatus TEH_PG_update_kyc_process_by_row ( void *cls, @@ -33,6 +34,7 @@ TEH_PG_update_kyc_process_by_row ( const struct TALER_PaytoHashP *h_payto, const char *provider_account_id, const char *provider_legitimization_id, + const char *redirect_url, struct GNUNET_TIME_Absolute expiration) { struct PostgresClosure *pg = cls; @@ -46,17 +48,23 @@ TEH_PG_update_kyc_process_by_row ( (NULL != provider_legitimization_id) ? GNUNET_PQ_query_param_string (provider_legitimization_id) : GNUNET_PQ_query_param_null (), + GNUNET_PQ_query_param_string (redirect_url), GNUNET_PQ_query_param_absolute_time (&expiration), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Updating KYC data for %llu (%s)\n", + (unsigned long long) process_row, + provider_section); PREPARE (pg, "update_legitimization_process", "UPDATE legitimization_processes" " SET provider_user_id=$4" " ,provider_legitimization_id=$5" - " ,expiration_time=GREATEST(expiration_time,$6)" + " ,redirect_url=$6" + " ,expiration_time=GREATEST(expiration_time,$7)" " WHERE" " h_payto=$3" " AND legitimization_process_serial_id=$1" diff --git a/src/exchangedb/pg_update_kyc_process_by_row.h b/src/exchangedb/pg_update_kyc_process_by_row.h @@ -35,6 +35,7 @@ * @param h_payto account that must be KYC'ed (helps access by shard, otherwise also redundant) * @param provider_account_id provider account ID * @param provider_legitimization_id provider legitimization ID + * @param redirect_url where the user should be redirected to start the KYC process * @param expiration how long is this KYC check set to be valid (in the past if invalid) * @return database transaction status */ @@ -46,6 +47,7 @@ TEH_PG_update_kyc_process_by_row ( const struct TALER_PaytoHashP *h_payto, const char *provider_account_id, const char *provider_legitimization_id, + const char *redirect_url, struct GNUNET_TIME_Absolute expiration); #endif diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c @@ -46,6 +46,7 @@ #include "pg_insert_close_request.h" #include "pg_insert_records_by_table.h" #include "pg_insert_reserve_open_deposit.h" +#include "pg_get_pending_kyc_requirement_process.h" #include "pg_iterate_kyc_reference.h" #include "pg_iterate_reserve_close_info.h" #include "pg_lookup_records_by_table.h" @@ -744,6 +745,8 @@ libtaler_plugin_exchangedb_postgres_init (void *cls) = &TEH_PG_select_purse_by_merge_pub; plugin->set_purse_balance = &TEH_PG_set_purse_balance; + plugin->get_pending_kyc_requirement_process + = &TEH_PG_get_pending_kyc_requirement_process; plugin->insert_kyc_attributes = &TEH_PG_insert_kyc_attributes; plugin->select_similar_kyc_attributes diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h @@ -3277,11 +3277,16 @@ struct TALER_EXCHANGE_RefreshData */ struct TALER_CoinSpendPrivateKeyP melt_priv; - /* - * age commitment and proof and its hash that went into the original coin, + /** + * age commitment and proof that went into the original coin, * might be NULL. */ const struct TALER_AgeCommitmentProof *melt_age_commitment_proof; + + /** + * Hash of age commitment and proof that went into the original coin, + * might be NULL. + */ const struct TALER_AgeCommitmentHash *melt_h_age_commitment; /** diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h @@ -6669,6 +6669,23 @@ struct TALER_EXCHANGEDB_Plugin /** + * Fetch information about pending KYC requirement process. + * + * @param cls closure + * @param h_payto account that must be KYC'ed + * @param provider_section provider that must be checked + * @param[out] redirect_url set to redirect URL for the process + * @return database transaction status + */ + enum GNUNET_DB_QueryStatus + (*get_pending_kyc_requirement_process)( + void *cls, + const struct TALER_PaytoHashP *h_payto, + const char *provider_section, + char **redirect_url); + + + /** * Update KYC process with updated provider-linkage and/or * expiration data. * @@ -6678,6 +6695,7 @@ struct TALER_EXCHANGEDB_Plugin * @param h_payto account that must be KYC'ed (helps access by shard, otherwise also redundant) * @param provider_account_id provider account ID * @param provider_legitimization_id provider legitimization ID + * @param redirect_url where the user should be redirected to start the KYC process * @param expiration how long is this KYC check set to be valid (in the past if invalid) * @return database transaction status */ @@ -6689,6 +6707,7 @@ struct TALER_EXCHANGEDB_Plugin const struct TALER_PaytoHashP *h_payto, const char *provider_account_id, const char *provider_legitimization_id, + const char *redirect_url, struct GNUNET_TIME_Absolute expiration); diff --git a/src/include/taler_kyclogic_plugin.h b/src/include/taler_kyclogic_plugin.h @@ -99,7 +99,13 @@ enum TALER_KYCLOGIC_KycStatus * Return code set to not update the KYC status * at all. */ - TALER_KYCLOGIC_STATUS_KEEP = 16 + TALER_KYCLOGIC_STATUS_KEEP = 16, + + /** + * We had an internal logic failure. + */ + TALER_KYCLOGIC_STATUS_INTERNAL_ERROR = 32 + }; diff --git a/src/include/taler_testing_lib.h b/src/include/taler_testing_lib.h @@ -126,6 +126,12 @@ struct TALER_TESTING_Credentials struct TALER_BANK_AuthenticationData ba; /** + * Bank authentication details for the admin bank + * account. + */ + struct TALER_BANK_AuthenticationData ba_admin; + + /** * Configuration file data. */ struct GNUNET_CONFIGURATION_Handle *cfg; @@ -591,19 +597,6 @@ TALER_TESTING_parse_coin_reference ( unsigned int *idx); -/** - * Compare @a h1 and @a h2. - * - * @param h1 a history entry - * @param h2 a history entry - * @return 0 if @a h1 and @a h2 are equal - */ -int -TALER_TESTING_history_entry_cmp ( - const struct TALER_EXCHANGE_ReserveHistoryEntry *h1, - const struct TALER_EXCHANGE_ReserveHistoryEntry *h2); - - /* ************** Specific interpreter commands ************ */ @@ -2721,6 +2714,7 @@ TALER_TESTING_get_trait (const struct TALER_TESTING_Trait *traits, 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) \ diff --git a/src/kyclogic/plugin_kyclogic_kycaid.c b/src/kyclogic/plugin_kyclogic_kycaid.c @@ -361,10 +361,10 @@ kycaid_load_configuration (void *cls, return NULL; } if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_filename (ps->cfg, - provider_section_name, - "KYC_KYCAID_CONVERTER_HELPER", - &pd->conversion_helper)) + GNUNET_CONFIGURATION_get_value_string (ps->cfg, + provider_section_name, + "KYC_KYCAID_CONVERTER_HELPER", + &pd->conversion_helper)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, provider_section_name, @@ -429,11 +429,14 @@ handle_initiate_finished (void *cls, { const char *verification_id; const char *form_url; + const char *form_id; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("verification_id", &verification_id), GNUNET_JSON_spec_string ("form_url", &form_url), + GNUNET_JSON_spec_string ("form_id", + &form_id), GNUNET_JSON_spec_end () }; @@ -455,6 +458,10 @@ handle_initiate_finished (void *cls, "type"))); break; } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Started new verification `%s' using form %s\n", + verification_id, + form_id); ih->cb (ih->cb_cls, TALER_EC_NONE, form_url, @@ -815,7 +822,7 @@ webhook_conversion_cb (void *cls, struct MHD_Response *resp; wh->econ = NULL; - if ( (0 == code) || + if ( (0 == code) && (NULL == result) ) { /* No result, but *our helper* was OK => bad input */ @@ -845,7 +852,9 @@ webhook_conversion_cb (void *cls, if (NULL == result) { /* Failure in our helper */ - GNUNET_break (0); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Helper exited with status code %d\n", + (int) code); json_dumpf (wh->json_response, stderr, JSON_INDENT (2)); @@ -905,6 +914,15 @@ handle_webhook_finished (void *cls, struct MHD_Response *resp; wh->job = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Webhook returned with HTTP status %u\n", + (unsigned int) response_code); +#if 1 + if (NULL != j) + json_dumpf (j, + stderr, + JSON_INDENT (2)); +#endif wh->kycaid_response_code = response_code; wh->json_response = json_incref ((json_t *) j); switch (response_code) @@ -952,6 +970,27 @@ handle_webhook_finished (void *cls, "-a", wh->pd->auth_token, NULL); + if (NULL == wh->econ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to start KYCAID conversion helper `%s'\n", + wh->pd->conversion_helper); + resp = TALER_MHD_make_error ( + TALER_EC_EXCHANGE_GENERIC_KYC_CONVERTER_FAILED, + NULL); + wh->cb (wh->cb_cls, + wh->process_row, + &wh->h_payto, + wh->pd->section, + wh->applicant_id, + wh->verification_id, + TALER_KYCLOGIC_STATUS_INTERNAL_ERROR, + GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */ + NULL, + MHD_HTTP_INTERNAL_SERVER_ERROR, + resp); + break; + } return; } break; @@ -1174,6 +1213,7 @@ kycaid_webhook (void *cls, const char *type; const char *verification_id; const char *applicant_id; + const char *form_id; const char *status = NULL; bool verified = false; bool no_verified = true; @@ -1187,6 +1227,8 @@ kycaid_webhook (void *cls, &verification_id), GNUNET_JSON_spec_string ("applicant_id", &applicant_id), + GNUNET_JSON_spec_string ("form_id", + &form_id), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("status", &status), @@ -1209,7 +1251,16 @@ kycaid_webhook (void *cls, wh->ps = ps; wh->pd = pd; wh->connection = connection; - + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "KYCAID webhook of `%s' triggered with %s\n", + pd->section, + http_method); +#if 1 + if (NULL != body) + json_dumpf (body, + stderr, + JSON_INDENT (2)); +#endif if (NULL == pd) { GNUNET_break_op (0); @@ -1259,8 +1310,9 @@ kycaid_webhook (void *cls, if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Received webhook for unknown verification ID `%s'\n", - verification_id); + "Received webhook for unknown verification ID `%s' and section %s\n", + verification_id, + pd->section); wh->resp = TALER_MHD_make_error ( TALER_EC_EXCHANGE_KYC_PROOF_REQUEST_UNKNOWN, verification_id); @@ -1279,6 +1331,9 @@ kycaid_webhook (void *cls, /* We don't need to re-confirm the failure by asking the API again. */ log_failure (verifications); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Webhook called with non-completion status: %s\n", + type); wh->response_code = MHD_HTTP_NO_CONTENT; wh->resp = MHD_create_response_from_buffer (0, "", diff --git a/src/kyclogic/plugin_kyclogic_oauth2.c b/src/kyclogic/plugin_kyclogic_oauth2.c @@ -1023,6 +1023,20 @@ parse_proof_success_reply (struct TALER_KYCLOGIC_ProofHandle *ph, pd->conversion_binary, pd->conversion_binary, NULL); + if (NULL == ph->ec) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to start KYCAID conversion helper `%s'\n", + pd->conversion_binary); + ph->status = TALER_KYCLOGIC_STATUS_INTERNAL_ERROR; + ph->response + = TALER_MHD_make_error ( + TALER_EC_EXCHANGE_GENERIC_KYC_CONVERTER_FAILED, + "Failed to launch KYC conversion helper"); + ph->http_status + = MHD_HTTP_INTERNAL_SERVER_ERROR; + return; + } } diff --git a/src/kyclogic/plugin_kyclogic_persona.c b/src/kyclogic/plugin_kyclogic_persona.c @@ -1257,6 +1257,20 @@ handle_proof_finished (void *cls, j, &proof_post_conversion_cb, ph); + if (NULL == ph->ec) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to start Persona conversion helper\n"); + proof_reply_error ( + ph, + ph->inquiry_id, + MHD_HTTP_BAD_GATEWAY, + "persona-logic-failure", + GNUNET_JSON_PACK ( + TALER_JSON_pack_ec ( + TALER_EC_EXCHANGE_GENERIC_KYC_CONVERTER_FAILED))); + break; + } } return; /* continued in proof_post_conversion_cb */ } @@ -1771,6 +1785,15 @@ handle_webhook_finished (void *cls, j, &webhook_post_conversion_cb, wh); + if (NULL == wh->ec) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to start Persona conversion helper\n"); + webhook_reply_error (wh, + inquiry_id, + MHD_HTTP_INTERNAL_SERVER_ERROR); + break; + } } return; /* continued in webhook_post_conversion_cb */ } diff --git a/src/kyclogic/taler-exchange-kyc-kycaid-converter.sh b/src/kyclogic/taler-exchange-kyc-kycaid-converter.sh @@ -29,7 +29,7 @@ J=$(jq '{"type":.type,"email":.email,"phone":.phone,"first_name":.first_name,"na # TODO: # log_failure (json_object_get (j, "decline_reasons")); -TYPE=$(echo "$J" | jq -r '.person') +TYPE=$(echo "$J" | jq -r '.type') N=0 DOCS_RAW="" @@ -39,7 +39,7 @@ do TYPE=$(jq -r ".documents[]|select(.id==\"$ID\")|.type") EXPIRY=$(jq -r ".documents[]|select(.id==\"$ID\")|.expiry_date") DOCUMENT_FILE=$(mktemp -t tmp.XXXXXXXXXX) - # Authoriazation: Token $TOKEN + # Authorization: Token $TOKEN DOCUMENT_URL="https://api.kycaid.com/documents/$ID" if [ -z "${TOKEN:-}" ] then @@ -61,7 +61,7 @@ do done -if [ "person" = "${TYPE}" ] +if [ "PERSON" = "${TYPE}" ] then # Next, combine some fields into larger values. @@ -70,16 +70,21 @@ then # CITY=$(echo $J | jq -r '[.postcode,.city,."address-subdivision,.cc"]|join(" ")') # Combine into final result for individual. - # FIXME: does jq tolerate 'pep = NULL' here? - echo "$J" | jq \ - --arg full_name "${FULLNAME}" \ - '{$full_name,"birthdate":.dob,"pep":.pep,"phone":."phone","email",.email,"residences":.residence_country}' + echo "$J" \ + | jq \ + --arg full_name "${FULLNAME}" \ + '{$full_name,"birthdate":.dob,"pep":.pep,"phone":.phone,"email":.email,"residences":.residence_country}' \ + | jq \ + 'del(..|select(.==null))' else # Combine into final result for business. - echo "$J" | jq \ - $DOCS_RAW \ - "{\"company_name\":.company_name,\"phone\":.phone,\"email\":.email,\"registration_country\":.registration_country,\"documents\":[${DOCS_JSON}]}" + echo "$J" \ + | jq \ + $DOCS_RAW \ + "{\"company_name\":.company_name,\"phone\":.phone,\"email\":.email,\"registration_country\":.registration_country,\"documents\":[${DOCS_JSON}]}" \ + | jq \ + 'del(..|select(.==null))' fi exit 0 diff --git a/src/kyclogic/taler-exchange-kyc-oauth2-nda.sh b/src/kyclogic/taler-exchange-kyc-oauth2-nda.sh @@ -20,8 +20,11 @@ fi # Next, combine some fields into larger values. FULLNAME=$(echo "$J" | jq -r '[.first_name,.last_name]|join(" ")') -echo "$J" | jq \ - --arg full_name "${FULLNAME}" \ - '{$full_name,"phone":.phone,"id":.id}' +echo "$J" \ + | jq \ + --arg full_name "${FULLNAME}" \ + '{$full_name,"phone":.phone,"id":.id}' \ + | jq \ + 'del(..|select(.==null))' exit 0 diff --git a/src/kyclogic/taler-exchange-kyc-oauth2-test-converter.sh b/src/kyclogic/taler-exchange-kyc-oauth2-test-converter.sh @@ -17,13 +17,15 @@ J=$(jq '{"id":.data.id,"first":.data.first_name,"last":.data.last_name,"birthdat STATUS=$(echo "$J" | jq -r '.status') if [ "$STATUS" != "success" ] then - return 1 + exit 1 fi FULLNAME=$(echo "$J" | jq -r '[.first,.last]|join(" ")') -echo $J | jq \ - --arg full_name "${FULLNAME}" \ - '{$full_name,"birthdate":.birthdate,"id":.id}' - +echo $J \ + | jq \ + --arg full_name "${FULLNAME}" \ + '{$full_name,"birthdate":.birthdate,"id":.id}' \ + | jq \ + 'del(..|select(.==null))' exit 0 diff --git a/src/kyclogic/taler-exchange-kyc-persona-converter.sh b/src/kyclogic/taler-exchange-kyc-persona-converter.sh @@ -44,11 +44,14 @@ else fi # Combine into final result. -echo "$J" | jq \ - --arg full_name "${FULLNAME}" \ - --arg street "${STREET}" \ - --arg city "${CITY}" \ - --rawfile photo "${PHOTO_FILE}" \ - '{$full_name,$street,$city,"birthdate":.birthdate,"residences":.cc,"identification_number":."identification-number",$photo}' +echo "$J" \ + | jq \ + --arg full_name "${FULLNAME}" \ + --arg street "${STREET}" \ + --arg city "${CITY}" \ + --rawfile photo "${PHOTO_FILE}" \ + '{$full_name,$street,$city,"birthdate":.birthdate,"residences":.cc,"identification_number":."identification-number",$photo}' \ + | jq \ + 'del(..|select(.==null))' exit 0 diff --git a/src/lib/exchange_api_coins_history.c b/src/lib/exchange_api_coins_history.c @@ -802,11 +802,10 @@ TALER_EXCHANGE_parse_coin_history ( json_t *transaction = json_array_get (history, off); enum GNUNET_GenericReturnValue add; - struct TALER_Amount amount; const char *type; struct GNUNET_JSON_Specification spec_glob[] = { TALER_JSON_spec_amount_any ("amount", - &amount), + &rh->amount), GNUNET_JSON_spec_string ("type", &type), GNUNET_JSON_spec_end () @@ -821,7 +820,7 @@ TALER_EXCHANGE_parse_coin_history ( return GNUNET_SYSERR; } if (GNUNET_YES != - TALER_amount_cmp_currency (&amount, + TALER_amount_cmp_currency (&rh->amount, total_in)) { GNUNET_break_op (0); @@ -830,7 +829,7 @@ TALER_EXCHANGE_parse_coin_history ( GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Operation of type %s with amount %s\n", type, - TALER_amount2s (&amount)); + TALER_amount2s (&rh->amount)); add = GNUNET_SYSERR; for (unsigned int i = 0; NULL != map[i].type; i++) { @@ -840,7 +839,7 @@ TALER_EXCHANGE_parse_coin_history ( rh->type = map[i].ctt; add = map[i].helper (&pc, rh, - &amount, + &rh->amount, transaction); break; } @@ -860,7 +859,7 @@ TALER_EXCHANGE_parse_coin_history ( if (0 > TALER_amount_add (total_out, total_out, - &amount)) + &rh->amount)) { /* overflow in history already!? inconceivable! Bad exchange! */ GNUNET_break_op (0); @@ -872,7 +871,7 @@ TALER_EXCHANGE_parse_coin_history ( if (0 > TALER_amount_add (total_in, total_in, - &amount)) + &rh->amount)) { /* overflow in refund history? inconceivable! Bad exchange! */ GNUNET_break_op (0); diff --git a/src/pq/pq_result_helper.c b/src/pq/pq_result_helper.c @@ -1141,13 +1141,13 @@ extract_array_generic ( *((void **) dst) = NULL; #define FAIL_IF(cond) \ - do { \ - if ((cond)) \ - { \ - GNUNET_break (! (cond)); \ - goto FAIL; \ - } \ - } while (0) + do { \ + if ((cond)) \ + { \ + GNUNET_break (! (cond)); \ + goto FAIL; \ + } \ + } while (0) col_num = PQfnumber (result, fname); FAIL_IF (0 > col_num); @@ -1365,12 +1365,19 @@ array_cleanup (void *cls, struct ArrayResultCls *info = cls; void **dst = rd; - /* FIXME-Oec: this does not properly clean up - denomination signatures! */ if ((0 == info->same_size) && (NULL != info->sizes)) GNUNET_free (*(info->sizes)); + /* Clean up signatures, if applicable */ + if (TALER_PQ_array_of_blinded_denom_sig == info->typ) + { + struct TALER_BlindedDenominationSignature *denom_sigs = *dst; + GNUNET_assert (NULL != info->num); + for (size_t i = 0; i < *info->num; i++) + GNUNET_free (denom_sigs[i].blinded_sig); + } + GNUNET_free (cls); GNUNET_free (*dst); *dst = NULL; diff --git a/src/testing/test-taler-exchange-aggregator-postgres.conf b/src/testing/test-taler-exchange-aggregator-postgres.conf @@ -49,6 +49,12 @@ WIRE_GATEWAY_AUTH_METHOD = basic USERNAME = Exchange PASSWORD = x +[admin-accountcredentials-1] +WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/" +WIRE_GATEWAY_AUTH_METHOD = basic +USERNAME = Exchange +PASSWORD = x + [bank] HTTP_PORT = 8082 diff --git a/src/testing/test-taler-exchange-wirewatch-postgres.conf b/src/testing/test-taler-exchange-wirewatch-postgres.conf @@ -48,6 +48,12 @@ WIRE_GATEWAY_AUTH_METHOD = basic USERNAME = Exchange PASSWORD = x +[admin-accountcredentials-1] +WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/" +WIRE_GATEWAY_AUTH_METHOD = basic +USERNAME = Exchange +PASSWORD = x + [bank] HTTP_PORT = 8082 diff --git a/src/testing/test_bank_api.c b/src/testing/test_bank_api.c @@ -98,7 +98,7 @@ run (void *cls, 1), TALER_TESTING_cmd_admin_add_incoming ("credit-1", "EUR:5.01", - &cred.ba, + &cred.ba_admin, cred.user42_payto), /** * This CMD doesn't care about the HTTP response code; that's @@ -108,7 +108,7 @@ run (void *cls, */ TALER_TESTING_cmd_admin_add_incoming_with_ref ("credit-1-fail", "EUR:2.01", - &cred.ba, + &cred.ba_admin, cred.user42_payto, "credit-1", -1), @@ -126,7 +126,7 @@ run (void *cls, 5), TALER_TESTING_cmd_admin_add_incoming ("credit-2", "EUR:3.21", - &cred.ba, + &cred.ba_admin, cred.user42_payto), TALER_TESTING_cmd_transfer ("debit-1", "EUR:3.22", diff --git a/src/testing/test_bank_api_fakebank.conf b/src/testing/test_bank_api_fakebank.conf @@ -12,3 +12,10 @@ WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/" WIRE_GATEWAY_AUTH_METHOD = basic USERNAME = Exchange PASSWORD = x + +[admin-accountcredentials-2] +WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/" +WIRE_GATEWAY_AUTH_METHOD = basic +# For now, fakebank still checks against the Exchange account... +USERNAME = Exchange +PASSWORD = x diff --git a/src/testing/test_bank_api_nexus.conf b/src/testing/test_bank_api_nexus.conf @@ -10,6 +10,13 @@ WIRE_GATEWAY_AUTH_METHOD = basic USERNAME = exchange PASSWORD = x +[admin-accountcredentials-2] +WIRE_GATEWAY_URL = http://localhost:8082/accounts/exchange/taler-wire-gateway/ +WIRE_GATEWAY_AUTH_METHOD = basic +USERNAME = admin +# 'secret' is from taler-unified-setup.sh +PASSWORD = secret + [libeufin-bankdb-postgres] CONFIG="postgresql:///talercheck" diff --git a/src/testing/test_exchange_api.conf b/src/testing/test_exchange_api.conf @@ -68,6 +68,10 @@ ENABLE_CREDIT = YES WIRE_GATEWAY_AUTH_METHOD = none WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/" +[admin-accountcredentials-1] +WIRE_GATEWAY_AUTH_METHOD = none +WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/" + [exchange-account-2] PAYTO_URI = "payto://x-taler-bank/localhost/2?receiver-name=2" ENABLE_DEBIT = YES @@ -79,6 +83,13 @@ USERNAME = Exchange PASSWORD = x WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/" +[admin-accountcredentials-2] +WIRE_GATEWAY_AUTH_METHOD = basic +# For now, fakebank still checks against the Exchange account... +USERNAME = Exchange +PASSWORD = x +WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/" + [kyc-provider-test-oauth2] COST = 0 diff --git a/src/testing/test_exchange_api_age_restriction.conf b/src/testing/test_exchange_api_age_restriction.conf @@ -54,6 +54,10 @@ ENABLE_CREDIT = YES 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 @@ -65,6 +69,12 @@ 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/" + [kyc-provider-test-oauth2] COST = 0 diff --git a/src/testing/test_exchange_api_keys_cherry_picking.conf b/src/testing/test_exchange_api_keys_cherry_picking.conf @@ -34,6 +34,9 @@ ENABLE_CREDIT = YES [exchange-accountcredentials-1] WIRE_GATEWAY_URL = "http://localhost:9082/accounts/42/taler-wire-gateway/" +[admin-accountcredentials-1] +WIRE_GATEWAY_URL = "http://localhost:9082/accounts/42/taler-wire-gateway/" + [exchange-account-2] PAYTO_URI = "payto://x-taler-bank/localhost/2?receiver-name=2" ENABLE_DEBIT = YES @@ -45,5 +48,11 @@ WIRE_GATEWAY_AUTH_METHOD = basic USERNAME = Exchange PASSWORD = x +[admin-accountcredentials-2] +WIRE_GATEWAY_URL = "http://localhost:9082/accounts/2/taler-wire-gateway/" +WIRE_GATEWAY_AUTH_METHOD = basic +USERNAME = Exchange +PASSWORD = x + [bank] HTTP_PORT=8082 diff --git a/src/testing/testing_api_cmd_batch_deposit.c b/src/testing/testing_api_cmd_batch_deposit.c @@ -70,11 +70,22 @@ struct Coin char *coin_reference; /** + * Denomination public key of the coin. + */ + const struct TALER_EXCHANGE_DenomPublicKey *denom_pub; + + /** * The command being referenced. */ const struct TALER_TESTING_Command *coin_cmd; /** + * Expected entry in the coin history created by this + * coin. + */ + struct TALER_EXCHANGE_CoinHistoryEntry che; + + /** * Index of the coin at @e coin_cmd. */ unsigned int coin_idx; @@ -231,7 +242,6 @@ batch_deposit_run (void *cls, struct TALER_TESTING_Interpreter *is) { struct BatchDepositState *ds = cls; - const struct TALER_EXCHANGE_DenomPublicKey *denom_pub; const struct TALER_DenominationSignature *denom_pub_sig; struct TALER_MerchantPublicKeyP merchant_pub; struct TALER_PrivateContractHashP h_contract_terms; @@ -335,7 +345,7 @@ batch_deposit_run (void *cls, (GNUNET_OK != TALER_TESTING_get_trait_denom_pub (coin->coin_cmd, coin->coin_idx, - &denom_pub)) || + &coin->denom_pub)) || (GNUNET_OK != TALER_TESTING_get_trait_denom_sig (coin->coin_cmd, coin->coin_idx, @@ -350,25 +360,38 @@ batch_deposit_run (void *cls, TALER_age_commitment_hash (&age_commitment_proof->commitment, &cdd->h_age_commitment); } - coin->deposit_fee = denom_pub->fees.deposit; + coin->deposit_fee = coin->denom_pub->fees.deposit; GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, &cdd->coin_pub.eddsa_pub); cdd->denom_sig = *denom_pub_sig; - cdd->h_denom_pub = denom_pub->h_key; + cdd->h_denom_pub = coin->denom_pub->h_key; TALER_wallet_deposit_sign (&coin->amount, - &denom_pub->fees.deposit, + &coin->denom_pub->fees.deposit, &h_wire, &h_contract_terms, NULL, /* wallet_data_hash */ &cdd->h_age_commitment, NULL, /* hash of extensions */ - &denom_pub->h_key, + &coin->denom_pub->h_key, ds->wallet_timestamp, &merchant_pub, ds->refund_deadline, coin_priv, &cdd->coin_sig); coin->coin_sig = cdd->coin_sig; + coin->che.type = TALER_EXCHANGE_CTT_DEPOSIT; + coin->che.amount = coin->amount; + coin->che.details.deposit.h_wire = h_wire; + coin->che.details.deposit.h_contract_terms = h_contract_terms; + coin->che.details.deposit.no_h_policy = true; + coin->che.details.deposit.no_wallet_data_hash = true; + coin->che.details.deposit.wallet_timestamp = ds->wallet_timestamp; + coin->che.details.deposit.merchant_pub = merchant_pub; + coin->che.details.deposit.refund_deadline = ds->refund_deadline; + coin->che.details.deposit.sig = cdd->coin_sig; + coin->che.details.deposit.no_hac = GNUNET_is_zero (&cdd->h_age_commitment); + coin->che.details.deposit.hac = cdd->h_age_commitment; + coin->che.details.deposit.deposit_fee = coin->denom_pub->fees.deposit; } GNUNET_assert (NULL == ds->dh); @@ -457,9 +480,10 @@ batch_deposit_traits (void *cls, unsigned int index) { struct BatchDepositState *ds = cls; - struct Coin *coin = &ds->coins[index]; + const struct Coin *coin = &ds->coins[index]; /* Will point to coin cmd internals. */ const struct TALER_CoinSpendPrivateKeyP *coin_spent_priv; + struct TALER_CoinSpendPublicKeyP coin_spent_pub; const struct TALER_AgeCommitmentProof *age_commitment_proof; if (index >= ds->num_coins) @@ -486,6 +510,10 @@ batch_deposit_traits (void *cls, TALER_TESTING_interpreter_fail (ds->is); return GNUNET_NO; } + + GNUNET_CRYPTO_eddsa_key_get_public (&coin_spent_priv->eddsa_priv, + &coin_spent_pub.eddsa_pub); + { struct TALER_TESTING_Trait traits[] = { /* First two traits are only available if @@ -500,6 +528,12 @@ batch_deposit_traits (void *cls, TALER_TESTING_make_trait_merchant_priv (&ds->merchant_priv), TALER_TESTING_make_trait_age_commitment_proof (index, age_commitment_proof), + TALER_TESTING_make_trait_coin_history (index, + &coin->che), + TALER_TESTING_make_trait_coin_pub (index, + &coin_spent_pub), + TALER_TESTING_make_trait_denom_pub (index, + coin->denom_pub), TALER_TESTING_make_trait_coin_priv (index, coin_spent_priv), TALER_TESTING_make_trait_coin_sig (index, @@ -508,7 +542,6 @@ batch_deposit_traits (void *cls, &coin->amount), TALER_TESTING_make_trait_deposit_fee_amount (index, &coin->deposit_fee), - TALER_TESTING_make_trait_timestamp (index, &ds->exchange_timestamp), TALER_TESTING_make_trait_wire_deadline (index, diff --git a/src/testing/testing_api_cmd_batch_withdraw.c b/src/testing/testing_api_cmd_batch_withdraw.c @@ -57,6 +57,11 @@ struct CoinState struct TALER_CoinSpendPrivateKeyP coin_priv; /** + * Public key of the coin. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + + /** * Blinding key used during the operation. */ union GNUNET_CRYPTO_BlindingSecretP bks; @@ -207,6 +212,9 @@ reserve_batch_withdraw_cb (void *cls, TALER_denom_sig_deep_copy (&cs->sig, &pcd->sig); cs->coin_priv = pcd->coin_priv; + GNUNET_CRYPTO_eddsa_key_get_public (&cs->coin_priv.eddsa_priv, + &cs->coin_pub.eddsa_pub); + cs->bks = pcd->bks; cs->exchange_vals = pcd->exchange_vals; } @@ -402,6 +410,8 @@ batch_withdraw_traits (void *cls, &cs->reserve_history), TALER_TESTING_make_trait_coin_priv (index, &cs->coin_priv), + TALER_TESTING_make_trait_coin_pub (index, + &cs->coin_pub), TALER_TESTING_make_trait_planchet_secrets (index, &cs->ps), TALER_TESTING_make_trait_blinding_key (index, diff --git a/src/testing/testing_api_cmd_coin_history.c b/src/testing/testing_api_cmd_coin_history.c @@ -104,7 +104,116 @@ struct AnalysisContext }; -#if 0 +/** + * Compare @a h1 and @a h2. + * + * @param h1 a history entry + * @param h2 a history entry + * @return 0 if @a h1 and @a h2 are equal + */ +static int +history_entry_cmp ( + const struct TALER_EXCHANGE_CoinHistoryEntry *h1, + const struct TALER_EXCHANGE_CoinHistoryEntry *h2) +{ + if (h1->type != h2->type) + return 1; + if (0 != TALER_amount_cmp (&h1->amount, + &h2->amount)) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Amount mismatch (%s)\n", + TALER_amount2s (&h1->amount)); + return 1; + } + switch (h1->type) + { + case TALER_EXCHANGE_CTT_NONE: + GNUNET_break (0); + break; + case TALER_EXCHANGE_CTT_DEPOSIT: + if (0 != GNUNET_memcmp (&h1->details.deposit.h_contract_terms, + &h2->details.deposit.h_contract_terms)) + return 1; + if (0 != GNUNET_memcmp (&h1->details.deposit.merchant_pub, + &h2->details.deposit.merchant_pub)) + return 1; + if (0 != GNUNET_memcmp (&h1->details.deposit.h_wire, + &h2->details.deposit.h_wire)) + return 1; + if (0 != GNUNET_memcmp (&h1->details.deposit.sig, + &h2->details.deposit.sig)) + return 1; + return 0; + case TALER_EXCHANGE_CTT_MELT: + if (0 != GNUNET_memcmp (&h1->details.melt.h_age_commitment, + &h2->details.melt.h_age_commitment)) + return 1; + /* Note: most other fields are not initialized + in the trait as they are hard to extract from + the API */ + return 0; + case TALER_EXCHANGE_CTT_REFUND: + if (0 != GNUNET_memcmp (&h1->details.refund.sig, + &h2->details.refund.sig)) + return 1; + return 0; + case TALER_EXCHANGE_CTT_RECOUP: + if (0 != GNUNET_memcmp (&h1->details.recoup.coin_sig, + &h2->details.recoup.coin_sig)) + return 1; + /* Note: exchange_sig, exchange_pub and timestamp are + fundamentally not available in the initiating command */ + return 0; + case TALER_EXCHANGE_CTT_RECOUP_REFRESH: + if (0 != GNUNET_memcmp (&h1->details.recoup_refresh.coin_sig, + &h2->details.recoup_refresh.coin_sig)) + return 1; + /* Note: exchange_sig, exchange_pub and timestamp are + fundamentally not available in the initiating command */ + return 0; + case TALER_EXCHANGE_CTT_OLD_COIN_RECOUP: + if (0 != GNUNET_memcmp (&h1->details.old_coin_recoup.new_coin_pub, + &h2->details.old_coin_recoup.new_coin_pub)) + return 1; + /* Note: exchange_sig, exchange_pub and timestamp are + fundamentally not available in the initiating command */ + return 0; + case TALER_EXCHANGE_CTT_PURSE_DEPOSIT: + /* coin_sig is not initialized */ + if (0 != GNUNET_memcmp (&h1->details.purse_deposit.purse_pub, + &h2->details.purse_deposit.purse_pub)) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Purse public key mismatch\n"); + return 1; + } + if (0 != strcmp (h1->details.purse_deposit.exchange_base_url, + h2->details.purse_deposit.exchange_base_url)) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Exchange base URL mismatch (%s/%s)\n", + h1->details.purse_deposit.exchange_base_url, + h2->details.purse_deposit.exchange_base_url); + GNUNET_break (0); + return 1; + } + return 0; + case TALER_EXCHANGE_CTT_PURSE_REFUND: + /* NOTE: not supported yet (trait not returned) */ + return 0; + case TALER_EXCHANGE_CTT_RESERVE_OPEN_DEPOSIT: + /* NOTE: not supported yet (trait not returned) */ + if (0 != GNUNET_memcmp (&h1->details.reserve_open_deposit.coin_sig, + &h2->details.reserve_open_deposit.coin_sig)) + return 1; + return 0; + } + GNUNET_assert (0); + return -1; +} + + /** * Check if @a cmd changed the coin, if so, find the * entry in our history and set the respective index in found @@ -156,64 +265,71 @@ analyze_command (void *cls, return; } + for (unsigned int j = 0; true; j++) { - const struct TALER_CoinPublicKeyP *rp; + const struct TALER_CoinSpendPublicKeyP *rp; + const struct TALER_EXCHANGE_CoinHistoryEntry *he; + bool matched = false; if (GNUNET_OK != TALER_TESTING_get_trait_coin_pub (cmd, + j, &rp)) - return; /* command does nothing for coins */ + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Command `%s#%u' has no public key for a coin\n", + cmd->label, + j); + break; /* command does nothing for coins */ + } if (0 != GNUNET_memcmp (rp, coin_pub)) - return; /* command affects some _other_ coin */ - for (unsigned int j = 0; true; j++) { - const struct TALER_EXCHANGE_CoinHistoryEntry *he; - bool matched = false; - - if (GNUNET_OK != - TALER_TESTING_get_trait_coin_history (cmd, - j, - &he)) - { - /* NOTE: only for debugging... */ - if (0 == j) - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Command `%s' has the coin_pub, but lacks coin history trait\n", - cmd->label); - return; /* command does nothing for coins */ - } - for (unsigned int i = 0; i<history_length; i++) + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Command `%s#%u' is about another coin\n", + cmd->label, + j); + continue; /* command affects some _other_ coin */ + } + if (GNUNET_OK != + TALER_TESTING_get_trait_coin_history (cmd, + j, + &he)) + { + /* NOTE: only for debugging... */ + if (0 == j) + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Command `%s' has the coin_pub, but lacks coin history trait\n", + cmd->label); + return; /* command does nothing for coins */ + } + for (unsigned int i = 0; i<history_length; i++) + { + if (found[i]) + continue; /* already found, skip */ + if (0 == + history_entry_cmp (he, + &history[i])) { - if (found[i]) - continue; /* already found, skip */ - if (0 == - TALER_TESTING_coin_history_entry_cmp (he, - &history[i])) - { - found[i] = true; - matched = true; - break; - } - } - if (! matched) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Command `%s' coin history entry #%u not found\n", - cmd->label, - j); - ac->failure = true; - return; + found[i] = true; + matched = true; + break; } } + if (! matched) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Command `%s' coin history entry #%u not found\n", + cmd->label, + j); + ac->failure = true; + return; + } } } -#endif - - /** * Check that the coin balance and HTTP response code are * both acceptable. @@ -325,7 +441,6 @@ coin_history_cb (void *cls, return; } (void) ac; -#if FIXME TALER_TESTING_iterate (is, true, &analyze_command, @@ -338,7 +453,8 @@ coin_history_cb (void *cls, TALER_TESTING_interpreter_fail (ss->is); return; } - for (unsigned int i = 0; i<rs->details.ok.history_len; i++) +#if 1 + for (unsigned int i = 0; i<hlen; i++) { if (found[i]) continue; diff --git a/src/testing/testing_api_cmd_common.c b/src/testing/testing_api_cmd_common.c @@ -25,155 +25,6 @@ #include "taler_testing_lib.h" -int -TALER_TESTING_history_entry_cmp ( - const struct TALER_EXCHANGE_ReserveHistoryEntry *h1, - const struct TALER_EXCHANGE_ReserveHistoryEntry *h2) -{ - if (h1->type != h2->type) - return 1; - switch (h1->type) - { - case TALER_EXCHANGE_RTT_CREDIT: - if ( (0 == - TALER_amount_cmp (&h1->amount, - &h2->amount)) && - (0 == strcasecmp (h1->details.in_details.sender_url, - h2->details.in_details.sender_url)) && - (h1->details.in_details.wire_reference == - h2->details.in_details.wire_reference) && - (GNUNET_TIME_timestamp_cmp (h1->details.in_details.timestamp, - ==, - h2->details.in_details.timestamp)) ) - return 0; - return 1; - case TALER_EXCHANGE_RTT_WITHDRAWAL: - if ( (0 == - TALER_amount_cmp (&h1->amount, - &h2->amount)) && - (0 == - TALER_amount_cmp (&h1->details.withdraw.fee, - &h2->details.withdraw.fee)) ) - /* testing_api_cmd_withdraw doesn't set the out_authorization_sig, - so we cannot test for it here. but if the amount matches, - that should be good enough. */ - return 0; - return 1; - case TALER_EXCHANGE_RTT_AGEWITHDRAWAL: - /* testing_api_cmd_age_withdraw doesn't set the out_authorization_sig, - so we cannot test for it here. but if the amount matches, - that should be good enough. */ - if ( (0 == - TALER_amount_cmp (&h1->amount, - &h2->amount)) && - (0 == - TALER_amount_cmp (&h1->details.age_withdraw.fee, - &h2->details.age_withdraw.fee)) && - (h1->details.age_withdraw.max_age == - h2->details.age_withdraw.max_age)) - return 0; - return 1; - case TALER_EXCHANGE_RTT_RECOUP: - /* exchange_sig, exchange_pub and timestamp are NOT available - from the original recoup response, hence here NOT check(able/ed) */ - if ( (0 == - TALER_amount_cmp (&h1->amount, - &h2->amount)) && - (0 == - GNUNET_memcmp (&h1->details.recoup_details.coin_pub, - &h2->details.recoup_details.coin_pub)) ) - return 0; - return 1; - case TALER_EXCHANGE_RTT_CLOSING: - /* testing_api_cmd_exec_closer doesn't set the - receiver_account_details, exchange_sig, exchange_pub or wtid or timestamp - so we cannot test for it here. but if the amount matches, - that should be good enough. */ - if ( (0 == - TALER_amount_cmp (&h1->amount, - &h2->amount)) && - (0 == - TALER_amount_cmp (&h1->details.close_details.fee, - &h2->details.close_details.fee)) ) - return 0; - return 1; - case TALER_EXCHANGE_RTT_MERGE: - if ( (0 == - TALER_amount_cmp (&h1->amount, - &h2->amount)) && - (0 == - TALER_amount_cmp (&h1->details.merge_details.purse_fee, - &h2->details.merge_details.purse_fee)) && - (GNUNET_TIME_timestamp_cmp (h1->details.merge_details.merge_timestamp, - ==, - h2->details.merge_details.merge_timestamp)) - && - (GNUNET_TIME_timestamp_cmp (h1->details.merge_details.purse_expiration, - ==, - h2->details.merge_details.purse_expiration)) - && - (0 == - GNUNET_memcmp (&h1->details.merge_details.merge_pub, - &h2->details.merge_details.merge_pub)) && - (0 == - GNUNET_memcmp (&h1->details.merge_details.h_contract_terms, - &h2->details.merge_details.h_contract_terms)) && - (0 == - GNUNET_memcmp (&h1->details.merge_details.purse_pub, - &h2->details.merge_details.purse_pub)) && - (0 == - GNUNET_memcmp (&h1->details.merge_details.reserve_sig, - &h2->details.merge_details.reserve_sig)) && - (h1->details.merge_details.min_age == - h2->details.merge_details.min_age) && - (h1->details.merge_details.flags == - h2->details.merge_details.flags) ) - return 0; - return 1; - case TALER_EXCHANGE_RTT_OPEN: - if ( (0 == - TALER_amount_cmp (&h1->amount, - &h2->amount)) && - (GNUNET_TIME_timestamp_cmp ( - h1->details.open_request.request_timestamp, - ==, - h2->details.open_request.request_timestamp)) && - (GNUNET_TIME_timestamp_cmp ( - h1->details.open_request.reserve_expiration, - ==, - h2->details.open_request.reserve_expiration)) && - (h1->details.open_request.purse_limit == - h2->details.open_request.purse_limit) && - (0 == - TALER_amount_cmp (&h1->details.open_request.reserve_payment, - &h2->details.open_request.reserve_payment)) && - (0 == - GNUNET_memcmp (&h1->details.open_request.reserve_sig, - &h2->details.open_request.reserve_sig)) ) - return 0; - return 1; - case TALER_EXCHANGE_RTT_CLOSE: - if ( (0 == - TALER_amount_cmp (&h1->amount, - &h2->amount)) && - (GNUNET_TIME_timestamp_cmp ( - h1->details.close_request.request_timestamp, - ==, - h2->details.close_request.request_timestamp)) && - (0 == - GNUNET_memcmp (&h1->details.close_request.target_account_h_payto, - &h2->details.close_request.target_account_h_payto)) && - (0 == - GNUNET_memcmp (&h1->details.close_request.reserve_sig, - &h2->details.close_request.reserve_sig)) ) - return 0; - return 1; - } - GNUNET_assert (0); - return 1; -} - - enum GNUNET_GenericReturnValue TALER_TESTING_parse_coin_reference ( const char *coin_reference, diff --git a/src/testing/testing_api_cmd_deposit.c b/src/testing/testing_api_cmd_deposit.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2018-2021 Taler Systems SA + Copyright (C) 2018-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 @@ -106,6 +106,11 @@ struct DepositState struct TALER_EXCHANGE_BatchDepositHandle *dh; /** + * Denomination public key of the deposited coin. + */ + const struct TALER_EXCHANGE_DenomPublicKey *denom_pub; + + /** * Timestamp of the /deposit operation in the wallet (contract signing time). */ struct GNUNET_TIME_Timestamp wallet_timestamp; @@ -136,10 +141,16 @@ struct DepositState unsigned int do_retry; /** - * Set to #GNUNET_YES if the /deposit succeeded + * Set to true if the /deposit succeeded * and we now can provide the resulting traits. */ - int deposit_succeeded; + bool deposit_succeeded; + + /** + * Expected entry in the coin history created by this + * operation. + */ + struct TALER_EXCHANGE_CoinHistoryEntry che; /** * When did the exchange receive the deposit? @@ -170,7 +181,7 @@ struct DepositState * When we're referencing another deposit operation, * this will only be set after the command has been started. */ - int command_initialized; + bool command_initialized; /** * Reference to fetch the merchant private key from. @@ -263,7 +274,7 @@ deposit_cb (void *cls, } if (MHD_HTTP_OK == dr->hr.http_status) { - ds->deposit_succeeded = GNUNET_YES; + ds->deposit_succeeded = true; ds->exchange_timestamp = dr->details.ok.deposit_timestamp; ds->exchange_pub = *dr->details.ok.exchange_pub; ds->exchange_sig = *dr->details.ok.exchange_sig; @@ -289,7 +300,6 @@ deposit_run (void *cls, const struct TALER_CoinSpendPrivateKeyP *coin_priv; struct TALER_CoinSpendPublicKeyP coin_pub; const struct TALER_AgeCommitmentHash *phac; - const struct TALER_EXCHANGE_DenomPublicKey *denom_pub; const struct TALER_DenominationSignature *denom_pub_sig; struct TALER_MerchantPublicKeyP merchant_pub; struct TALER_PrivateContractHashP h_contract_terms; @@ -354,7 +364,7 @@ deposit_run (void *cls, ds->wire_deadline = ods->wire_deadline; ds->amount = ods->amount; ds->merchant_priv = ods->merchant_priv; - ds->command_initialized = GNUNET_YES; + ds->command_initialized = true; } else if (NULL != ds->merchant_priv_reference) { @@ -414,7 +424,7 @@ deposit_run (void *cls, (GNUNET_OK != TALER_TESTING_get_trait_denom_pub (coin_cmd, ds->coin_index, - &denom_pub)) || + &ds->denom_pub)) || (GNUNET_OK != TALER_TESTING_get_trait_denom_sig (coin_cmd, ds->coin_index, @@ -428,7 +438,7 @@ deposit_run (void *cls, return; } - ds->deposit_fee = denom_pub->fees.deposit; + ds->deposit_fee = ds->denom_pub->fees.deposit; GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, &coin_pub.eddsa_pub); @@ -441,18 +451,30 @@ deposit_run (void *cls, TALER_JSON_merchant_wire_signature_hash (ds->wire_details, &h_wire)); TALER_wallet_deposit_sign (&ds->amount, - &denom_pub->fees.deposit, + &ds->denom_pub->fees.deposit, &h_wire, &h_contract_terms, NULL, /* wallet data hash */ phac, NULL, /* hash of extensions */ - &denom_pub->h_key, + &ds->denom_pub->h_key, ds->wallet_timestamp, &merchant_pub, ds->refund_deadline, coin_priv, &ds->coin_sig); + ds->che.type = TALER_EXCHANGE_CTT_DEPOSIT; + ds->che.amount = ds->amount; + ds->che.details.deposit.h_wire = h_wire; + ds->che.details.deposit.h_contract_terms = h_contract_terms; + ds->che.details.deposit.no_h_policy = true; + ds->che.details.deposit.no_wallet_data_hash = true; + ds->che.details.deposit.wallet_timestamp = ds->wallet_timestamp; + ds->che.details.deposit.merchant_pub = merchant_pub; + ds->che.details.deposit.refund_deadline = ds->refund_deadline; + ds->che.details.deposit.sig = ds->coin_sig; + ds->che.details.deposit.no_hac = true; + ds->che.details.deposit.deposit_fee = ds->denom_pub->fees.deposit; } GNUNET_assert (NULL == ds->dh); { @@ -461,7 +483,7 @@ deposit_run (void *cls, .coin_pub = coin_pub, .coin_sig = ds->coin_sig, .denom_sig = *denom_pub_sig, - .h_denom_pub = denom_pub->h_key, + .h_denom_pub = ds->denom_pub->h_key, .h_age_commitment = {{{0}}}, }; struct TALER_EXCHANGE_DepositContractDetail dcd = { @@ -550,10 +572,11 @@ deposit_traits (void *cls, const struct TALER_TESTING_Command *coin_cmd; /* Will point to coin cmd internals. */ const struct TALER_CoinSpendPrivateKeyP *coin_spent_priv; + struct TALER_CoinSpendPublicKeyP coin_spent_pub; const struct TALER_AgeCommitmentProof *age_commitment_proof; const struct TALER_AgeCommitmentHash *h_age_commitment; - if (GNUNET_YES != ds->command_initialized) + if (! ds->command_initialized) { /* No access to traits yet. */ GNUNET_break (0); @@ -587,17 +610,26 @@ deposit_traits (void *cls, return GNUNET_NO; } + GNUNET_CRYPTO_eddsa_key_get_public (&coin_spent_priv->eddsa_priv, + &coin_spent_pub.eddsa_pub); + { struct TALER_TESTING_Trait traits[] = { /* First two traits are only available if - ds->traits is #GNUNET_YES */ + ds->traits is true */ TALER_TESTING_make_trait_exchange_pub (0, &ds->exchange_pub), TALER_TESTING_make_trait_exchange_sig (0, &ds->exchange_sig), /* These traits are always available */ + TALER_TESTING_make_trait_coin_history (0, + &ds->che), TALER_TESTING_make_trait_coin_priv (0, coin_spent_priv), + TALER_TESTING_make_trait_coin_pub (0, + &coin_spent_pub), + TALER_TESTING_make_trait_denom_pub (0, + ds->denom_pub), TALER_TESTING_make_trait_coin_sig (0, &ds->coin_sig), TALER_TESTING_make_trait_age_commitment_proof (0, @@ -679,7 +711,7 @@ TALER_TESTING_cmd_deposit ( TALER_string_to_amount (amount, &ds->amount)); ds->expected_response_code = expected_response_code; - ds->command_initialized = GNUNET_YES; + ds->command_initialized = true; { struct TALER_TESTING_Command cmd = { .cls = ds, @@ -744,7 +776,7 @@ TALER_TESTING_cmd_deposit_with_ref ( TALER_string_to_amount (amount, &ds->amount)); ds->expected_response_code = expected_response_code; - ds->command_initialized = GNUNET_YES; + ds->command_initialized = true; { struct TALER_TESTING_Command cmd = { .cls = ds, diff --git a/src/testing/testing_api_cmd_purse_create_deposit.c b/src/testing/testing_api_cmd_purse_create_deposit.c @@ -44,10 +44,20 @@ struct Coin unsigned int coin_index; /** + * Public key of the deposited coin. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + + /** * Amount to deposit (with fee). */ struct TALER_Amount deposit_with_fee; + /** + * Entry in the coin's history generated by this operation. + */ + struct TALER_EXCHANGE_CoinHistoryEntry che; + }; @@ -193,9 +203,15 @@ deposit_run (void *cls, (void) cmd; ds->is = is; + GNUNET_CRYPTO_eddsa_key_create (&ds->purse_priv.eddsa_priv); + GNUNET_CRYPTO_eddsa_key_create (&ds->merge_priv.eddsa_priv); + GNUNET_CRYPTO_ecdhe_key_create (&ds->contract_priv.ecdhe_priv); + GNUNET_CRYPTO_eddsa_key_get_public (&ds->purse_priv.eddsa_priv, + &ds->purse_pub.eddsa_pub); + for (unsigned int i = 0; i<ds->num_coin_references; i++) { - const struct Coin *cr = &ds->coin_references[i]; + struct Coin *cr = &ds->coin_references[i]; struct TALER_EXCHANGE_PurseDeposit *pd = &deposits[i]; const struct TALER_TESTING_Command *coin_cmd; const struct TALER_CoinSpendPrivateKeyP *coin_priv; @@ -239,14 +255,20 @@ deposit_run (void *cls, pd->coin_priv = *coin_priv; pd->amount = cr->deposit_with_fee; pd->h_denom_pub = denom_pub->h_key; + GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, + &cr->coin_pub.eddsa_pub); + cr->che.type = TALER_EXCHANGE_CTT_PURSE_DEPOSIT; + cr->che.amount = cr->deposit_with_fee; + GNUNET_CRYPTO_eddsa_key_get_public ( + &ds->purse_priv.eddsa_priv, + &cr->che.details.purse_deposit.purse_pub.eddsa_pub); + cr->che.details.purse_deposit.exchange_base_url + = TALER_TESTING_get_exchange_url (is); + TALER_age_commitment_hash ( + &age_commitment_proof->commitment, + &cr->che.details.purse_deposit.phac); } - GNUNET_CRYPTO_eddsa_key_create (&ds->purse_priv.eddsa_priv); - GNUNET_CRYPTO_eddsa_key_create (&ds->merge_priv.eddsa_priv); - GNUNET_CRYPTO_ecdhe_key_create (&ds->contract_priv.ecdhe_priv); - GNUNET_CRYPTO_eddsa_key_get_public (&ds->purse_priv.eddsa_priv, - &ds->purse_pub.eddsa_pub); - ds->purse_expiration = GNUNET_TIME_absolute_to_timestamp ( GNUNET_TIME_relative_to_absolute (ds->rel_expiration)); @@ -323,23 +345,33 @@ deposit_traits (void *cls, unsigned int index) { struct PurseCreateDepositState *ds = cls; - struct TALER_TESTING_Trait traits[] = { - TALER_TESTING_make_trait_merge_priv (&ds->merge_priv), - TALER_TESTING_make_trait_contract_priv (&ds->contract_priv), - TALER_TESTING_make_trait_purse_priv (&ds->purse_priv), - TALER_TESTING_make_trait_purse_pub (&ds->purse_pub), - TALER_TESTING_make_trait_contract_terms (ds->contract_terms), - TALER_TESTING_make_trait_deposit_amount (0, - &ds->target_amount), - TALER_TESTING_make_trait_timestamp (index, - &ds->purse_expiration), - TALER_TESTING_trait_end () - }; - - return TALER_TESTING_get_trait (traits, - ret, - trait, - index); + if (index >= ds->num_coin_references) + return GNUNET_NO; + + { + const struct Coin *co = &ds->coin_references[index]; + struct TALER_TESTING_Trait traits[] = { + TALER_TESTING_make_trait_merge_priv (&ds->merge_priv), + TALER_TESTING_make_trait_contract_priv (&ds->contract_priv), + TALER_TESTING_make_trait_coin_history (index, + &co->che), + TALER_TESTING_make_trait_coin_pub (index, + &co->coin_pub), + TALER_TESTING_make_trait_purse_priv (&ds->purse_priv), + TALER_TESTING_make_trait_purse_pub (&ds->purse_pub), + TALER_TESTING_make_trait_contract_terms (ds->contract_terms), + TALER_TESTING_make_trait_deposit_amount (0, + &ds->target_amount), + TALER_TESTING_make_trait_timestamp (index, + &ds->purse_expiration), + TALER_TESTING_trait_end () + }; + + return TALER_TESTING_get_trait (traits, + ret, + trait, + index); + } } diff --git a/src/testing/testing_api_cmd_purse_deposit.c b/src/testing/testing_api_cmd_purse_deposit.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2022 Taler Systems SA + Copyright (C) 2022, 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 @@ -39,6 +39,16 @@ struct Coin char *command_ref; /** + * Entry in the coin's history generated by this operation. + */ + struct TALER_EXCHANGE_CoinHistoryEntry che; + + /** + * Public key of the deposited coin. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + + /** * index of the specific coin in the traits of @e command_ref. */ unsigned int coin_index; @@ -274,7 +284,7 @@ deposit_run (void *cls, ds->purse_pub = *purse_pub; for (unsigned int i = 0; i<ds->num_coin_references; i++) { - const struct Coin *cr = &ds->coin_references[i]; + struct Coin *cr = &ds->coin_references[i]; struct TALER_EXCHANGE_PurseDeposit *pd = &deposits[i]; const struct TALER_TESTING_Command *coin_cmd; const struct TALER_CoinSpendPrivateKeyP *coin_priv; @@ -307,6 +317,16 @@ deposit_run (void *cls, TALER_TESTING_interpreter_fail (is); return; } + GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, + &cr->coin_pub.eddsa_pub); + cr->che.type = TALER_EXCHANGE_CTT_PURSE_DEPOSIT; + cr->che.amount = cr->deposit_with_fee; + cr->che.details.purse_deposit.purse_pub = *purse_pub; + cr->che.details.purse_deposit.exchange_base_url + = TALER_TESTING_get_exchange_url (is); + TALER_age_commitment_hash ( + &age_commitment_proof->commitment, + &cr->che.details.purse_deposit.phac); pd->age_commitment_proof = age_commitment_proof; pd->denom_sig = *denom_pub_sig; pd->coin_priv = *coin_priv; @@ -379,21 +399,31 @@ deposit_traits (void *cls, unsigned int index) { struct PurseDepositState *ds = cls; - struct TALER_TESTING_Trait traits[] = { - /* history entry MUST be first due to response code logic below! */ - TALER_TESTING_make_trait_reserve_history (0, - &ds->reserve_history), - TALER_TESTING_make_trait_reserve_pub (&ds->reserve_pub), - TALER_TESTING_make_trait_purse_pub (&ds->purse_pub), - TALER_TESTING_trait_end () - }; - - return TALER_TESTING_get_trait (ds->purse_complete - ? &traits[0] /* we have reserve history */ - : &traits[1], /* skip reserve history */ - ret, - trait, - index); + + if (index >= ds->num_coin_references) + return GNUNET_NO; + { + const struct Coin *co = &ds->coin_references[index]; + struct TALER_TESTING_Trait traits[] = { + /* history entry MUST be first due to response code logic below! */ + TALER_TESTING_make_trait_reserve_history (0, + &ds->reserve_history), + TALER_TESTING_make_trait_coin_history (index, + &co->che), + TALER_TESTING_make_trait_coin_pub (index, + &co->coin_pub), + TALER_TESTING_make_trait_reserve_pub (&ds->reserve_pub), + TALER_TESTING_make_trait_purse_pub (&ds->purse_pub), + TALER_TESTING_trait_end () + }; + + return TALER_TESTING_get_trait (ds->purse_complete + ? &traits[0] /* we have reserve history */ + : &traits[1], /* skip reserve history */ + ret, + trait, + index); + } } @@ -431,7 +461,8 @@ TALER_TESTING_cmd_purse_deposit_coins ( { struct Coin *c = &ds->coin_references[i++]; - GNUNET_assert (NULL != (val = va_arg (ap, const char *))); + GNUNET_assert (NULL != (val = va_arg (ap, + const char *))); GNUNET_assert (GNUNET_OK == TALER_TESTING_parse_coin_reference ( ref, diff --git a/src/testing/testing_api_cmd_recoup.c b/src/testing/testing_api_cmd_recoup.c @@ -59,6 +59,16 @@ struct RecoupState struct TALER_ReservePublicKeyP reserve_pub; /** + * Entry in the coin's history generated by this operation. + */ + struct TALER_EXCHANGE_CoinHistoryEntry che; + + /** + * Public key of the refunded coin. + */ + struct TALER_CoinSpendPublicKeyP coin; + + /** * Reserve history entry, set if this recoup actually filled up a reserve. * Otherwise `reserve_history.type` will be zero. */ @@ -145,6 +155,7 @@ recoup_cb (void *cls, TALER_amount_is_valid (&ps->reserve_history.amount)) ps->reserve_history.type = TALER_EXCHANGE_RTT_RECOUP; /* ps->reserve_history.details.recoup_details.coin_pub; // initialized earlier */ + ps->che.details.recoup.reserve_pub = ps->reserve_pub; } break; case MHD_HTTP_NOT_FOUND: @@ -183,6 +194,7 @@ recoup_run (void *cls, char *cref; unsigned int idx; const struct TALER_ExchangeWithdrawValues *ewv; + struct TALER_DenominationHashP h_denom_pub; ps->is = is; if (GNUNET_OK != @@ -214,6 +226,8 @@ recoup_run (void *cls, TALER_TESTING_interpreter_fail (is); return; } + GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, + &ps->coin.eddsa_pub); if (GNUNET_OK != TALER_TESTING_get_trait_exchange_wd_value (coin_cmd, idx, @@ -256,6 +270,17 @@ recoup_run (void *cls, GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Trying to recoup denomination '%s'\n", TALER_B2S (&denom_pub->h_key)); + ps->che.type = TALER_EXCHANGE_CTT_RECOUP; + ps->che.amount = ps->reserve_history.amount; + TALER_planchet_blinding_secret_create (planchet, + ewv, + &ps->che.details.recoup.coin_bks); + TALER_denom_pub_hash (&denom_pub->key, + &h_denom_pub); + TALER_wallet_recoup_sign (&h_denom_pub, + &ps->che.details.recoup.coin_bks, + coin_priv, + &ps->che.details.recoup.coin_sig); ps->ph = TALER_EXCHANGE_recoup ( TALER_TESTING_interpreter_get_context (is), TALER_TESTING_get_exchange_url (is), @@ -316,6 +341,10 @@ recoup_traits (void *cls, TALER_TESTING_make_trait_reserve_pub (&ps->reserve_pub), TALER_TESTING_make_trait_reserve_history (0, &ps->reserve_history), + TALER_TESTING_make_trait_coin_history (0, + &ps->che), + TALER_TESTING_make_trait_coin_pub (0, + &ps->coin), TALER_TESTING_trait_end () }; diff --git a/src/testing/testing_api_cmd_recoup_refresh.c b/src/testing/testing_api_cmd_recoup_refresh.c @@ -44,6 +44,26 @@ struct RecoupRefreshState const char *coin_reference; /** + * Entry in the old coin's history generated by this operation. + */ + struct TALER_EXCHANGE_CoinHistoryEntry che_old; + + /** + * Entry in the recouped coin's history generated by this operation. + */ + struct TALER_EXCHANGE_CoinHistoryEntry che_new; + + /** + * Public key of the refunded coin. + */ + struct TALER_CoinSpendPublicKeyP coin_pub_old; + + /** + * Public key of the refunded coin. + */ + struct TALER_CoinSpendPublicKeyP coin_pub_new; + + /** * Amount to be recouped. */ struct TALER_Amount amount; @@ -178,6 +198,7 @@ recoup_refresh_run (void *cls, const struct TALER_TESTING_Command *coin_cmd; const struct TALER_TESTING_Command *melt_cmd; const struct TALER_CoinSpendPrivateKeyP *coin_priv; + const struct TALER_CoinSpendPrivateKeyP *coin_priv_old; const struct TALER_EXCHANGE_DenomPublicKey *denom_pub; const struct TALER_DenominationSignature *coin_sig; const struct TALER_RefreshMasterSecretP *rplanchet; @@ -185,6 +206,7 @@ recoup_refresh_run (void *cls, const struct TALER_ExchangeWithdrawValues *ewv; char *cref; unsigned int idx; + struct TALER_DenominationHashP h_denom_pub; rrs->is = is; if (GNUNET_OK != @@ -224,6 +246,22 @@ recoup_refresh_run (void *cls, return; } if (GNUNET_OK != + TALER_TESTING_get_trait_coin_priv (melt_cmd, + 0, + &coin_priv_old)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + GNUNET_CRYPTO_eddsa_key_get_public ( + &coin_priv->eddsa_priv, + &rrs->coin_pub_new.eddsa_pub); + GNUNET_CRYPTO_eddsa_key_get_public ( + &coin_priv_old->eddsa_priv, + &rrs->coin_pub_old.eddsa_pub); + + if (GNUNET_OK != TALER_TESTING_get_trait_exchange_wd_value (melt_cmd, idx, &ewv)) @@ -270,6 +308,29 @@ recoup_refresh_run (void *cls, GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Trying to recoup_refresh denomination '%s'\n", TALER_B2S (&denom_pub->h_key)); + rrs->che_old.type + = TALER_EXCHANGE_CTT_OLD_COIN_RECOUP; + rrs->che_old.amount + = rrs->amount; + rrs->che_old.details.old_coin_recoup.new_coin_pub + = rrs->coin_pub_new; + rrs->che_new.type + = TALER_EXCHANGE_CTT_RECOUP_REFRESH; + rrs->che_new.amount + = rrs->amount; + rrs->che_new.details.recoup_refresh.old_coin_pub + = rrs->coin_pub_old; + TALER_planchet_blinding_secret_create ( + planchet, + ewv, + &rrs->che_new.details.recoup_refresh.coin_bks); + TALER_denom_pub_hash (&denom_pub->key, + &h_denom_pub); + TALER_wallet_recoup_refresh_sign ( + &h_denom_pub, + &rrs->che_new.details.recoup_refresh.coin_bks, + coin_priv, + &rrs->che_new.details.recoup_refresh.coin_sig); rrs->ph = TALER_EXCHANGE_recoup_refresh ( TALER_TESTING_interpreter_get_context (is), TALER_TESTING_get_exchange_url (is), @@ -307,6 +368,42 @@ recoup_refresh_cleanup (void *cls, } +/** + * Offer internal data from a "recoup-refresh" CMD state to other + * commands. + * + * @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 enum GNUNET_GenericReturnValue +recoup_refresh_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + struct RecoupRefreshState *rrs = cls; + struct TALER_TESTING_Trait traits[] = { + TALER_TESTING_make_trait_coin_history (0, + &rrs->che_old), + TALER_TESTING_make_trait_coin_pub (0, + &rrs->coin_pub_old), + TALER_TESTING_make_trait_coin_history (1, + &rrs->che_new), + TALER_TESTING_make_trait_coin_pub (1, + &rrs->coin_pub_new), + TALER_TESTING_trait_end () + }; + + return TALER_TESTING_get_trait (traits, + ret, + trait, + index); +} + + struct TALER_TESTING_Command TALER_TESTING_cmd_recoup_refresh (const char *label, unsigned int expected_response_code, @@ -335,7 +432,8 @@ TALER_TESTING_cmd_recoup_refresh (const char *label, .cls = rrs, .label = label, .run = &recoup_refresh_run, - .cleanup = &recoup_refresh_cleanup + .cleanup = &recoup_refresh_cleanup, + .traits = &recoup_refresh_traits }; return cmd; diff --git a/src/testing/testing_api_cmd_refresh.c b/src/testing/testing_api_cmd_refresh.c @@ -118,6 +118,12 @@ struct RefreshMeltState struct TALER_EXCHANGE_MeltHandle *rmh; /** + * Expected entry in the coin history created by this + * operation. + */ + struct TALER_EXCHANGE_CoinHistoryEntry che; + + /** * Interpreter state. */ struct TALER_TESTING_Interpreter *is; @@ -145,6 +151,11 @@ struct RefreshMeltState const struct TALER_CoinSpendPrivateKeyP *melt_priv; /** + * Public key of the dirty coin being melted. + */ + struct TALER_CoinSpendPublicKeyP melt_pub; + + /** * Task scheduled to try later. */ struct GNUNET_SCHEDULER_Task *retry_task; @@ -1147,6 +1158,8 @@ melt_run (void *cls, } /* end for */ rms->refresh_data.melt_priv = *rms->melt_priv; + GNUNET_CRYPTO_eddsa_key_get_public (&rms->melt_priv->eddsa_priv, + &rms->melt_pub.eddsa_pub); rms->refresh_data.melt_amount = melt_amount; rms->refresh_data.melt_sig = *melt_sig; rms->refresh_data.melt_pk = *melt_denom_pub; @@ -1165,6 +1178,13 @@ melt_run (void *cls, GNUNET_assert ((NULL == age_commitment_proof) || (0 < age_commitment_proof->commitment.num)); + rms->che.type = TALER_EXCHANGE_CTT_MELT; + rms->che.amount = melt_amount; + if (NULL != age_commitment_proof) + rms->che.details.melt.h_age_commitment = *h_age_commitment; + else + rms->che.details.melt.no_hac = true; + rms->rmh = TALER_EXCHANGE_melt ( TALER_TESTING_interpreter_get_context (is), TALER_TESTING_get_exchange_url (is), @@ -1253,8 +1273,12 @@ melt_traits (void *cls, struct TALER_TESTING_Trait traits[] = { TALER_TESTING_make_trait_denom_pub (index, &rms->fresh_pks[index]), - TALER_TESTING_make_trait_coin_priv (index, + TALER_TESTING_make_trait_coin_priv (0, rms->melt_priv), + TALER_TESTING_make_trait_coin_pub (0, + &rms->melt_pub), + TALER_TESTING_make_trait_coin_history (0, + &rms->che), TALER_TESTING_make_trait_age_commitment_proof ( index, rms->refresh_data.melt_age_commitment_proof), diff --git a/src/testing/testing_api_cmd_refund.c b/src/testing/testing_api_cmd_refund.c @@ -54,6 +54,16 @@ struct RefundState uint64_t refund_transaction_id; /** + * Entry in the coin's history generated by this operation. + */ + struct TALER_EXCHANGE_CoinHistoryEntry che; + + /** + * Public key of the refunded coin. + */ + struct TALER_CoinSpendPublicKeyP coin; + + /** * Handle to the refund operation. */ struct TALER_EXCHANGE_RefundHandle *rh; @@ -105,12 +115,12 @@ refund_run (void *cls, { struct RefundState *rs = cls; const struct TALER_CoinSpendPrivateKeyP *coin_priv; - struct TALER_CoinSpendPublicKeyP coin; const json_t *contract_terms; struct TALER_PrivateContractHashP h_contract_terms; struct TALER_Amount refund_amount; const struct TALER_MerchantPrivateKeyP *merchant_priv; const struct TALER_TESTING_Command *coin_cmd; + const struct TALER_EXCHANGE_DenomPublicKey *denom_pub; rs->is = is; if (GNUNET_OK != @@ -145,10 +155,15 @@ refund_run (void *cls, &h_contract_terms)); /* Hunting for a coin .. */ - if (GNUNET_OK != - TALER_TESTING_get_trait_coin_priv (coin_cmd, - 0, - &coin_priv)) + if ( (GNUNET_OK != + TALER_TESTING_get_trait_coin_priv (coin_cmd, + 0, + &coin_priv)) || + (GNUNET_OK != + TALER_TESTING_get_trait_denom_pub (coin_cmd, + 0, + &denom_pub)) ) + { GNUNET_break (0); TALER_TESTING_interpreter_fail (is); @@ -156,7 +171,7 @@ refund_run (void *cls, } GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, - &coin.eddsa_pub); + &rs->coin.eddsa_pub); if (GNUNET_OK != TALER_TESTING_get_trait_merchant_priv (coin_cmd, &merchant_priv)) @@ -165,13 +180,30 @@ refund_run (void *cls, TALER_TESTING_interpreter_fail (is); return; } + rs->che.type = TALER_EXCHANGE_CTT_REFUND; + rs->che.details.refund.h_contract_terms = h_contract_terms; + GNUNET_CRYPTO_eddsa_key_get_public ( + &merchant_priv->eddsa_priv, + &rs->che.details.refund.merchant_pub.eddsa_pub); + rs->che.details.refund.refund_fee = denom_pub->fees.refund; + rs->che.details.refund.sig_amount = refund_amount; + TALER_amount_subtract (&rs->che.amount, + &refund_amount, + &rs->che.details.refund.refund_fee); + rs->che.details.refund.rtransaction_id = rs->refund_transaction_id; + TALER_merchant_refund_sign (&rs->coin, + &h_contract_terms, + rs->refund_transaction_id, + &refund_amount, + merchant_priv, + &rs->che.details.refund.sig); rs->rh = TALER_EXCHANGE_refund ( TALER_TESTING_interpreter_get_context (is), TALER_TESTING_get_exchange_url (is), TALER_TESTING_get_keys (is), &refund_amount, &h_contract_terms, - &coin, + &rs->coin, rs->refund_transaction_id, merchant_priv, &refund_cb, @@ -181,6 +213,37 @@ refund_run (void *cls, /** + * Offer internal data from a "refund" CMD, to other commands. + * + * @param cls closure. + * @param[out] ret result. + * @param trait name of the trait. + * @param index index number of the object to offer. + * @return #GNUNET_OK on success. + */ +static enum GNUNET_GenericReturnValue +refund_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + struct RefundState *rs = cls; + struct TALER_TESTING_Trait traits[] = { + TALER_TESTING_make_trait_coin_history (0, + &rs->che), + TALER_TESTING_make_trait_coin_pub (0, + &rs->coin), + TALER_TESTING_trait_end () + }; + + return TALER_TESTING_get_trait (traits, + ret, + trait, + index); +} + + +/** * Free the state from a "refund" CMD, and possibly cancel * a pending operation thereof. * @@ -221,7 +284,8 @@ TALER_TESTING_cmd_refund (const char *label, .cls = rs, .label = label, .run = &refund_run, - .cleanup = &refund_cleanup + .cleanup = &refund_cleanup, + .traits = &refund_traits }; return cmd; @@ -249,7 +313,8 @@ TALER_TESTING_cmd_refund_with_id ( .cls = rs, .label = label, .run = &refund_run, - .cleanup = &refund_cleanup + .cleanup = &refund_cleanup, + .traits = &refund_traits }; return cmd; diff --git a/src/testing/testing_api_cmd_reserve_history.c b/src/testing/testing_api_cmd_reserve_history.c @@ -105,6 +105,162 @@ struct AnalysisContext /** + * Compare @a h1 and @a h2. + * + * @param h1 a history entry + * @param h2 a history entry + * @return 0 if @a h1 and @a h2 are equal + */ +static int +history_entry_cmp ( + const struct TALER_EXCHANGE_ReserveHistoryEntry *h1, + const struct TALER_EXCHANGE_ReserveHistoryEntry *h2) +{ + if (h1->type != h2->type) + return 1; + switch (h1->type) + { + case TALER_EXCHANGE_RTT_CREDIT: + if ( (0 == + TALER_amount_cmp (&h1->amount, + &h2->amount)) && + (0 == strcasecmp (h1->details.in_details.sender_url, + h2->details.in_details.sender_url)) && + (h1->details.in_details.wire_reference == + h2->details.in_details.wire_reference) && + (GNUNET_TIME_timestamp_cmp (h1->details.in_details.timestamp, + ==, + h2->details.in_details.timestamp)) ) + return 0; + return 1; + case TALER_EXCHANGE_RTT_WITHDRAWAL: + if ( (0 == + TALER_amount_cmp (&h1->amount, + &h2->amount)) && + (0 == + TALER_amount_cmp (&h1->details.withdraw.fee, + &h2->details.withdraw.fee)) ) + /* testing_api_cmd_withdraw doesn't set the out_authorization_sig, + so we cannot test for it here. but if the amount matches, + that should be good enough. */ + return 0; + return 1; + case TALER_EXCHANGE_RTT_AGEWITHDRAWAL: + /* testing_api_cmd_age_withdraw doesn't set the out_authorization_sig, + so we cannot test for it here. but if the amount matches, + that should be good enough. */ + if ( (0 == + TALER_amount_cmp (&h1->amount, + &h2->amount)) && + (0 == + TALER_amount_cmp (&h1->details.age_withdraw.fee, + &h2->details.age_withdraw.fee)) && + (h1->details.age_withdraw.max_age == + h2->details.age_withdraw.max_age)) + return 0; + return 1; + case TALER_EXCHANGE_RTT_RECOUP: + /* exchange_sig, exchange_pub and timestamp are NOT available + from the original recoup response, hence here NOT check(able/ed) */ + if ( (0 == + TALER_amount_cmp (&h1->amount, + &h2->amount)) && + (0 == + GNUNET_memcmp (&h1->details.recoup_details.coin_pub, + &h2->details.recoup_details.coin_pub)) ) + return 0; + return 1; + case TALER_EXCHANGE_RTT_CLOSING: + /* testing_api_cmd_exec_closer doesn't set the + receiver_account_details, exchange_sig, exchange_pub or wtid or timestamp + so we cannot test for it here. but if the amount matches, + that should be good enough. */ + if ( (0 == + TALER_amount_cmp (&h1->amount, + &h2->amount)) && + (0 == + TALER_amount_cmp (&h1->details.close_details.fee, + &h2->details.close_details.fee)) ) + return 0; + return 1; + case TALER_EXCHANGE_RTT_MERGE: + if ( (0 == + TALER_amount_cmp (&h1->amount, + &h2->amount)) && + (0 == + TALER_amount_cmp (&h1->details.merge_details.purse_fee, + &h2->details.merge_details.purse_fee)) && + (GNUNET_TIME_timestamp_cmp (h1->details.merge_details.merge_timestamp, + ==, + h2->details.merge_details.merge_timestamp)) + && + (GNUNET_TIME_timestamp_cmp (h1->details.merge_details.purse_expiration, + ==, + h2->details.merge_details.purse_expiration)) + && + (0 == + GNUNET_memcmp (&h1->details.merge_details.merge_pub, + &h2->details.merge_details.merge_pub)) && + (0 == + GNUNET_memcmp (&h1->details.merge_details.h_contract_terms, + &h2->details.merge_details.h_contract_terms)) && + (0 == + GNUNET_memcmp (&h1->details.merge_details.purse_pub, + &h2->details.merge_details.purse_pub)) && + (0 == + GNUNET_memcmp (&h1->details.merge_details.reserve_sig, + &h2->details.merge_details.reserve_sig)) && + (h1->details.merge_details.min_age == + h2->details.merge_details.min_age) && + (h1->details.merge_details.flags == + h2->details.merge_details.flags) ) + return 0; + return 1; + case TALER_EXCHANGE_RTT_OPEN: + if ( (0 == + TALER_amount_cmp (&h1->amount, + &h2->amount)) && + (GNUNET_TIME_timestamp_cmp ( + h1->details.open_request.request_timestamp, + ==, + h2->details.open_request.request_timestamp)) && + (GNUNET_TIME_timestamp_cmp ( + h1->details.open_request.reserve_expiration, + ==, + h2->details.open_request.reserve_expiration)) && + (h1->details.open_request.purse_limit == + h2->details.open_request.purse_limit) && + (0 == + TALER_amount_cmp (&h1->details.open_request.reserve_payment, + &h2->details.open_request.reserve_payment)) && + (0 == + GNUNET_memcmp (&h1->details.open_request.reserve_sig, + &h2->details.open_request.reserve_sig)) ) + return 0; + return 1; + case TALER_EXCHANGE_RTT_CLOSE: + if ( (0 == + TALER_amount_cmp (&h1->amount, + &h2->amount)) && + (GNUNET_TIME_timestamp_cmp ( + h1->details.close_request.request_timestamp, + ==, + h2->details.close_request.request_timestamp)) && + (0 == + GNUNET_memcmp (&h1->details.close_request.target_account_h_payto, + &h2->details.close_request.target_account_h_payto)) && + (0 == + GNUNET_memcmp (&h1->details.close_request.reserve_sig, + &h2->details.close_request.reserve_sig)) ) + return 0; + return 1; + } + GNUNET_assert (0); + return 1; +} + + +/** * Check if @a cmd changed the reserve, if so, find the * entry in our history and set the respective index in found * to true. If the entry is not found, set failure. @@ -188,8 +344,8 @@ analyze_command (void *cls, if (found[i]) continue; /* already found, skip */ if (0 == - TALER_TESTING_history_entry_cmp (he, - &history[i])) + history_entry_cmp (he, + &history[i])) { found[i] = true; matched = true; diff --git a/src/testing/testing_api_misc.c b/src/testing/testing_api_misc.c @@ -118,6 +118,23 @@ TALER_TESTING_get_credentials ( } GNUNET_free (csn); } + { + char *csn; + + GNUNET_asprintf (&csn, + "admin-accountcredentials-%s", + &exchange_account_section[strlen ("exchange-account-")]); + if (GNUNET_OK != + TALER_BANK_auth_parse_cfg (ua->cfg, + csn, + &ua->ba_admin)) + { + GNUNET_break (0); + GNUNET_free (csn); + return GNUNET_SYSERR; + } + GNUNET_free (csn); + } if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (ua->cfg, "exchange", diff --git a/src/util/conversion.c b/src/util/conversion.c @@ -255,11 +255,6 @@ child_done_cb (void *cls, json_error_t err; ec->cwh = NULL; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Conversion helper exited with status %d and code %llu after outputting %llu bytes of data\n", - (int) type, - (unsigned long long) exit_code, - (unsigned long long) ec->read_pos); if (NULL != ec->read_task) { GNUNET_SCHEDULER_cancel (ec->read_task); @@ -267,11 +262,11 @@ child_done_cb (void *cls, the read buffer. So drain it now, just in case. */ read_cb (ec); } - if (NULL != ec->read_task) - { - GNUNET_SCHEDULER_cancel (ec->read_task); - ec->read_task = NULL; - } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Conversion helper exited with status %d and code %llu after outputting %llu bytes of data\n", + (int) type, + (unsigned long long) exit_code, + (unsigned long long) ec->read_pos); GNUNET_OS_process_destroy (ec->helper); ec->helper = NULL; if (0 != ec->read_pos)