diff options
Diffstat (limited to 'src/mint')
-rw-r--r-- | src/mint/Makefile.am | 24 | ||||
-rw-r--r-- | src/mint/taler-mint-aggregator.c | 914 | ||||
-rw-r--r-- | src/mint/taler-mint-httpd.c | 45 | ||||
-rw-r--r-- | src/mint/taler-mint-httpd.h | 8 | ||||
-rw-r--r-- | src/mint/taler-mint-httpd_admin.c | 4 | ||||
-rw-r--r-- | src/mint/taler-mint-httpd_db.c | 297 | ||||
-rw-r--r-- | src/mint/taler-mint-httpd_db.h | 16 | ||||
-rw-r--r-- | src/mint/taler-mint-httpd_deposit.c | 4 | ||||
-rw-r--r-- | src/mint/taler-mint-httpd_responses.c | 101 | ||||
-rw-r--r-- | src/mint/taler-mint-httpd_responses.h | 43 | ||||
-rw-r--r-- | src/mint/taler-mint-httpd_tracking.c | 24 | ||||
-rw-r--r-- | src/mint/taler-mint-httpd_validation.c | 231 | ||||
-rw-r--r-- | src/mint/taler-mint-httpd_validation.h | 76 | ||||
-rw-r--r-- | src/mint/taler-mint-httpd_wire.c | 31 |
14 files changed, 1695 insertions, 123 deletions
diff --git a/src/mint/Makefile.am b/src/mint/Makefile.am index a115d63a0..8e2eae77b 100644 --- a/src/mint/Makefile.am +++ b/src/mint/Makefile.am @@ -7,21 +7,33 @@ if USE_COVERAGE endif bin_PROGRAMS = \ + taler-mint-aggregator \ taler-mint-httpd +taler_mint_aggregator_SOURCES = \ + taler-mint-aggregator.c +taler_mint_aggregator_LDADD = \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/util/libtalerutil.la \ + $(top_builddir)/src/wire/libtalerwire.la \ + $(top_builddir)/src/mintdb/libtalermintdb.la \ + -ljansson \ + -lgnunetutil + taler_mint_httpd_SOURCES = \ taler-mint-httpd.c taler-mint-httpd.h \ - taler-mint-httpd_keystate.c taler-mint-httpd_keystate.h \ - taler-mint-httpd_db.c taler-mint-httpd_db.h \ - taler-mint-httpd_parsing.c taler-mint-httpd_parsing.h \ - taler-mint-httpd_responses.c taler-mint-httpd_responses.h \ - taler-mint-httpd_mhd.c taler-mint-httpd_mhd.h \ taler-mint-httpd_admin.c taler-mint-httpd_admin.h \ + taler-mint-httpd_db.c taler-mint-httpd_db.h \ taler-mint-httpd_deposit.c taler-mint-httpd_deposit.h \ + taler-mint-httpd_keystate.c taler-mint-httpd_keystate.h \ + taler-mint-httpd_mhd.c taler-mint-httpd_mhd.h \ + taler-mint-httpd_parsing.c taler-mint-httpd_parsing.h \ + taler-mint-httpd_refresh.c taler-mint-httpd_refresh.h \ taler-mint-httpd_reserve.c taler-mint-httpd_reserve.h \ + taler-mint-httpd_responses.c taler-mint-httpd_responses.h \ taler-mint-httpd_tracking.c taler-mint-httpd_tracking.h \ taler-mint-httpd_wire.c taler-mint-httpd_wire.h \ - taler-mint-httpd_refresh.c taler-mint-httpd_refresh.h + taler-mint-httpd_validation.c taler-mint-httpd_validation.h taler_mint_httpd_LDADD = \ $(LIBGCRYPT_LIBS) \ $(top_builddir)/src/util/libtalerutil.la \ diff --git a/src/mint/taler-mint-aggregator.c b/src/mint/taler-mint-aggregator.c new file mode 100644 index 000000000..5e05c8673 --- /dev/null +++ b/src/mint/taler-mint-aggregator.c @@ -0,0 +1,914 @@ +/* + This file is part of TALER + Copyright (C) 2016 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file taler-mint-aggregator.c + * @brief Process that aggregates outgoing transactions and executes them + * @author Christian Grothoff + * + * TODO: + * - simplify global_ret: make it a global! + * - handle shutdown more nicely (call 'cancel' method on wire transfers) + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <jansson.h> +#include <pthread.h> +#include "taler_mintdb_lib.h" +#include "taler_mintdb_plugin.h" +#include "taler_wire_lib.h" + +/** + * Which currency is used by this mint? + */ +static char *mint_currency_string; + +/** + * Which wireformat should be supported by this aggregator? + */ +static char *mint_wireformat; + +/** + * Base directory of the mint (global) + */ +static char *mint_directory; + +/** + * The mint's configuration (global) + */ +static struct GNUNET_CONFIGURATION_Handle *cfg; + +/** + * Our DB plugin. + */ +static struct TALER_MINTDB_Plugin *db_plugin; + +/** + * Our wire plugin. + */ +static struct TALER_WIRE_Plugin *wire_plugin; + +/** + * Task for the main #run() function. + */ +static struct GNUNET_SCHEDULER_Task *task; + +/** + * Limit on the number of transactions we aggregate at once. Note + * that the limit must be big enough to ensure that when transactions + * of the smallest possible unit are aggregated, they do surpass the + * "tiny" threshold beyond which we never trigger a wire transaction! + * + * TODO: make configurable (via config file or command line option) + */ +static unsigned int aggregation_limit = 10000; + + +/** + * Load configuration parameters for the mint + * server into the corresponding global variables. + * + * @param mint_directory the mint's directory + * @return #GNUNET_OK on success + */ +static int +mint_serve_process_config (const char *mint_directory) +{ + char *type; + + cfg = TALER_config_load (mint_directory); + if (NULL == cfg) + { + fprintf (stderr, + "Failed to load mint configuration\n"); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "mint", + "currency", + &mint_currency_string)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "mint", + "currency"); + return GNUNET_SYSERR; + } + if (strlen (mint_currency_string) >= TALER_CURRENCY_LEN) + { + fprintf (stderr, + "Currency `%s' longer than the allowed limit of %u characters.", + mint_currency_string, + (unsigned int) TALER_CURRENCY_LEN); + return GNUNET_SYSERR; + } + if (NULL != mint_wireformat) + GNUNET_CONFIGURATION_set_value_string (cfg, + "mint", + "wireformat", + mint_wireformat); + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "mint", + "wireformat", + &type)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "mint", + "wireformat"); + return GNUNET_SYSERR; + } + + if (NULL == + (db_plugin = TALER_MINTDB_plugin_load (cfg))) + { + fprintf (stderr, + "Failed to initialize DB subsystem\n"); + GNUNET_free (type); + return GNUNET_SYSERR; + } + + if (NULL == + (wire_plugin = TALER_WIRE_plugin_load (cfg, + type))) + { + fprintf (stderr, + "Failed to load wire plugin for `%s'\n", + type); + GNUNET_free (type); + return GNUNET_SYSERR; + } + GNUNET_free (type); + + return GNUNET_OK; +} + + +/** + * Information about one aggregation process to + * be executed. + */ +struct AggregationUnit +{ + /** + * Public key of the merchant. + */ + struct TALER_MerchantPublicKeyP merchant_pub; + + /** + * Total amount to be transferred. + */ + struct TALER_Amount total_amount; + + /** + * Hash of @e wire. + */ + struct GNUNET_HashCode h_wire; + + /** + * Wire transfer identifier we use. + */ + struct TALER_WireTransferIdentifierRawP wtid; + + /** + * Row ID of the transaction that started it all. + */ + unsigned long long row_id; + + /** + * The current time. + */ + struct GNUNET_TIME_Absolute execution_time; + + /** + * Wire details of the merchant. + */ + json_t *wire; + + /** + * Database session for all of our transactions. + */ + struct TALER_MINTDB_Session *session; + + /** + * Wire preparation handle. + */ + struct TALER_WIRE_PrepareHandle *ph; + + /** + * Array of #aggregation_limit row_ids from the + * aggregation. + */ + unsigned long long *additional_rows; + + /** + * Pointer to global return value. Closure for #run(). + */ + int *global_ret; + + /** + * Offset specifying how many #additional_rows are in use. + */ + unsigned int rows_offset; + + /** + * Set to #GNUNET_YES if we have to abort due to failure. + */ + int failed; + +}; + + +/** + * Function called with details about deposits that have been made, + * with the goal of executing the corresponding wire transaction. + * + * @param cls closure with the `struct AggregationUnit` + * @param row_id identifies database entry + * @param merchant_pub public key of the merchant + * @param coin_pub public key of the coin + * @param amount_with_fee amount that was deposited including fee + * @param deposit_fee amount the mint gets to keep as transaction fees + * @param transaction_id unique transaction ID chosen by the merchant + * @param h_contract hash of the contract between merchant and customer + * @param wire_deadline by which the merchant adviced that he would like the + * wire transfer to be executed + * @param wire wire details for the merchant + * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop + */ +static int +deposit_cb (void *cls, + unsigned long long row_id, + const struct TALER_MerchantPublicKeyP *merchant_pub, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_Amount *amount_with_fee, + const struct TALER_Amount *deposit_fee, + uint64_t transaction_id, + const struct GNUNET_HashCode *h_contract, + struct GNUNET_TIME_Absolute wire_deadline, + const json_t *wire) +{ + struct AggregationUnit *au = cls; + + au->merchant_pub = *merchant_pub; + if (GNUNET_OK != + TALER_amount_subtract (&au->total_amount, + amount_with_fee, + deposit_fee)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Fatally malformed record at %llu\n", + row_id); + return GNUNET_SYSERR; + } + au->row_id = row_id; + au->wire = (json_t *) wire; + au->execution_time = GNUNET_TIME_absolute_get (); + TALER_hash_json (au->wire, + &au->h_wire); + json_incref (au->wire); + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, + &au->wtid, + sizeof (au->wtid)); + if (GNUNET_OK != + db_plugin->insert_aggregation_tracking (db_plugin->cls, + au->session, + &au->wtid, + merchant_pub, + &au->h_wire, + h_contract, + transaction_id, + au->execution_time, + coin_pub, + amount_with_fee, + deposit_fee)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + db_plugin->mark_deposit_done (db_plugin->cls, + au->session, + row_id)) + { + GNUNET_break (0); + au->failed = GNUNET_YES; + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + + +/** + * Function called with details about another deposit we + * can aggregate into an existing aggregation unit. + * + * @param cls closure with the `struct AggregationUnit` + * @param row_id identifies database entry + * @param merchant_pub public key of the merchant + * @param coin_pub public key of the coin + * @param amount_with_fee amount that was deposited including fee + * @param deposit_fee amount the mint gets to keep as transaction fees + * @param transaction_id unique transaction ID chosen by the merchant + * @param h_contract hash of the contract between merchant and customer + * @param wire_deadline by which the merchant adviced that he would like the + * wire transfer to be executed + * @param wire wire details for the merchant + * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop + */ +static int +aggregate_cb (void *cls, + unsigned long long row_id, + const struct TALER_MerchantPublicKeyP *merchant_pub, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_Amount *amount_with_fee, + const struct TALER_Amount *deposit_fee, + uint64_t transaction_id, + const struct GNUNET_HashCode *h_contract, + struct GNUNET_TIME_Absolute wire_deadline, + const json_t *wire) +{ + struct AggregationUnit *au = cls; + struct TALER_Amount delta; + + GNUNET_break (0 == + memcmp (&au->merchant_pub, + merchant_pub, + sizeof (struct TALER_MerchantPublicKeyP))); + /* compute contribution of this coin after fees */ + if (GNUNET_OK != + TALER_amount_subtract (&delta, + amount_with_fee, + deposit_fee)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Fatally malformed record at %llu\n", + row_id); + return GNUNET_SYSERR; + } + /* add to total */ + if (GNUNET_OK != + TALER_amount_add (&au->total_amount, + &au->total_amount, + &delta)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Overflow or currency incompatibility during aggregation at %llu\n", + row_id); + /* Skip this one, but keep going! */ + return GNUNET_OK; + } + if (au->rows_offset >= aggregation_limit) + { + /* Bug: we asked for at most #aggregation_limit results! */ + GNUNET_break (0); + /* Skip this one, but keep going. */ + return GNUNET_OK; + } + if (NULL == au->additional_rows) + au->additional_rows = GNUNET_new_array (aggregation_limit, + unsigned long long); + /* "append" to our list of rows */ + au->additional_rows[au->rows_offset++] = row_id; + /* insert into aggregation tracking table */ + if (GNUNET_OK != + db_plugin->insert_aggregation_tracking (db_plugin->cls, + au->session, + &au->wtid, + merchant_pub, + &au->h_wire, + h_contract, + transaction_id, + au->execution_time, + coin_pub, + amount_with_fee, + deposit_fee)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + db_plugin->mark_deposit_done (db_plugin->cls, + au->session, + row_id)) + { + GNUNET_break (0); + au->failed = GNUNET_YES; + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Function to be called with the prepared transfer data. + * + * @param cls closure with the `struct AggregationUnit` + * @param buf transaction data to persist, NULL on error + * @param buf_size number of bytes in @a buf, 0 on error + */ +static void +prepare_cb (void *cls, + const char *buf, + size_t buf_size); + + +/** + * Main work function that queries the DB and aggregates transactions + * into larger wire transfers. + * + * @param cls pointer to an `int` which we will return from main() + * @param tc scheduler context + */ +static void +run_aggregation (void *cls, + const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + int *global_ret = cls; + struct TALER_MINTDB_Session *session; + struct AggregationUnit *au; + unsigned int i; + int ret; + + if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN)) + return; + if (NULL == (session = db_plugin->get_session (db_plugin->cls, + GNUNET_NO))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to obtain database session!\n"); + *global_ret = GNUNET_SYSERR; + return; + } + if (GNUNET_OK != + db_plugin->start (db_plugin->cls, + session)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to start database transaction!\n"); + *global_ret = GNUNET_SYSERR; + return; + } + au = GNUNET_new (struct AggregationUnit); + au->session = session; + ret = db_plugin->get_ready_deposit (db_plugin->cls, + session, + &deposit_cb, + au); + if (GNUNET_OK != ret) + { + GNUNET_free (au); + db_plugin->rollback (db_plugin->cls, + session); + if (0 != ret) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to execute deposit iteration!\n"); + *global_ret = GNUNET_SYSERR; + return; + } + /* nothing to do, sleep for a minute and try again */ + task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_MINUTES, + &run_aggregation, + global_ret); + return; + } + /* Now try to find other deposits to aggregate */ + ret = db_plugin->iterate_matching_deposits (db_plugin->cls, + session, + &au->h_wire, + &au->merchant_pub, + &aggregate_cb, + au, + aggregation_limit); + if ( (GNUNET_SYSERR == ret) || + (GNUNET_YES == au->failed) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to execute deposit iteration!\n"); + GNUNET_free_non_null (au->additional_rows); + GNUNET_free (au); + db_plugin->rollback (db_plugin->cls, + session); + *global_ret = GNUNET_SYSERR; + return; + } + /* Round to the unit supported by the wire transfer method */ + GNUNET_assert (GNUNET_SYSERR != + wire_plugin->amount_round (wire_plugin->cls, + &au->total_amount)); + /* Check if after rounding down, we still have an amount to transfer */ + if ( (0 == au->total_amount.value) && + (0 == au->total_amount.fraction) ) + { + /* Rollback ongoing transaction, as we will not use the respective + WTID and thus need to remove the tracking data */ + db_plugin->rollback (db_plugin->cls, + session); + /* Start another transaction to mark all* of the selected deposits + *as minor! */ + if (GNUNET_OK != + db_plugin->start (db_plugin->cls, + session)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to start database transaction!\n"); + *global_ret = GNUNET_SYSERR; + GNUNET_free_non_null (au->additional_rows); + GNUNET_free (au); + return; + } + /* Mark transactions by row_id as minor */ + ret = GNUNET_OK; + if (GNUNET_OK != + db_plugin->mark_deposit_tiny (db_plugin->cls, + session, + au->row_id)) + ret = GNUNET_SYSERR; + else + for (i=0;i<au->rows_offset;i++) + if (GNUNET_OK != + db_plugin->mark_deposit_tiny (db_plugin->cls, + session, + au->additional_rows[i])) + ret = GNUNET_SYSERR; + /* commit */ + if (GNUNET_OK != + db_plugin->commit (db_plugin->cls, + session)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to commit database transaction!\n"); + } + GNUNET_free_non_null (au->additional_rows); + GNUNET_free (au); + /* start again */ + task = GNUNET_SCHEDULER_add_now (&run_aggregation, + global_ret); + return; + } + au->global_ret = global_ret; + au->ph = wire_plugin->prepare_wire_transfer (wire_plugin->cls, + au->wire, + &au->total_amount, + &au->wtid, + &prepare_cb, + au); + /* FIXME: currently we have no clean-up plan on + shutdown to call prepare_wire_transfer_cancel! + Maybe make 'au' global? */ + if (NULL == au->ph) + { + GNUNET_break (0); /* why? how to best recover? */ + db_plugin->rollback (db_plugin->cls, + session); + GNUNET_free_non_null (au->additional_rows); + GNUNET_free (au); + /* start again */ + task = GNUNET_SCHEDULER_add_now (&run_aggregation, + global_ret); + return; + } + /* otherwise we continue with #prepare_cb(), see below */ +} + + +/** + * Execute the wire transfers that we have committed to + * do. + * + * @param cls pointer to an `int` which we will return from main() + * @param tc scheduler context + */ +static void +run_transfers (void *cls, + const struct GNUNET_SCHEDULER_TaskContext *tc); + + +/** + * Function to be called with the prepared transfer data. + * + * @param cls closure with the `struct AggregationUnit` + * @param buf transaction data to persist, NULL on error + * @param buf_size number of bytes in @a buf, 0 on error + */ +static void +prepare_cb (void *cls, + const char *buf, + size_t buf_size) +{ + struct AggregationUnit *au = cls; + int *global_ret = au->global_ret; + struct TALER_MINTDB_Session *session = au->session; + + GNUNET_free_non_null (au->additional_rows); + GNUNET_free (au); + if (NULL == buf) + { + GNUNET_break (0); /* why? how to best recover? */ + db_plugin->rollback (db_plugin->cls, + session); + /* start again */ + task = GNUNET_SCHEDULER_add_now (&run_aggregation, + global_ret); + return; + } + + /* Commit our intention to execute the wire transfer! */ + if (GNUNET_OK != + db_plugin->wire_prepare_data_insert (db_plugin->cls, + session, + mint_wireformat, + buf, + buf_size)) + { + GNUNET_break (0); /* why? how to best recover? */ + db_plugin->rollback (db_plugin->cls, + session); + /* start again */ + task = GNUNET_SCHEDULER_add_now (&run_aggregation, + global_ret); + return; + } + + /* Now we can finally commit the overall transaction, as we are + again consistent if all of this passes. */ + if (GNUNET_OK != + db_plugin->commit (db_plugin->cls, + session)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Failed to commit database transaction!\n"); + /* try again */ + task = GNUNET_SCHEDULER_add_now (&run_aggregation, + global_ret); + return; + } + + /* run alternative task: actually do wire transfer! */ + task = GNUNET_SCHEDULER_add_now (&run_transfers, + &global_ret); +} + + +/** + * Data we keep to #run_transfers(). + */ +struct WirePrepareData +{ + + /** + * Database session for all of our transactions. + */ + struct TALER_MINTDB_Session *session; + + /** + * Wire execution handle. + */ + struct TALER_WIRE_ExecuteHandle *eh; + + /** + * Pointer to global return value. Closure for #run(). + */ + int *global_ret; + + + /** + * Row ID of the transfer. + */ + unsigned long long row_id; + +}; + + +/** + * Function called with the result from the execute step. + * + * @param cls closure with the `struct WirePrepareData` + * @param success #GNUNET_OK on success, #GNUNET_SYSERR on failure + * @param emsg NULL on success, otherwise an error message + */ +static void +wire_confirm_cb (void *cls, + int success, + const char *emsg) +{ + struct WirePrepareData *wpd = cls; + int *global_ret = wpd->global_ret; + struct TALER_MINTDB_Session *session = wpd->session; + + wpd->eh = NULL; + if (GNUNET_SYSERR == success) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Wire transaction failed: %s\n", + emsg); + db_plugin->rollback (db_plugin->cls, + session); + *global_ret = GNUNET_SYSERR; + GNUNET_free (wpd); + return; + } + if (GNUNET_OK != + db_plugin->wire_prepare_data_mark_finished (db_plugin->cls, + session, + wpd->row_id)) + { + GNUNET_break (0); /* why!? */ + db_plugin->rollback (db_plugin->cls, + session); + *global_ret = GNUNET_SYSERR; + GNUNET_free (wpd); + return; + } + GNUNET_free (wpd); + if (GNUNET_OK != + db_plugin->commit (db_plugin->cls, + session)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Failed to commit database transaction!\n"); + /* try again */ + task = GNUNET_SCHEDULER_add_now (&run_aggregation, + global_ret); + return; + } + /* continue with #run_transfers(), just to guard + against the unlikely case that there are more. */ + task = GNUNET_SCHEDULER_add_now (&run_transfers, + &global_ret); + +} + + +/** + * Callback with data about a prepared transaction. + * + * @param cls closure with the `struct WirePrepareData` + * @param rowid row identifier used to mark prepared transaction as done + * @param buf transaction data that was persisted, NULL on error + * @param buf_size number of bytes in @a buf, 0 on error + */ +static void +wire_prepare_cb (void *cls, + unsigned long long rowid, + const char *buf, + size_t buf_size) +{ + struct WirePrepareData *wpd = cls; + int *global_ret = wpd->global_ret; + + wpd->row_id = rowid; + wpd->eh = wire_plugin->execute_wire_transfer (wire_plugin->cls, + buf, + buf_size, + &wire_confirm_cb, + wpd); + /* FIXME: currently we have no clean-up plan on + shutdown to call execute_wire_transfer_cancel! + Maybe make 'wpd' global? */ + if (NULL == wpd->eh) + { + GNUNET_break (0); /* why? how to best recover? */ + db_plugin->rollback (db_plugin->cls, + wpd->session); + *global_ret = GNUNET_SYSERR; + GNUNET_free (wpd); + return; + } +} + + +/** + * Execute the wire transfers that we have committed to + * do. + * + * @param cls pointer to an `int` which we will return from main() + * @param tc scheduler context + */ +static void +run_transfers (void *cls, + const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + int *global_ret = cls; + int ret; + struct WirePrepareData *wpd; + struct TALER_MINTDB_Session *session; + + if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN)) + return; + if (NULL == (session = db_plugin->get_session (db_plugin->cls, + GNUNET_NO))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to obtain database session!\n"); + *global_ret = GNUNET_SYSERR; + return; + } + if (GNUNET_OK != + db_plugin->start (db_plugin->cls, + session)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to start database transaction!\n"); + *global_ret = GNUNET_SYSERR; + return; + } + wpd = GNUNET_new (struct WirePrepareData); + wpd->session = session; + wpd->global_ret = global_ret; + ret = db_plugin->wire_prepare_data_get (db_plugin->cls, + session, + mint_wireformat, + &wire_prepare_cb, + wpd); + if (GNUNET_SYSERR == ret) + { + GNUNET_break (0); /* why? how to best recover? */ + db_plugin->rollback (db_plugin->cls, + session); + *global_ret = GNUNET_SYSERR; + GNUNET_free (wpd); + return; + } + if (GNUNET_NO == ret) + { + /* no more prepared wire transfers, go back to aggregation! */ + db_plugin->rollback (db_plugin->cls, + session); + task = GNUNET_SCHEDULER_add_now (&run_aggregation, + global_ret); + GNUNET_free (wpd); + return; + } + /* otherwise, continues in #wire_prepare_cb() */ +} + + +/** + * The main function of the taler-mint-httpd server ("the mint"). + * + * @param argc number of arguments from the command line + * @param argv command line arguments + * @return 0 ok, 1 on error + */ +int +main (int argc, + char *const *argv) +{ + static const struct GNUNET_GETOPT_CommandLineOption options[] = { + {'d', "mint-dir", "DIR", + "mint directory with configuration and keys for operating the mint", 1, + &GNUNET_GETOPT_set_filename, &mint_directory}, + {'f', "format", "WIREFORMAT", + "wireformat to use, overrides WIREFORMAT option in [mint] section", 1, + &GNUNET_GETOPT_set_filename, &mint_wireformat}, + TALER_GETOPT_OPTION_HELP ("background process that aggregates and executes wire transfers to merchants"), + GNUNET_GETOPT_OPTION_VERSION (VERSION "-" VCS_VERSION), + GNUNET_GETOPT_OPTION_END + }; + int ret = GNUNET_OK; + + GNUNET_assert (GNUNET_OK == + GNUNET_log_setup ("taler-mint-aggregator", + "INFO", + NULL)); + if (0 >= + GNUNET_GETOPT_run ("taler-mint-aggregator", + options, + argc, argv)) + return 1; + if (NULL == mint_directory) + { + fprintf (stderr, + "Mint directory not specified\n"); + return 1; + } + if (GNUNET_OK != + mint_serve_process_config (mint_directory)) + { + return 1; + } + + GNUNET_SCHEDULER_run (&run_transfers, &ret); + + TALER_MINTDB_plugin_unload (db_plugin); + TALER_WIRE_plugin_unload (wire_plugin); + return (GNUNET_SYSERR == ret) ? 1 : 0; +} + +/* end of taler-mint-aggregator.c */ diff --git a/src/mint/taler-mint-httpd.c b/src/mint/taler-mint-httpd.c index 26a440c95..5d6aa0589 100644 --- a/src/mint/taler-mint-httpd.c +++ b/src/mint/taler-mint-httpd.c @@ -39,6 +39,7 @@ #include "taler-mint-httpd_test.h" #endif #include "taler_mintdb_plugin.h" +#include "taler-mint-httpd_validation.h" /** * Which currency is used by this mint? @@ -67,13 +68,6 @@ struct GNUNET_CONFIGURATION_Handle *cfg; struct GNUNET_CRYPTO_EddsaPublicKey TMH_master_public_key; /** - * In which format does this MINT expect wiring instructions? - * NULL-terminated array of 0-terminated wire format types, - * suitable for passing to #TALER_json_validate_wireformat(). - */ -const char **TMH_expected_wire_formats; - -/** * Our DB plugin. */ struct TALER_MINTDB_Plugin *TMH_plugin; @@ -384,9 +378,6 @@ mint_serve_process_config (const char *mint_directory) { unsigned long long port; char *TMH_master_public_key_str; - char *wireformats; - const char *token; - unsigned int len; cfg = TALER_config_load (mint_directory); if (NULL == cfg) @@ -414,35 +405,9 @@ mint_serve_process_config (const char *mint_directory) (unsigned int) TALER_CURRENCY_LEN); return GNUNET_SYSERR; } - /* Find out list of supported wire formats */ if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (cfg, - "mint", - "wireformat", - &wireformats)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "mint", - "wireformat"); + TMH_VALIDATION_init (cfg)) return GNUNET_SYSERR; - } - /* build NULL-terminated array of TMH_expected_wire_formats */ - TMH_expected_wire_formats = GNUNET_new_array (1, - const char *); - len = 1; - for (token = strtok (wireformats, - " "); - NULL != token; - token = strtok (NULL, - " ")) - { - /* Grow by 1, appending NULL-terminator */ - GNUNET_array_append (TMH_expected_wire_formats, - len, - NULL); - TMH_expected_wire_formats[len - 2] = GNUNET_strdup (token); - } - GNUNET_free (wireformats); if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, @@ -453,6 +418,7 @@ mint_serve_process_config (const char *mint_directory) GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "mint", "master_public_key"); + TMH_VALIDATION_done (); return GNUNET_SYSERR; } if (GNUNET_OK != @@ -463,6 +429,7 @@ mint_serve_process_config (const char *mint_directory) fprintf (stderr, "Invalid master public key given in mint configuration."); GNUNET_free (TMH_master_public_key_str); + TMH_VALIDATION_done (); return GNUNET_SYSERR; } GNUNET_free (TMH_master_public_key_str); @@ -472,6 +439,7 @@ mint_serve_process_config (const char *mint_directory) { fprintf (stderr, "Failed to initialize DB subsystem\n"); + TMH_VALIDATION_done (); return GNUNET_SYSERR; } if (GNUNET_YES == @@ -496,6 +464,7 @@ mint_serve_process_config (const char *mint_directory) "mint", "port", "port number required"); + TMH_VALIDATION_done (); return GNUNET_SYSERR; } @@ -505,6 +474,7 @@ mint_serve_process_config (const char *mint_directory) fprintf (stderr, "Invalid configuration (value out of range): %llu is not a valid port\n", port); + TMH_VALIDATION_done (); return GNUNET_SYSERR; } serve_port = (uint16_t) port; @@ -772,6 +742,7 @@ main (int argc, session); } TALER_MINTDB_plugin_unload (TMH_plugin); + TMH_VALIDATION_done (); return (GNUNET_SYSERR == ret) ? 1 : 0; } diff --git a/src/mint/taler-mint-httpd.h b/src/mint/taler-mint-httpd.h index e83dd66f2..d45325aa6 100644 --- a/src/mint/taler-mint-httpd.h +++ b/src/mint/taler-mint-httpd.h @@ -27,6 +27,7 @@ #include <microhttpd.h> + /** * Which currency is used by this mint? */ @@ -53,13 +54,6 @@ extern int TMH_test_mode; extern char *TMH_mint_directory; /** - * In which formats does this MINT expect wiring instructions? - * NULL-terminated array of 0-terminated wire format types, - * suitable for passing to #TALER_json_validate_wireformat(). - */ -extern const char **TMH_expected_wire_formats; - -/** * Master public key (according to the * configuration in the mint directory). */ diff --git a/src/mint/taler-mint-httpd_admin.c b/src/mint/taler-mint-httpd_admin.c index 99f256418..e6f186f0b 100644 --- a/src/mint/taler-mint-httpd_admin.c +++ b/src/mint/taler-mint-httpd_admin.c @@ -24,6 +24,7 @@ #include "taler-mint-httpd_admin.h" #include "taler-mint-httpd_parsing.h" #include "taler-mint-httpd_responses.h" +#include "taler-mint-httpd_validation.h" /** @@ -144,8 +145,7 @@ TMH_ADMIN_handler_admin_add_incoming (struct TMH_RequestHandler *rh, return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; } if (GNUNET_YES != - TALER_json_validate_wireformat (TMH_expected_wire_formats, - wire)) + TMH_json_validate_wireformat (wire)) { GNUNET_break_op (0); TMH_PARSE_release_data (spec); diff --git a/src/mint/taler-mint-httpd_db.c b/src/mint/taler-mint-httpd_db.c index 2b4ade595..b93ead3af 100644 --- a/src/mint/taler-mint-httpd_db.c +++ b/src/mint/taler-mint-httpd_db.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) + Copyright (C) 2014, 2015, 2016 GNUnet e.V. 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 @@ -1552,9 +1552,214 @@ TMH_DB_execute_admin_add_incoming (struct MHD_Connection *connection, /** + * Closure for #handle_transaction_data. + */ +struct WtidTransactionContext +{ + + /** + * Total amount of the wire transfer, as calculated by + * summing up the individual amounts. To be rounded down + * to calculate the real transfer amount at the end. + * Only valid if @e is_valid is #GNUNET_YES. + */ + struct TALER_Amount total; + + /** + * Value we find in the DB for the @e total; only valid if @e is_valid + * is #GNUNET_YES. + */ + struct TALER_Amount db_transaction_value; + + /** + * Public key of the merchant, only valid if @e is_valid + * is #GNUNET_YES. + */ + struct TALER_MerchantPublicKeyP merchant_pub; + + /** + * Hash of the wire details of the merchant (identical for all + * deposits), only valid if @e is_valid is #GNUNET_YES. + */ + struct GNUNET_HashCode h_wire; + + /** + * JSON array with details about the individual deposits. + */ + json_t *deposits; + + /** + * Initially #GNUNET_NO, if we found no deposits so far. Set to + * #GNUNET_YES if we got transaction data, and the database replies + * remained consistent with respect to @e merchant_pub and @e h_wire + * (as they should). Set to #GNUNET_SYSERR if we encountered an + * internal error. + */ + int is_valid; + +}; + + +/** + * Function called with the results of the lookup of the + * transaction data for the given wire transfer identifier. + * + * @param cls our context for transmission + * @param merchant_pub public key of the merchant (should be same for all callbacks with the same @e cls) + * @param h_wire hash of wire transfer details of the merchant (should be same for all callbacks with the same @e cls) + * @param h_contract which contract was this payment about + * @param transaction_id merchant's transaction ID for the payment + * @param coin_pub which public key was this payment about + * @param deposit_value amount contributed by this coin in total + * @param deposit_fee deposit fee charged by mint for this coin + * @param transaction_value total value of the wire transaction + */ +static void +handle_transaction_data (void *cls, + const struct TALER_MerchantPublicKeyP *merchant_pub, + const struct GNUNET_HashCode *h_wire, + const struct GNUNET_HashCode *h_contract, + uint64_t transaction_id, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_Amount *deposit_value, + const struct TALER_Amount *deposit_fee, + const struct TALER_Amount *transaction_value) +{ + struct WtidTransactionContext *ctx = cls; + struct TALER_Amount delta; + + if (GNUNET_SYSERR == ctx->is_valid) + return; + if (GNUNET_NO == ctx->is_valid) + { + ctx->merchant_pub = *merchant_pub; + ctx->h_wire = *h_wire; + ctx->db_transaction_value = *transaction_value; + ctx->is_valid = GNUNET_YES; + if (GNUNET_OK != + TALER_amount_subtract (&ctx->total, + deposit_value, + deposit_fee)) + { + GNUNET_break (0); + ctx->is_valid = GNUNET_SYSERR; + return; + } + } + else + { + if ( (0 != memcmp (&ctx->merchant_pub, + merchant_pub, + sizeof (struct TALER_MerchantPublicKeyP))) || + (0 != memcmp (&ctx->h_wire, + h_wire, + sizeof (struct GNUNET_HashCode))) || + (0 != TALER_amount_cmp (transaction_value, + &ctx->db_transaction_value)) ) + { + GNUNET_break (0); + ctx->is_valid = GNUNET_SYSERR; + return; + } + if (GNUNET_OK != + TALER_amount_subtract (&delta, + deposit_value, + deposit_fee)) + { + GNUNET_break (0); + ctx->is_valid = GNUNET_SYSERR; + return; + } + if (GNUNET_OK != + TALER_amount_add (&ctx->total, + &ctx->total, + &delta)) + { + GNUNET_break (0); + ctx->is_valid = GNUNET_SYSERR; + return; + } + } + /* NOTE: We usually keep JSON stuff out of the _DB file, and this + is also ugly if we ever add signatures over this data. (#4135) */ + json_array_append (ctx->deposits, + json_pack ("{s:o, s:o, s:o, s:I, s:o}", + "deposit_value", TALER_json_from_amount (deposit_value), + "deposit_fee", TALER_json_from_amount (deposit_fee), + "H_contract", TALER_json_from_data (h_contract, + sizeof (struct GNUNET_HashCode)), + "transaction_id", (json_int_t) transaction_id, + "coin_pub", TALER_json_from_data (coin_pub, + sizeof (struct TALER_CoinSpendPublicKeyP)))); +} + + +/** + * Execute a "/wire/deposits". Returns the transaction information + * associated with the given wire transfer identifier. + * + * @param connection the MHD connection to handle + * @param wtid wire transfer identifier to resolve + * @return MHD result code + */ +int +TMH_DB_execute_wire_deposits (struct MHD_Connection *connection, + const struct TALER_WireTransferIdentifierRawP *wtid) +{ + int ret; + struct WtidTransactionContext ctx; + struct TALER_MINTDB_Session *session; + + if (NULL == (session = TMH_plugin->get_session (TMH_plugin->cls, + TMH_test_mode))) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + ctx.is_valid = GNUNET_NO; + ctx.deposits = json_array (); + ret = TMH_plugin->lookup_wire_transfer (TMH_plugin->cls, + session, + wtid, + &handle_transaction_data, + &ctx); + if (GNUNET_SYSERR == ret) + { + GNUNET_break (0); + json_decref (ctx.deposits); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + if (GNUNET_SYSERR == ctx.is_valid) + { + GNUNET_break (0); + json_decref (ctx.deposits); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + if (GNUNET_NO == ctx.is_valid) + { + json_decref (ctx.deposits); + return TMH_RESPONSE_reply_arg_unknown (connection, + "wtid"); + } + if (0 != TALER_amount_cmp (&ctx.total, + &ctx.db_transaction_value)) + { + /* FIXME: this CAN actually differ, due to rounding + down. But we should still check that the values + do match after rounding 'total' down! */ + } + return TMH_RESPONSE_reply_wire_deposit_details (connection, + &ctx.db_transaction_value, + &ctx.merchant_pub, + &ctx.h_wire, + ctx.deposits); +} + + +/** * Closure for #handle_wtid_data. */ -struct DepositWtidContext +struct DepositWtidContext { /** @@ -1563,6 +1768,26 @@ struct DepositWtidContext struct MHD_Connection *connection; /** + * Hash of the contract we are looking up. + */ + struct GNUNET_HashCode h_contract; + + /** + * Hash of the wire transfer details we are looking up. + */ + struct GNUNET_HashCode h_wire; + + /** + * Public key we are looking up. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + + /** + * Transaction ID we are looking up. + */ + uint64_t transaction_id; + + /** * MHD result code to return. */ int res; @@ -1572,10 +1797,13 @@ struct DepositWtidContext /** * Function called with the results of the lookup of the * wire transfer identifier information. - * + * * @param cls our context for transmission - * @param wtid base32-encoded wire transfer identifier, NULL + * @param wtid raw wire transfer identifier, NULL * if the transaction was not yet done + * @param coin_contribution how much did the coin we asked about + * contribute to the total transfer value? (deposit value including fee) + * @param coin_fee how much did the mint charge for the deposit fee * @param execution_time when was the transaction done, or * when we expect it to be done (if @a wtid was NULL); * #GNUNET_TIME_UNIT_FOREVER_ABS if the /deposit is unknown @@ -1583,23 +1811,41 @@ struct DepositWtidContext */ static void handle_wtid_data (void *cls, - const char *wtid, + const struct TALER_WireTransferIdentifierRawP *wtid, + const struct TALER_Amount *coin_contribution, + const struct TALER_Amount *coin_fee, struct GNUNET_TIME_Absolute execution_time) { struct DepositWtidContext *ctx = cls; + struct TALER_Amount coin_delta; if (NULL == wtid) { - if (GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us == - execution_time.abs_value_us) - ctx->res = TMH_RESPONSE_reply_deposit_unknown (ctx->connection); - else - ctx->res = TMH_RESPONSE_reply_deposit_pending (ctx->connection); + ctx->res = TMH_RESPONSE_reply_deposit_pending (ctx->connection, + execution_time); } else { - ctx->res = TMH_RESPONSE_reply_deposit_wtid (ctx->connection); - } + if (GNUNET_SYSERR == + TALER_amount_subtract (&coin_delta, + coin_contribution, + coin_fee)) + { + GNUNET_break (0); + ctx->res = TMH_RESPONSE_reply_internal_db_error (ctx->connection); + } + else + { + ctx->res = TMH_RESPONSE_reply_deposit_wtid (ctx->connection, + &ctx->h_contract, + &ctx->h_wire, + &ctx->coin_pub, + &coin_delta, + ctx->transaction_id, + wtid, + execution_time); + } + } } @@ -1625,21 +1871,46 @@ TMH_DB_execute_deposit_wtid (struct MHD_Connection *connection, { int ret; struct DepositWtidContext ctx; + struct TALER_MINTDB_Session *session; + if (NULL == (session = TMH_plugin->get_session (TMH_plugin->cls, + TMH_test_mode))) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_db_error (connection); + } ctx.connection = connection; + ctx.h_contract = *h_contract; + ctx.h_wire = *h_wire; + ctx.coin_pub = *coin_pub; + ctx.transaction_id = transaction_id; + ctx.res = GNUNET_SYSERR; ret = TMH_plugin->wire_lookup_deposit_wtid (TMH_plugin->cls, + session, h_contract, h_wire, coin_pub, merchant_pub, transaction_id, &handle_wtid_data, - connection); + &ctx); if (GNUNET_SYSERR == ret) { GNUNET_break (0); + GNUNET_break (GNUNET_SYSERR == ctx.res); return TMH_RESPONSE_reply_internal_db_error (connection); } + if (GNUNET_NO == ret) + { + GNUNET_break (GNUNET_SYSERR == ctx.res); + return TMH_RESPONSE_reply_deposit_unknown (connection); + } + if (GNUNET_SYSERR == ctx.res) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_error (connection, + "bug resolving deposit wtid"); + } return ctx.res; } diff --git a/src/mint/taler-mint-httpd_db.h b/src/mint/taler-mint-httpd_db.h index d9adba2d9..0327bef2a 100644 --- a/src/mint/taler-mint-httpd_db.h +++ b/src/mint/taler-mint-httpd_db.h @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) + Copyright (C) 2014, 2015 GNUnet e.V. 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 @@ -193,6 +193,19 @@ TMH_DB_execute_admin_add_incoming (struct MHD_Connection *connection, /** + * Execute a "/wire/deposits". Returns the transaction information + * associated with the given wire transfer identifier. + * + * @param connection the MHD connection to handle + * @param wtid wire transfer identifier to resolve + * @return MHD result code + */ +int +TMH_DB_execute_wire_deposits (struct MHD_Connection *connection, + const struct TALER_WireTransferIdentifierRawP *wtid); + + +/** * Execute a "/deposit/wtid". Returns the transfer information * associated with the given deposit. * @@ -212,5 +225,6 @@ TMH_DB_execute_deposit_wtid (struct MHD_Connection *connection, const struct TALER_MerchantPublicKeyP *merchant_pub, uint64_t transaction_id); + #endif /* TALER_MINT_HTTPD_DB_H */ diff --git a/src/mint/taler-mint-httpd_deposit.c b/src/mint/taler-mint-httpd_deposit.c index 0aef4775c..40c5a4db7 100644 --- a/src/mint/taler-mint-httpd_deposit.c +++ b/src/mint/taler-mint-httpd_deposit.c @@ -34,6 +34,7 @@ #include "taler-mint-httpd_deposit.h" #include "taler-mint-httpd_responses.h" #include "taler-mint-httpd_keystate.h" +#include "taler-mint-httpd_validation.h" /** @@ -162,8 +163,7 @@ parse_and_handle_deposit_request (struct MHD_Connection *connection, return MHD_YES; /* failure */ if (GNUNET_YES != - TALER_json_validate_wireformat (TMH_expected_wire_formats, - wire)) + TMH_json_validate_wireformat (wire)) { TMH_PARSE_release_data (spec); return TMH_RESPONSE_reply_arg_unknown (connection, diff --git a/src/mint/taler-mint-httpd_responses.c b/src/mint/taler-mint-httpd_responses.c index f3498b469..2ebd0d331 100644 --- a/src/mint/taler-mint-httpd_responses.c +++ b/src/mint/taler-mint-httpd_responses.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014, 2015 GNUnet e.V. + Copyright (C) 2014, 2015, 2016 GNUnet e.V. TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software @@ -1056,15 +1056,15 @@ TMH_RESPONSE_reply_refresh_link_success (struct MHD_Connection *connection, * 404 reply. * * @param connection connection to the client - * @param * @return MHD result code */ int -TMH_RESPONSE_reply_deposit_unknown (struct MHD_Connection *connection, - ...) +TMH_RESPONSE_reply_deposit_unknown (struct MHD_Connection *connection) { - GNUNET_break (0); // FIXME: not implemented - return MHD_NO; + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_NOT_FOUND, + "{s:s}", + "error", "Deposit unknown"); } @@ -1073,15 +1073,17 @@ TMH_RESPONSE_reply_deposit_unknown (struct MHD_Connection *connection, * we did not execute the deposit yet. Generate a 202 reply. * * @param connection connection to the client - * @param + * @param planned_exec_time planned execution time * @return MHD result code */ int TMH_RESPONSE_reply_deposit_pending (struct MHD_Connection *connection, - ...) + struct GNUNET_TIME_Absolute planned_exec_time) { - GNUNET_break (0); // FIXME: not implemented - return MHD_NO; + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_ACCEPTED, + "{s:o}", + "execution_time", TALER_json_from_abs (planned_exec_time)); } @@ -1090,15 +1092,86 @@ TMH_RESPONSE_reply_deposit_pending (struct MHD_Connection *connection, * them. Generates the 200 reply. * * @param connection connection to the client - * @param + * @param h_contract hash of the contract + * @param h_wire hash of wire account details + * @param coin_pub public key of the coin + * @param coin_contribution how much did the coin we asked about + * contribute to the total transfer value? (deposit value minus fee) + * @param transaction_id merchant transaction identifier + * @param wtid raw wire transfer identifier + * @param exec_time execution time of the wire transfer * @return MHD result code */ int TMH_RESPONSE_reply_deposit_wtid (struct MHD_Connection *connection, - ...) + const struct GNUNET_HashCode *h_contract, + const struct GNUNET_HashCode *h_wire, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_Amount *coin_contribution, + uint64_t transaction_id, + const struct TALER_WireTransferIdentifierRawP *wtid, + struct GNUNET_TIME_Absolute exec_time) +{ + struct TALER_ConfirmWirePS cw; + struct TALER_MintPublicKeyP pub; + struct TALER_MintSignatureP sig; + + cw.purpose.purpose = htonl (TALER_SIGNATURE_MINT_CONFIRM_WIRE); + cw.purpose.size = htonl (sizeof (struct TALER_ConfirmWirePS)); + cw.h_wire = *h_wire; + cw.h_contract = *h_contract; + cw.wtid = *wtid; + cw.coin_pub = *coin_pub; + cw.transaction_id = GNUNET_htonll (transaction_id); + cw.execution_time = GNUNET_TIME_absolute_hton (exec_time); + TALER_amount_hton (&cw.coin_contribution, + coin_contribution); + TMH_KS_sign (&cw.purpose, + &pub, + &sig); + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_OK, + "{s:o, s:o, s:o, s:o, s:o, s:o}", + "wtid", TALER_json_from_data (wtid, + sizeof (*wtid)), + "execution_time", TALER_json_from_abs (exec_time), + "coin_contribution", TALER_json_from_amount (coin_contribution), + "mint_sig", TALER_json_from_data (&sig, + sizeof (sig)), + "mint_pub", TALER_json_from_data (&pub, + sizeof (pub))); +} + + +/** + * A merchant asked for transaction details about a wire transfer. + * Provide them. Generates the 200 reply. + * + * @param connection connection to the client + * @param total total amount that was transferred + * @param merchant_pub public key of the merchant + * @param h_wire destination account + * @param deposits details about the combined deposits + * @return MHD result code + */ +int +TMH_RESPONSE_reply_wire_deposit_details (struct MHD_Connection *connection, + const struct TALER_Amount *total, + const struct TALER_MerchantPublicKeyP *merchant_pub, + const struct GNUNET_HashCode *h_wire, + json_t *deposits) { - GNUNET_break (0); // FIXME: not implemented - return MHD_NO; + /* FIXME: #4135: signing not implemented here */ + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_OK, + "{s:o, s:o, s:o, s:o}", + "total", TALER_json_from_amount (total), + "merchant_pub", TALER_json_from_data (merchant_pub, + sizeof (struct TALER_MerchantPublicKeyP)), + "h_wire", TALER_json_from_data (h_wire, + sizeof (struct GNUNET_HashCode)), + "deposits", deposits); } + /* end of taler-mint-httpd_responses.c */ diff --git a/src/mint/taler-mint-httpd_responses.h b/src/mint/taler-mint-httpd_responses.h index 5d1523b4b..a0396c8a1 100644 --- a/src/mint/taler-mint-httpd_responses.h +++ b/src/mint/taler-mint-httpd_responses.h @@ -253,12 +253,10 @@ TMH_RESPONSE_reply_deposit_insufficient_funds (struct MHD_Connection *connection * 404 reply. * * @param connection connection to the client - * @param * @return MHD result code */ int -TMH_RESPONSE_reply_deposit_unknown (struct MHD_Connection *connection, - ...); +TMH_RESPONSE_reply_deposit_unknown (struct MHD_Connection *connection); /** @@ -266,12 +264,12 @@ TMH_RESPONSE_reply_deposit_unknown (struct MHD_Connection *connection, * we did not execute the deposit yet. Generate a 202 reply. * * @param connection connection to the client - * @param + * @param planned_exec_time planned execution time * @return MHD result code */ int TMH_RESPONSE_reply_deposit_pending (struct MHD_Connection *connection, - ...); + struct GNUNET_TIME_Absolute planned_exec_time); /** @@ -279,12 +277,43 @@ TMH_RESPONSE_reply_deposit_pending (struct MHD_Connection *connection, * them. Generates the 200 reply. * * @param connection connection to the client - * @param + * @param h_contract hash of the contract + * @param h_wire hash of wire account details + * @param coin_pub public key of the coin + * @param coin_contribution contribution of this coin to the total amount transferred + * @param transaction_id merchant transaction identifier + * @param wtid raw wire transfer identifier + * @param exec_time execution time of the wire transfer * @return MHD result code */ int TMH_RESPONSE_reply_deposit_wtid (struct MHD_Connection *connection, - ...); + const struct GNUNET_HashCode *h_contract, + const struct GNUNET_HashCode *h_wire, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_Amount *coin_contribution, + uint64_t transaction_id, + const struct TALER_WireTransferIdentifierRawP *wtid, + struct GNUNET_TIME_Absolute exec_time); + + +/** + * A merchant asked for transaction details about a wire transfer. + * Provide them. Generates the 200 reply. + * + * @param connection connection to the client + * @param total total amount that was transferred + * @param merchant_pub public key of the merchant + * @param h_wire destination account + * @param deposits details about the combined deposits + * @return MHD result code + */ +int +TMH_RESPONSE_reply_wire_deposit_details (struct MHD_Connection *connection, + const struct TALER_Amount *total, + const struct TALER_MerchantPublicKeyP *merchant_pub, + const struct GNUNET_HashCode *h_wire, + json_t *deposits); /** diff --git a/src/mint/taler-mint-httpd_tracking.c b/src/mint/taler-mint-httpd_tracking.c index 59d029429..a6b41cf86 100644 --- a/src/mint/taler-mint-httpd_tracking.c +++ b/src/mint/taler-mint-httpd_tracking.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014, 2015 GNUnet e.V. + Copyright (C) 2014, 2015, 2016 GNUnet e.V. TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software @@ -46,8 +46,19 @@ TMH_TRACKING_handler_wire_deposits (struct TMH_RequestHandler *rh, const char *upload_data, size_t *upload_data_size) { - GNUNET_break (0); // not implemented - return MHD_NO; + struct TALER_WireTransferIdentifierRawP wtid; + int res; + + res = TMH_PARSE_mhd_request_arg_data (connection, + "wtid", + &wtid, + sizeof (struct TALER_WireTransferIdentifierRawP)); + if (GNUNET_SYSERR == res) + return MHD_NO; /* internal error */ + if (GNUNET_NO == res) + return MHD_YES; /* parse error */ + return TMH_DB_execute_wire_deposits (connection, + &wtid); } @@ -57,7 +68,7 @@ TMH_TRACKING_handler_wire_deposits (struct TMH_RequestHandler *rh, * * @param connection the MHD connection to handle * @param tps signed request to execute - * @param merchant_pub public key from the merchant + * @param merchant_pub public key from the merchant * @param merchant_sig signature from the merchant (to be checked) * @param transaction_id transaction ID (in host byte order) * @return MHD result code @@ -110,13 +121,12 @@ TMH_TRACKING_handler_deposit_wtid (struct TMH_RequestHandler *rh, struct TALER_DepositTrackPS tps; uint64_t transaction_id; struct TALER_MerchantSignatureP merchant_sig; - struct TALER_MerchantPublicKeyP merchant_pub; struct TMH_PARSE_FieldSpecification spec[] = { TMH_PARSE_member_fixed ("H_wire", &tps.h_wire), TMH_PARSE_member_fixed ("H_contract", &tps.h_contract), TMH_PARSE_member_fixed ("coin_pub", &tps.coin_pub), TMH_PARSE_member_uint64 ("transaction_id", &transaction_id), - TMH_PARSE_member_fixed ("merchant_pub", &merchant_pub), + TMH_PARSE_member_fixed ("merchant_pub", &tps.merchant), TMH_PARSE_member_fixed ("merchant_sig", &merchant_sig), TMH_PARSE_MEMBER_END }; @@ -143,7 +153,7 @@ TMH_TRACKING_handler_deposit_wtid (struct TMH_RequestHandler *rh, tps.transaction_id = GNUNET_htonll (transaction_id); res = check_and_handle_deposit_wtid_request (connection, &tps, - &merchant_pub, + &tps.merchant, &merchant_sig, transaction_id); TMH_PARSE_release_data (spec); diff --git a/src/mint/taler-mint-httpd_validation.c b/src/mint/taler-mint-httpd_validation.c new file mode 100644 index 000000000..461e88759 --- /dev/null +++ b/src/mint/taler-mint-httpd_validation.c @@ -0,0 +1,231 @@ +/* + This file is part of TALER + Copyright (C) 2016 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file taler-mint-httpd_validation.c + * @brief helpers for calling the wire plugins to validate addresses + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include "taler-mint-httpd_validation.h" +#include "taler_wire_plugin.h" + + +/** + * Information we keep for each plugin. + */ +struct Plugin +{ + + /** + * We keep plugins in a DLL. + */ + struct Plugin *next; + + /** + * We keep plugins in a DLL. + */ + struct Plugin *prev; + + /** + * Type of the wireformat. + */ + char *type; + + /** + * Pointer to the plugin. + */ + struct TALER_WIRE_Plugin *plugin; + +}; + +/** + * Head of DLL of wire plugins. + */ +static struct Plugin *wire_head; + +/** + * Tail of DLL of wire plugins. + */ +static struct Plugin *wire_tail; + + +/** + * Initialize validation subsystem. + * + * @param cfg configuration to use + * @return #GNUNET_OK on success + */ +int +TMH_VALIDATION_init (const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + struct Plugin *p; + char *wireformats; + char *lib_name; + const char *token; + + /* Find out list of supported wire formats */ + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "mint", + "wireformat", + &wireformats)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "mint", + "wireformat"); + return GNUNET_SYSERR; + } + for (token = strtok (wireformats, + " "); + NULL != token; + token = strtok (NULL, + " ")) + { + (void) GNUNET_asprintf (&lib_name, + "libtaler_plugin_wire_%s", + lib_name); + p = GNUNET_new (struct Plugin); + p->type = GNUNET_strdup (token); + p->plugin = GNUNET_PLUGIN_load (lib_name, + (void *) cfg); + if (NULL == p->plugin) + { + GNUNET_free (p); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to load plugin %s\n", + lib_name); + GNUNET_free (lib_name); + TMH_VALIDATION_done (); + return GNUNET_SYSERR; + } + p->plugin->library_name = lib_name; + GNUNET_CONTAINER_DLL_insert (wire_head, + wire_tail, + p); + } + GNUNET_free (wireformats); + return GNUNET_OK; +} + + +/** + * Shutdown validation subsystem. + */ +void +TMH_VALIDATION_done () +{ + struct Plugin *p; + char *lib_name; + + while (NULL != (p = wire_head)) + { + GNUNET_CONTAINER_DLL_remove (wire_head, + wire_tail, + p); + lib_name = p->plugin->library_name; + GNUNET_assert (NULL == GNUNET_PLUGIN_unload (lib_name, + p->plugin)); + GNUNET_free (lib_name); + GNUNET_free (p->type); + GNUNET_free (p); + } +} + + +/** + * Check if the given wire format JSON object is correctly formatted as + * a wire address. + * + * @param wire the JSON wire format object + * @return #GNUNET_YES if correctly formatted; #GNUNET_NO if not + */ +int +TMH_json_validate_wireformat (const json_t *wire) +{ + const char *stype; + json_error_t error; + struct Plugin *p; + + if (0 != json_unpack_ex ((json_t *) wire, + &error, 0, + "{s:s}", + "type", &stype)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + for (p=wire_head; NULL != p; p = p->next) + if (0 == strcasecmp (p->type, + stype)) + return p->plugin->wire_validate (wire); + return GNUNET_NO; +} + + +/** + * Check if we support the given wire method. + * + * @param type type of wire method to check + * @return #GNUNET_YES if the method is supported + */ +int +TMH_VALIDATION_test_method (const char *type) +{ + struct Plugin *p; + + for (p=wire_head;NULL != p;p = p->next) + if (0 == strcasecmp (type, + p->type)) + return GNUNET_YES; + return GNUNET_NO; +} + + +/** + * Obtain supported validation methods as a JSON array, + * and as a hash. + * + * @param[out] h set to the hash of the JSON methods + * @return JSON array with the supported validation methods + */ +json_t * +TMH_VALIDATION_get_methods (struct GNUNET_HashCode *h) +{ + json_t *methods; + struct GNUNET_HashContext *hc; + const char *wf; + struct Plugin *p; + + methods = json_array (); + hc = GNUNET_CRYPTO_hash_context_start (); + for (p=wire_head;NULL != p;p = p->next) + { + wf = p->type; + json_array_append_new (methods, + json_string (wf)); + GNUNET_CRYPTO_hash_context_read (hc, + wf, + strlen (wf) + 1); + } + GNUNET_CRYPTO_hash_context_finish (hc, + h); + return methods; +} + + +/* end of taler-mint-httpd_validation.c */ diff --git a/src/mint/taler-mint-httpd_validation.h b/src/mint/taler-mint-httpd_validation.h new file mode 100644 index 000000000..f5fb19003 --- /dev/null +++ b/src/mint/taler-mint-httpd_validation.h @@ -0,0 +1,76 @@ +/* + This file is part of TALER + Copyright (C) 2016 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file taler-mint-httpd_validation.h + * @brief helpers for calling the wire plugins to validate addresses + * @author Christian Grothoff + */ +#ifndef TALER_MINT_HTTPD_VALIDATION_H +#define TALER_MINT_HTTPD_VALIDATION_H +#include <gnunet/gnunet_util_lib.h> +#include <jansson.h> + + +/** + * Initialize validation subsystem. + * + * @param cfg configuration to use + * @return #GNUNET_OK on success + */ +int +TMH_VALIDATION_init (const struct GNUNET_CONFIGURATION_Handle *cfg); + + +/** + * Shutdown validation subsystem. + */ +void +TMH_VALIDATION_done (void); + + +/** + * Check if the given wire format JSON object is correctly formatted as + * a wire address. + * + * @param wire the JSON wire format object + * @return #GNUNET_YES if correctly formatted; #GNUNET_NO if not + */ +int +TMH_json_validate_wireformat (const json_t *wire); + +/** + * Check if we support the given wire method. + * + * @param type type of wire method to check + * @return #GNUNET_YES if the method is supported + */ +int +TMH_VALIDATION_test_method (const char *type); + + +/** + * Obtain supported validation methods as a JSON array, + * and as a hash. + * + * @param[out] h set to the hash of the JSON methods + * @return JSON array with the supported validation methods + */ +json_t * +TMH_VALIDATION_get_methods (struct GNUNET_HashCode *h); + + +#endif diff --git a/src/mint/taler-mint-httpd_wire.c b/src/mint/taler-mint-httpd_wire.c index 1eb3f6bef..020a7e108 100644 --- a/src/mint/taler-mint-httpd_wire.c +++ b/src/mint/taler-mint-httpd_wire.c @@ -21,6 +21,7 @@ #include "platform.h" #include "taler-mint-httpd_keystate.h" #include "taler-mint-httpd_responses.h" +#include "taler-mint-httpd_validation.h" #include "taler-mint-httpd_wire.h" #include <jansson.h> @@ -45,24 +46,10 @@ TMH_WIRE_handler_wire (struct TMH_RequestHandler *rh, struct TALER_MintPublicKeyP pub; struct TALER_MintSignatureP sig; json_t *methods; - struct GNUNET_HashContext *hc; - unsigned int i; - const char *wf; - methods = json_array (); - hc = GNUNET_CRYPTO_hash_context_start (); - for (i=0;NULL != (wf = TMH_expected_wire_formats[i]); i++) - { - json_array_append_new (methods, - json_string (wf)); - GNUNET_CRYPTO_hash_context_read (hc, - wf, - strlen (wf) + 1); - } wsm.purpose.size = htonl (sizeof (wsm)); wsm.purpose.purpose = htonl (TALER_SIGNATURE_MINT_WIRE_TYPES); - GNUNET_CRYPTO_hash_context_finish (hc, - &wsm.h_wire_types); + methods = TMH_VALIDATION_get_methods (&wsm.h_wire_types); TMH_KS_sign (&wsm.purpose, &pub, &sig); @@ -97,7 +84,6 @@ TMH_WIRE_handler_wire_test (struct TMH_RequestHandler *rh, struct MHD_Response *response; int ret; char *wire_test_redirect; - unsigned int i; response = MHD_create_response_from_buffer (0, NULL, MHD_RESPMEM_PERSISTENT); @@ -107,11 +93,7 @@ TMH_WIRE_handler_wire_test (struct TMH_RequestHandler *rh, return MHD_NO; } TMH_RESPONSE_add_global_headers (response); - for (i=0;NULL != TMH_expected_wire_formats[i];i++) - if (0 == strcasecmp ("test", - TMH_expected_wire_formats[i])) - break; - if (NULL == TMH_expected_wire_formats[i]) + if (GNUNET_NO == TMH_VALIDATION_test_method ("test")) { /* Return 501: not implemented */ ret = MHD_queue_response (connection, @@ -165,13 +147,8 @@ TMH_WIRE_handler_wire_sepa (struct TMH_RequestHandler *rh, char *sepa_wire_file; int fd; struct stat sbuf; - unsigned int i; - for (i=0;NULL != TMH_expected_wire_formats[i];i++) - if (0 == strcasecmp ("sepa", - TMH_expected_wire_formats[i])) - break; - if (NULL == TMH_expected_wire_formats[i]) + if (GNUNET_NO == TMH_VALIDATION_test_method ("sepa")) { /* Return 501: not implemented */ response = MHD_create_response_from_buffer (0, NULL, |