From 6ab67a3a76ee5ce8f8dec910dae7da524f066d2a Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sat, 4 Mar 2017 16:49:33 +0100 Subject: [PATCH] implementing #4929 --- .../exchange_api_track_transfer.c | 6 + src/exchange-lib/test_exchange_api.c | 46 +++- src/exchange/taler-exchange-aggregator.c | 159 +++++++++--- src/exchange/taler-exchange-httpd_db.c | 41 +++ src/exchange/taler-exchange-httpd_responses.c | 7 +- src/exchange/taler-exchange-httpd_responses.h | 2 + ...st-taler-exchange-aggregator-postgres.conf | 14 ++ src/exchange/test_taler_exchange_aggregator.c | 42 +++- src/exchangedb/exchangedb.conf | 2 +- src/exchangedb/plugin_exchangedb_postgres.c | 236 +++++++++++++++++- src/exchangedb/test_exchangedb.c | 4 + src/include/taler_error_codes.h | 15 +- src/include/taler_exchange_service.h | 2 + src/include/taler_exchangedb_plugin.h | 50 ++++ src/include/taler_signatures.h | 5 + 15 files changed, 573 insertions(+), 58 deletions(-) diff --git a/src/exchange-lib/exchange_api_track_transfer.c b/src/exchange-lib/exchange_api_track_transfer.c index 819a00adf..dff39eb29 100644 --- a/src/exchange-lib/exchange_api_track_transfer.c +++ b/src/exchange-lib/exchange_api_track_transfer.c @@ -87,12 +87,14 @@ check_track_transfer_response_ok (struct TALER_EXCHANGE_TrackTransferHandle *wdh struct GNUNET_HashCode h_wire; struct GNUNET_TIME_Absolute exec_time; struct TALER_Amount total_amount; + struct TALER_Amount wire_fee; struct TALER_MerchantPublicKeyP merchant_pub; unsigned int num_details; struct TALER_ExchangePublicKeyP exchange_pub; struct TALER_ExchangeSignatureP exchange_sig; struct GNUNET_JSON_Specification spec[] = { TALER_JSON_spec_amount ("total", &total_amount), + TALER_JSON_spec_amount ("wire_fee", &wire_fee), GNUNET_JSON_spec_fixed_auto ("merchant_pub", &merchant_pub), GNUNET_JSON_spec_fixed_auto ("H_wire", &h_wire), GNUNET_JSON_spec_absolute_time ("execution_time", &exec_time), @@ -158,6 +160,8 @@ check_track_transfer_response_ok (struct TALER_EXCHANGE_TrackTransferHandle *wdh wdp.purpose.size = htonl (sizeof (struct TALER_WireDepositDataPS)); TALER_amount_hton (&wdp.total, &total_amount); + TALER_amount_hton (&wdp.wire_fee, + &wire_fee); wdp.merchant_pub = merchant_pub; wdp.h_wire = h_wire; GNUNET_CRYPTO_hash_context_finish (hash_context, @@ -186,6 +190,7 @@ check_track_transfer_response_ok (struct TALER_EXCHANGE_TrackTransferHandle *wdh &h_wire, exec_time, &total_amount, + &wire_fee, num_details, details); } @@ -257,6 +262,7 @@ handle_track_transfer_finished (void *cls, NULL, GNUNET_TIME_UNIT_ZERO_ABS, NULL, + NULL, 0, NULL); TALER_EXCHANGE_track_transfer_cancel (wdh); } diff --git a/src/exchange-lib/test_exchange_api.c b/src/exchange-lib/test_exchange_api.c index 2456d77a7..3e69c25bb 100644 --- a/src/exchange-lib/test_exchange_api.c +++ b/src/exchange-lib/test_exchange_api.c @@ -540,6 +540,12 @@ struct Command */ const char *total_amount_expected; + /** + * What is the expected wire fee? Only used if + * @e expected_response_code was #MHD_HTTP_OK. + */ + const char *wire_fee_expected; + /* TODO: may want to add list of deposits we expected to see aggregated here in the future. */ @@ -1417,6 +1423,7 @@ wire_cb (void *cls, * @param execution_time time when the exchange claims to have performed the wire transfer * @param total_amount total amount of the wire transfer, or NULL if the exchange could * not provide any @a wtid (set only if @a http_status is #MHD_HTTP_OK) + * @param wire_fee wire fee that was charged by the exchange * @param details_length length of the @a details array * @param details array with details about the combined transactions */ @@ -1429,6 +1436,7 @@ wire_deposits_cb (void *cls, const struct GNUNET_HashCode *h_wire, struct GNUNET_TIME_Absolute execution_time, const struct TALER_Amount *total_amount, + const struct TALER_Amount *wire_fee, unsigned int details_length, const struct TALER_TrackTransferDetails *details) { @@ -1469,6 +1477,24 @@ wire_deposits_cb (void *cls, fail (is); return; } + if (GNUNET_OK != + TALER_string_to_amount (cmd->details.wire_deposits.wire_fee_expected, + &expected_amount)) + { + GNUNET_break (0); + fail (is); + return; + } + if (0 != TALER_amount_cmp (wire_fee, + &expected_amount)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Wire fee missmatch to command %s\n", + cmd->label); + json_dumpf (json, stderr, 0); + fail (is); + return; + } ref = find_command (is, cmd->details.wire_deposits.wtid_ref); GNUNET_assert (NULL != ref); @@ -2923,28 +2949,28 @@ run (void *cls) { .oc = OC_CHECK_BANK_TRANSFER, .label = "check_bank_transfer-499c", .details.check_bank_transfer.exchange_base_url = "https://exchange.com/", - .details.check_bank_transfer.amount = "EUR:4.99", + .details.check_bank_transfer.amount = "EUR:4.98", .details.check_bank_transfer.account_debit = 2, .details.check_bank_transfer.account_credit = 42 }, { .oc = OC_CHECK_BANK_TRANSFER, .label = "check_bank_transfer-99c1", .details.check_bank_transfer.exchange_base_url = "https://exchange.com/", - .details.check_bank_transfer.amount = "EUR:0.99", + .details.check_bank_transfer.amount = "EUR:0.98", .details.check_bank_transfer.account_debit = 2, .details.check_bank_transfer.account_credit = 42 }, { .oc = OC_CHECK_BANK_TRANSFER, .label = "check_bank_transfer-99c2", .details.check_bank_transfer.exchange_base_url = "https://exchange.com/", - .details.check_bank_transfer.amount = "EUR:0.99", + .details.check_bank_transfer.amount = "EUR:0.98", .details.check_bank_transfer.account_debit = 2, .details.check_bank_transfer.account_credit = 42 }, { .oc = OC_CHECK_BANK_TRANSFER, .label = "check_bank_transfer-9c", .details.check_bank_transfer.exchange_base_url = "https://exchange.com/", - .details.check_bank_transfer.amount = "EUR:0.09", + .details.check_bank_transfer.amount = "EUR:0.08", .details.check_bank_transfer.account_debit = 2, .details.check_bank_transfer.account_credit = 43 }, @@ -2959,16 +2985,18 @@ run (void *cls) .details.deposit_wtid.bank_transfer_ref = "check_bank_transfer-499c" }, { .oc = OC_WIRE_DEPOSITS, - .label = "wire-deposits-sucess-bank", + .label = "wire-deposits-success-bank", .expected_response_code = MHD_HTTP_OK, .details.wire_deposits.wtid_ref = "check_bank_transfer-99c1", - .details.wire_deposits.total_amount_expected = "EUR:0.99" }, + .details.wire_deposits.total_amount_expected = "EUR:0.98", + .details.wire_deposits.wire_fee_expected = "EUR:0.01" }, { .oc = OC_WIRE_DEPOSITS, - .label = "wire-deposits-sucess-wtid", + .label = "wire-deposits-success-wtid", .expected_response_code = MHD_HTTP_OK, .details.wire_deposits.wtid_ref = "deposit-wtid-ok", - .details.wire_deposits.total_amount_expected = "EUR:4.99" }, + .details.wire_deposits.total_amount_expected = "EUR:4.98", + .details.wire_deposits.wire_fee_expected = "EUR:0.01" }, /* ************** End of tracking API testing************* */ @@ -3030,7 +3058,7 @@ run (void *cls) { .oc = OC_CHECK_BANK_TRANSFER, .label = "check_bank_transfer-pre-refund", .details.check_bank_transfer.exchange_base_url = "https://exchange.com/", - .details.check_bank_transfer.amount = "EUR:4.98", + .details.check_bank_transfer.amount = "EUR:4.97", .details.check_bank_transfer.account_debit = 2, .details.check_bank_transfer.account_credit = 42 }, diff --git a/src/exchange/taler-exchange-aggregator.c b/src/exchange/taler-exchange-aggregator.c index 96ec7627f..cbf3fb5d3 100644 --- a/src/exchange/taler-exchange-aggregator.c +++ b/src/exchange/taler-exchange-aggregator.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2016 GNUnet e.V. + Copyright (C) 2016, 2017 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 @@ -53,6 +53,12 @@ struct WirePlugin * Name of the plugin. */ char *type; + + /** + * Wire transfer fee structure. + */ + struct TALER_EXCHANGEDB_AggregateFees *af; + }; @@ -102,6 +108,11 @@ struct AggregationUnit */ struct TALER_Amount total_amount; + /** + * Wire fee we charge for @e wp at @e execution_time. + */ + struct TALER_Amount wire_fee; + /** * Hash of @e wire. */ @@ -259,6 +270,83 @@ extract_type (const json_t *wire) } +/** + * Advance the "af" pointer in @a wp to point to the + * currently valid record. + * + * @param wp wire transfer fee data structure to update + * @param now timestamp to update fees to + */ +static void +advance_fees (struct WirePlugin *wp, + struct GNUNET_TIME_Absolute now) +{ + struct TALER_EXCHANGEDB_AggregateFees *af; + + /* First, try to see if we have current fee information in memory */ + af = wp->af; + while ( (NULL != af) && + (af->end_date.abs_value_us < now.abs_value_us) ) + { + struct TALER_EXCHANGEDB_AggregateFees *n = af->next; + + GNUNET_free (af); + af = n; + } + wp->af = af; +} + + +/** + * Update wire transfer fee data structure in @a wp. + * + * @param wp wire transfer fee data structure to update + * @param now timestamp to update fees to + * @param session DB session to use + * @return #GNUNET_OK on success, #GNUNET_SYSERR if we + * lack current fee information (and need to exit) + */ +static int +update_fees (struct WirePlugin *wp, + struct GNUNET_TIME_Absolute now, + struct TALER_EXCHANGEDB_Session *session) +{ + advance_fees (wp, + now); + if (NULL != wp->af) + return GNUNET_OK; + /* Let's try to load it from disk... */ + wp->af = TALER_EXCHANGEDB_fees_read (cfg, + wp->type); + advance_fees (wp, + now); + for (struct TALER_EXCHANGEDB_AggregateFees *p = wp->af; + NULL != p; + p = p->next) + { + if (GNUNET_SYSERR == + db_plugin->insert_wire_fee (db_plugin->cls, + session, + wp->type, + p->start_date, + p->end_date, + &p->wire_fee, + &p->master_sig)) + { + TALER_EXCHANGEDB_fees_free (wp->af); + wp->af = NULL; + return GNUNET_SYSERR; + } + } + if (NULL != wp->af) + return GNUNET_OK; + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to find current wire transfer fees for `%s'\n", + wp->type); + return GNUNET_SYSERR; +} + + /** * Find the wire plugin for the given wire address. * @@ -345,6 +433,7 @@ shutdown_task (void *cls) wp_tail, wp); TALER_WIRE_plugin_unload (wp->wire_plugin); + TALER_EXCHANGEDB_fees_free (wp->af); GNUNET_free (wp->type); GNUNET_free (wp); } @@ -433,9 +522,8 @@ deposit_cb (void *cls, return GNUNET_SYSERR; } au->row_id = row_id; + GNUNET_assert (NULL == au->wire); au->wire = json_incref ((json_t *) wire); - au->execution_time = GNUNET_TIME_absolute_get (); - (void) GNUNET_TIME_round_abs (&au->execution_time); TALER_JSON_hash (au->wire, &au->h_wire); GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, @@ -444,6 +532,21 @@ deposit_cb (void *cls, GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Starting aggregation under H(WTID)=%s\n", TALER_B2S (&au->wtid)); + + au->wp = find_plugin (extract_type (au->wire)); + if (NULL == au->wp) + return GNUNET_SYSERR; + + /* make sure we have current fees */ + au->execution_time = GNUNET_TIME_absolute_get (); + (void) GNUNET_TIME_round_abs (&au->execution_time); + if (GNUNET_OK != + update_fees (au->wp, + au->execution_time, + au->session)) + return GNUNET_SYSERR; + au->wire_fee = au->wp->af->wire_fee; + if (GNUNET_OK != db_plugin->insert_aggregation_tracking (db_plugin->cls, au->session, @@ -585,7 +688,7 @@ run_aggregation (void *cls) unsigned int i; int ret; const struct GNUNET_SCHEDULER_TaskContext *tc; - struct WirePlugin *wp; + struct TALER_Amount final_amount; task = NULL; tc = GNUNET_SCHEDULER_get_task_context (); @@ -650,18 +753,6 @@ run_aggregation (void *cls) return; } - wp = find_plugin (extract_type (au->wire)); - if (NULL == wp) - { - json_decref (au->wire); - GNUNET_free (au); - au = NULL; - db_plugin->rollback (db_plugin->cls, - session); - GNUNET_SCHEDULER_shutdown (); - return; - } - /* Now try to find other deposits to aggregate */ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Found ready deposit for %s, aggregating\n", @@ -689,13 +780,18 @@ run_aggregation (void *cls) return; } - /* Round to the unit supported by the wire transfer method */ - GNUNET_assert (GNUNET_SYSERR != - wp->wire_plugin->amount_round (wp->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) ) + /* Subtract wire transfer fee and round to the unit supported by the + wire transfer method; Check if after rounding down, we still have + an amount to transfer, and if not mark as 'tiny'. */ + if ( (GNUNET_OK != + TALER_amount_subtract (&final_amount, + &au->total_amount, + &au->wire_fee)) || + (GNUNET_SYSERR == + au->wp->wire_plugin->amount_round (au->wp->wire_plugin->cls, + &final_amount)) || + ( (0 == final_amount.value) && + (0 == final_amount.fraction) ) ) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Aggregate value too low for transfer\n"); @@ -755,21 +851,20 @@ run_aggregation (void *cls) { char *amount_s; - amount_s = TALER_amount_to_string (&au->total_amount); + amount_s = TALER_amount_to_string (&final_amount); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Preparing wire transfer of %s to %s\n", amount_s, TALER_B2S (&au->merchant_pub)); GNUNET_free (amount_s); } - au->wp = wp; - au->ph = wp->wire_plugin->prepare_wire_transfer (wp->wire_plugin->cls, - au->wire, - &au->total_amount, - exchange_base_url, - &au->wtid, - &prepare_cb, - au); + au->ph = au->wp->wire_plugin->prepare_wire_transfer (au->wp->wire_plugin->cls, + au->wire, + &final_amount, + exchange_base_url, + &au->wtid, + &prepare_cb, + au); if (NULL == au->ph) { GNUNET_break (0); /* why? how to best recover? */ diff --git a/src/exchange/taler-exchange-httpd_db.c b/src/exchange/taler-exchange-httpd_db.c index b9d3451af..9257cbef8 100644 --- a/src/exchange/taler-exchange-httpd_db.c +++ b/src/exchange/taler-exchange-httpd_db.c @@ -1864,6 +1864,11 @@ struct WtidTransactionContext */ struct TALER_MerchantPublicKeyP merchant_pub; + /** + * Which method was used to wire the funds? + */ + char *wire_method; + /** * Hash of the wire details of the merchant (identical for all * deposits), only valid if @e is_valid is #GNUNET_YES. @@ -1918,6 +1923,7 @@ struct WtidTransactionContext static void handle_transaction_data (void *cls, const struct TALER_MerchantPublicKeyP *merchant_pub, + const char *wire_method, const struct GNUNET_HashCode *h_wire, struct GNUNET_TIME_Absolute exec_time, const struct GNUNET_HashCode *h_proposal_data, @@ -1936,6 +1942,7 @@ handle_transaction_data (void *cls, ctx->merchant_pub = *merchant_pub; ctx->h_wire = *h_wire; ctx->exec_time = exec_time; + ctx->wire_method = GNUNET_strdup (wire_method); ctx->is_valid = GNUNET_YES; if (GNUNET_OK != TALER_amount_subtract (&ctx->total, @@ -1952,6 +1959,8 @@ handle_transaction_data (void *cls, if ( (0 != memcmp (&ctx->merchant_pub, merchant_pub, sizeof (struct TALER_MerchantPublicKeyP))) || + (0 != strcmp (wire_method, + ctx->wire_method)) || (0 != memcmp (&ctx->h_wire, h_wire, sizeof (struct GNUNET_HashCode))) ) @@ -2006,6 +2015,10 @@ TEH_DB_execute_track_transfer (struct MHD_Connection *connection, struct WtidTransactionContext ctx; struct TALER_EXCHANGEDB_Session *session; struct TEH_TrackTransferDetail *wdd; + struct GNUNET_TIME_Absolute wire_fee_start_date; + struct GNUNET_TIME_Absolute wire_fee_end_date; + struct TALER_Amount wire_fee; + struct TALER_MasterSignatureP wire_fee_master_sig; if (NULL == (session = TEH_plugin->get_session (TEH_plugin->cls))) { @@ -2016,6 +2029,7 @@ TEH_DB_execute_track_transfer (struct MHD_Connection *connection, ctx.is_valid = GNUNET_NO; ctx.wdd_head = NULL; ctx.wdd_tail = NULL; + ctx.wire_method = NULL; ret = TEH_plugin->lookup_wire_transfer (TEH_plugin->cls, session, wtid, @@ -2042,10 +2056,36 @@ TEH_DB_execute_track_transfer (struct MHD_Connection *connection, "wtid"); goto cleanup; } + if (GNUNET_OK != + TEH_plugin->get_wire_fee (TEH_plugin->cls, + session, + ctx.wire_method, + ctx.exec_time, + &wire_fee_start_date, + &wire_fee_end_date, + &wire_fee, + &wire_fee_master_sig)) + { + GNUNET_break (0); + ret = TEH_RESPONSE_reply_internal_db_error (connection, + TALER_EC_TRACK_TRANSFER_WIRE_FEE_NOT_FOUND); + goto cleanup; + } + if (GNUNET_OK != + TALER_amount_subtract (&ctx.total, + &ctx.total, + &wire_fee)) + { + GNUNET_break (0); + ret = TEH_RESPONSE_reply_internal_db_error (connection, + TALER_EC_TRACK_TRANSFER_WIRE_FEE_INCONSISTENT); + goto cleanup; + } ret = TEH_RESPONSE_reply_track_transfer_details (connection, &ctx.total, &ctx.merchant_pub, &ctx.h_wire, + &wire_fee, ctx.exec_time, ctx.wdd_head); cleanup: @@ -2056,6 +2096,7 @@ TEH_DB_execute_track_transfer (struct MHD_Connection *connection, wdd); GNUNET_free (wdd); } + GNUNET_free_non_null (ctx.wire_method); return ret; } diff --git a/src/exchange/taler-exchange-httpd_responses.c b/src/exchange/taler-exchange-httpd_responses.c index bae6707f1..1caef3469 100644 --- a/src/exchange/taler-exchange-httpd_responses.c +++ b/src/exchange/taler-exchange-httpd_responses.c @@ -1214,6 +1214,7 @@ TEH_RESPONSE_reply_track_transaction (struct MHD_Connection *connection, * @param total total amount that was transferred * @param merchant_pub public key of the merchant * @param h_wire destination account + * @param wire_fee wire fee that was charged * @param exec_time execution time of the wire transfer * @param wdd_head linked list with details about the combined deposits * @return MHD result code @@ -1223,6 +1224,7 @@ TEH_RESPONSE_reply_track_transfer_details (struct MHD_Connection *connection, const struct TALER_Amount *total, const struct TALER_MerchantPublicKeyP *merchant_pub, const struct GNUNET_HashCode *h_wire, + const struct TALER_Amount *wire_fee, struct GNUNET_TIME_Absolute exec_time, const struct TEH_TrackTransferDetail *wdd_head) { @@ -1261,6 +1263,8 @@ TEH_RESPONSE_reply_track_transfer_details (struct MHD_Connection *connection, wdp.purpose.size = htonl (sizeof (struct TALER_WireDepositDataPS)); TALER_amount_hton (&wdp.total, total); + TALER_amount_hton (&wdp.wire_fee, + wire_fee); wdp.merchant_pub = *merchant_pub; wdp.h_wire = *h_wire; GNUNET_CRYPTO_hash_context_finish (hash_context, @@ -1270,8 +1274,9 @@ TEH_RESPONSE_reply_track_transfer_details (struct MHD_Connection *connection, &sig); return TEH_RESPONSE_reply_json_pack (connection, MHD_HTTP_OK, - "{s:o, s:o, s:o, s:o, s:o, s:o, s:o}", + "{s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:o}", "total", TALER_JSON_from_amount (total), + "wire_fee", TALER_JSON_from_amount (wire_fee), "merchant_pub", GNUNET_JSON_from_data_auto (merchant_pub), "H_wire", GNUNET_JSON_from_data_auto (h_wire), "execution_time", GNUNET_JSON_from_time_abs (exec_time), diff --git a/src/exchange/taler-exchange-httpd_responses.h b/src/exchange/taler-exchange-httpd_responses.h index 17abd7ecb..179ae0066 100644 --- a/src/exchange/taler-exchange-httpd_responses.h +++ b/src/exchange/taler-exchange-httpd_responses.h @@ -399,6 +399,7 @@ struct TEH_TrackTransferDetail * @param total total amount that was transferred * @param merchant_pub public key of the merchant * @param h_wire destination account + * @param wire_fee wire fee that was charged * @param exec_time execution time of the wire transfer * @param wdd_head linked list with details about the combined deposits * @return MHD result code @@ -408,6 +409,7 @@ TEH_RESPONSE_reply_track_transfer_details (struct MHD_Connection *connection, const struct TALER_Amount *total, const struct TALER_MerchantPublicKeyP *merchant_pub, const struct GNUNET_HashCode *h_wire, + const struct TALER_Amount *wire_fee, struct GNUNET_TIME_Absolute exec_time, const struct TEH_TrackTransferDetail *wdd_head); diff --git a/src/exchange/test-taler-exchange-aggregator-postgres.conf b/src/exchange/test-taler-exchange-aggregator-postgres.conf index 4aa74b037..e70a933b7 100644 --- a/src/exchange/test-taler-exchange-aggregator-postgres.conf +++ b/src/exchange/test-taler-exchange-aggregator-postgres.conf @@ -29,6 +29,20 @@ DB_CONN_STR = postgres:///talercheck # Enable 'test' for testing of the actual coin operations. ENABLE = YES +# Fees for the forseeable future... +# If you see this after 2017, update to match the next 10 years... +WIRE-FEE-2017 = EUR:0.01 +WIRE-FEE-2018 = EUR:0.01 +WIRE-FEE-2019 = EUR:0.01 +WIRE-FEE-2020 = EUR:0.01 +WIRE-FEE-2021 = EUR:0.01 +WIRE-FEE-2022 = EUR:0.01 +WIRE-FEE-2023 = EUR:0.01 +WIRE-FEE-2024 = EUR:0.01 +WIRE-FEE-2025 = EUR:0.01 +WIRE-FEE-2026 = EUR:0.01 + + [exchange-wire-outgoing-test] # What is the main website of the bank? BANK_URI = "http://localhost:8082/" diff --git a/src/exchange/test_taler_exchange_aggregator.c b/src/exchange/test_taler_exchange_aggregator.c index 6bd0d709b..5a3974f34 100644 --- a/src/exchange/test_taler_exchange_aggregator.c +++ b/src/exchange/test_taler_exchange_aggregator.c @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2016 Inria and GNUnet e.V. + (C) 2016, 2017 Inria and 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 @@ -621,7 +621,7 @@ run_test () .details.expect_transaction.debit_account = 3, .details.expect_transaction.credit_account = 4, .details.expect_transaction.exchange_base_url = "https://exchange.taler.net/", - .details.expect_transaction.amount = "EUR:0.9" + .details.expect_transaction.amount = "EUR:0.89" }, { @@ -668,7 +668,7 @@ run_test () .details.expect_transaction.debit_account = 3, .details.expect_transaction.credit_account = 4, .details.expect_transaction.exchange_base_url = "https://exchange.taler.net/", - .details.expect_transaction.amount = "EUR:1.8" + .details.expect_transaction.amount = "EUR:1.79" }, { @@ -714,7 +714,7 @@ run_test () .details.expect_transaction.debit_account = 3, .details.expect_transaction.credit_account = 4, .details.expect_transaction.exchange_base_url = "https://exchange.taler.net/", - .details.expect_transaction.amount = "EUR:0.9" + .details.expect_transaction.amount = "EUR:0.89" }, { .opcode = OPCODE_EXPECT_TRANSACTION, @@ -722,7 +722,7 @@ run_test () .details.expect_transaction.debit_account = 3, .details.expect_transaction.credit_account = 4, .details.expect_transaction.exchange_base_url = "https://exchange.taler.net/", - .details.expect_transaction.amount = "EUR:0.9" + .details.expect_transaction.amount = "EUR:0.89" }, { .opcode = OPCODE_EXPECT_TRANSACTION, @@ -730,7 +730,7 @@ run_test () .details.expect_transaction.debit_account = 3, .details.expect_transaction.credit_account = 5, .details.expect_transaction.exchange_base_url = "https://exchange.taler.net/", - .details.expect_transaction.amount = "EUR:0.9" + .details.expect_transaction.amount = "EUR:0.89" }, { .opcode = OPCODE_EXPECT_TRANSACTIONS_EMPTY, @@ -779,7 +779,7 @@ run_test () .details.expect_transaction.debit_account = 3, .details.expect_transaction.credit_account = 4, .details.expect_transaction.exchange_base_url = "https://exchange.taler.net/", - .details.expect_transaction.amount = "EUR:0.2" + .details.expect_transaction.amount = "EUR:0.19" }, /* test picking all deposits at earliest deadline */ @@ -824,7 +824,7 @@ run_test () .details.expect_transaction.debit_account = 3, .details.expect_transaction.credit_account = 4, .details.expect_transaction.exchange_base_url = "https://exchange.taler.net/", - .details.expect_transaction.amount = "EUR:0.2" + .details.expect_transaction.amount = "EUR:0.19" }, /* Test NEVER running 'tiny' unless they make up minimum unit */ @@ -894,7 +894,7 @@ run_test () .details.deposit.merchant_name = "bob", .details.deposit.merchant_account = 4, .details.deposit.wire_deadline = { 1000LL * 1000 * 0 }, /* 0s */ - .details.deposit.amount_with_fee = "EUR:0.102", + .details.deposit.amount_with_fee = "EUR:0.112", .details.deposit.deposit_fee = "EUR:0.1" }, { @@ -934,7 +934,7 @@ run_test () .details.deposit.merchant_name = "bob", .details.deposit.merchant_account = 4, .details.deposit.wire_deadline = { 1000LL * 1000 * 0 }, /* 0s */ - .details.deposit.amount_with_fee = "EUR:0.109", + .details.deposit.amount_with_fee = "EUR:0.119", .details.deposit.deposit_fee = "EUR:0.1" }, { @@ -969,7 +969,7 @@ run_test () .details.expect_transaction.debit_account = 3, .details.expect_transaction.credit_account = 4, .details.expect_transaction.exchange_base_url = "https://exchange.taler.net/", - .details.expect_transaction.amount = "EUR:0.02" + .details.expect_transaction.amount = "EUR:0.01" }, /* Test that aggregation would happen fully if wire deadline is long */ @@ -1027,7 +1027,7 @@ run_test () .details.expect_transaction.debit_account = 3, .details.expect_transaction.credit_account = 4, .details.expect_transaction.exchange_base_url = "https://exchange.taler.net/", - .details.expect_transaction.amount = "EUR:0.04" + .details.expect_transaction.amount = "EUR:0.03" }, @@ -1087,7 +1087,7 @@ run_test () .details.expect_transaction.debit_account = 3, .details.expect_transaction.credit_account = 4, .details.expect_transaction.exchange_base_url = "https://exchange.taler.net/", - .details.expect_transaction.amount = "EUR:0.02" + .details.expect_transaction.amount = "EUR:0.01" }, /* Everything tested, terminate with success */ @@ -1203,6 +1203,7 @@ main (int argc, { const char *plugin_name; char *testname; + struct GNUNET_OS_Process *proc; struct GNUNET_CONFIGURATION_Handle *cfg; struct GNUNET_SIGNAL_Context *shc_chld; @@ -1225,6 +1226,21 @@ main (int argc, GNUNET_log_setup ("test_taler_exchange_aggregator", "WARNING", NULL); + proc = GNUNET_OS_start_process (GNUNET_NO, + GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + "taler-exchange-keyup", + "taler-exchange-keyup", + "-c", config_filename, + NULL); + if (NULL == proc) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to run `taler-exchange-keyup`, is your PATH correct?\n"); + return 77; + } + GNUNET_OS_process_wait (proc); + GNUNET_OS_process_destroy (proc); cfg = GNUNET_CONFIGURATION_create (); if (GNUNET_OK != GNUNET_CONFIGURATION_parse (cfg, diff --git a/src/exchangedb/exchangedb.conf b/src/exchangedb/exchangedb.conf index 4640507dc..73e1603a9 100644 --- a/src/exchangedb/exchangedb.conf +++ b/src/exchangedb/exchangedb.conf @@ -11,4 +11,4 @@ AUDITOR_BASE_DIR = ${TALER_DATA_HOME}/auditors/ # the merchant per wire transfer. The directory is expected to # contain files "$METHOD.fee" with the cost structure, where # $METHOD corresponds to a wire transfer method. -WIREFEE_BASE_DIR = ${TALER_DATA_HOME}/wirefees/ +WIREFEE_BASE_DIR = ${TALER_DATA_HOME}/exchange/wirefees/ diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index ebca07fae..feb03f975 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -215,6 +215,8 @@ postgres_drop_tables (void *cls) "DROP TABLE IF EXISTS prewire;"); SQLEXEC_ (conn, "DROP TABLE IF EXISTS aggregation_tracking;"); + SQLEXEC_ (conn, + "DROP TABLE IF EXISTS wire_fee;"); SQLEXEC_ (conn, "DROP TABLE IF EXISTS deposits;"); SQLEXEC_ (conn, @@ -472,6 +474,23 @@ postgres_create_tables (void *cls) SQLEXEC_INDEX("CREATE INDEX aggregation_tracking_wtid_index " "ON aggregation_tracking(wtid_raw)"); + + /* Table for the wire fees. */ + SQLEXEC("CREATE TABLE IF NOT EXISTS wire_fee " + "(wire_method VARCHAR NOT NULL" + ",start_date INT8 NOT NULL" + ",end_date INT8 NOT NULL" + ",wire_fee_val INT8 NOT NULL" + ",wire_fee_frac INT4 NOT NULL" + ",wire_fee_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL" + ",master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)" + ",PRIMARY KEY (wire_method, start_date)" /* this combo must be unique */ + ")"); + /* Index for lookup_transactions statement on wtid */ + SQLEXEC_INDEX("CREATE INDEX aggregation_tracking_wtid_index " + "ON aggregation_tracking(wtid_raw)"); + + /* This table contains the pre-commit data for wire transfers the exchange is about to execute. */ SQLEXEC("CREATE TABLE IF NOT EXISTS prewire " @@ -1193,6 +1212,7 @@ postgres_prepare (PGconn *db_conn) PREPARE ("lookup_transactions", "SELECT" " deposits.h_proposal_data" + ",deposits.wire" ",deposits.h_wire" ",deposits.coin_pub" ",deposits.merchant_pub" @@ -1241,6 +1261,35 @@ postgres_prepare (PGconn *db_conn) "($1, $2, $3)", 3, NULL); + /* Used in #postgres_get_wire_fee() */ + PREPARE ("get_wire_fee", + "SELECT " + " start_date" + ",end_date" + ",wire_fee_val" + ",wire_fee_frac" + ",wire_fee_curr" + ",master_sig" + " FROM wire_fee" + " WHERE wire_method=$1" + " AND start_date <= $2" + " AND end_date > $2", + 2, NULL); + + /* Used in #postgres_insert_wire_fee */ + PREPARE ("insert_wire_fee", + "INSERT INTO wire_fee " + "(wire_method" + ",start_date" + ",end_date" + ",wire_fee_val" + ",wire_fee_frac" + ",wire_fee_curr" + ",master_sig" + ") VALUES " + "($1, $2, $3, $4, $5, $6, $7)", + 7, NULL); + /* Used in #postgres_wire_prepare_data_insert() to store wire transfer information before actually committing it with the bank */ @@ -3980,15 +4029,19 @@ postgres_lookup_wire_transfer (void *cls, struct GNUNET_TIME_Absolute exec_time; struct TALER_Amount amount_with_fee; struct TALER_Amount deposit_fee; + json_t *wire; + json_t *t; + const char *wire_method; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_auto_from_type ("h_proposal_data", &h_proposal_data), + TALER_PQ_result_spec_json ("wire", &wire), GNUNET_PQ_result_spec_auto_from_type ("h_wire", &h_wire), GNUNET_PQ_result_spec_auto_from_type ("coin_pub", &coin_pub), GNUNET_PQ_result_spec_auto_from_type ("merchant_pub", &merchant_pub), GNUNET_PQ_result_spec_absolute_time ("execution_time", &exec_time), TALER_PQ_result_spec_amount ("amount_with_fee", &amount_with_fee), TALER_PQ_result_spec_amount ("fee_deposit", &deposit_fee), - GNUNET_PQ_result_spec_end + GNUNET_PQ_result_spec_end }; if (GNUNET_OK != GNUNET_PQ_extract_result (result, @@ -3999,8 +4052,23 @@ postgres_lookup_wire_transfer (void *cls, PQclear (result); return GNUNET_SYSERR; } + t = json_object_get (wire, "type"); + if (NULL == t) + { + GNUNET_break (0); + PQclear (result); + return GNUNET_SYSERR; + } + wire_method = json_string_value (t); + if (NULL == wire_method) + { + GNUNET_break (0); + PQclear (result); + return GNUNET_SYSERR; + } cb (cb_cls, &merchant_pub, + wire_method, &h_wire, exec_time, &h_proposal_data, @@ -4210,6 +4278,170 @@ postgres_insert_aggregation_tracking (void *cls, } +/** + * Obtain wire fee from database. + * + * @param cls closure + * @param session database connection + * @param type type of wire transfer the fee applies for + * @param date for which date do we want the fee? + * @param[out] start_date when does the fee go into effect + * @param[out] end_date when does the fee end being valid + * @param[out] wire_fee how high is the wire transfer fee + * @param[out] master_sig signature over the above by the exchange master key + * @return #GNUNET_OK on success, #GNUNET_NO if no fee is known + * #GNUNET_SYSERR on failure + */ +static int +postgres_get_wire_fee (void *cls, + struct TALER_EXCHANGEDB_Session *session, + const char *type, + struct GNUNET_TIME_Absolute date, + struct GNUNET_TIME_Absolute *start_date, + struct GNUNET_TIME_Absolute *end_date, + struct TALER_Amount *wire_fee, + struct TALER_MasterSignatureP *master_sig) +{ + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (type), + GNUNET_PQ_query_param_absolute_time (&date), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_absolute_time ("start_date", start_date), + GNUNET_PQ_result_spec_absolute_time ("end_date", end_date), + TALER_PQ_result_spec_amount ("wire_fee", wire_fee), + GNUNET_PQ_result_spec_auto_from_type ("master_sig", master_sig), + GNUNET_PQ_result_spec_end + }; + PGresult *result; + int nrows; + + result = GNUNET_PQ_exec_prepared (session->conn, + "get_wire_fee", + params); + if (PGRES_TUPLES_OK != + PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + nrows = PQntuples (result); + if (0 == nrows) + { + /* no matches found */ + PQclear (result); + return GNUNET_NO; + } + if (1 != nrows) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + 0)) + { + PQclear (result); + GNUNET_break (0); + return GNUNET_SYSERR; + } + PQclear (result); + return GNUNET_OK; +} + + +/** + * Insert wire transfer fee into database. + * + * @param cls closure + * @param session database connection + * @param type type of wire transfer this fee applies for + * @param start_date when does the fee go into effect + * @param end_date when does the fee end being valid + * @param wire_fee how high is the wire transfer fee + * @param master_sig signature over the above by the exchange master key + * @return #GNUNET_OK on success, #GNUNET_NO if the record exists, + * #GNUNET_SYSERR on failure + */ +static int +postgres_insert_wire_fee (void *cls, + struct TALER_EXCHANGEDB_Session *session, + const char *type, + struct GNUNET_TIME_Absolute start_date, + struct GNUNET_TIME_Absolute end_date, + const struct TALER_Amount *wire_fee, + const struct TALER_MasterSignatureP *master_sig) +{ + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (type), + GNUNET_PQ_query_param_absolute_time (&start_date), + GNUNET_PQ_query_param_absolute_time (&end_date), + TALER_PQ_query_param_amount (wire_fee), + GNUNET_PQ_query_param_auto_from_type (master_sig), + GNUNET_PQ_query_param_end + }; + PGresult *result; + struct TALER_Amount wf; + struct TALER_MasterSignatureP sig; + struct GNUNET_TIME_Absolute sd; + struct GNUNET_TIME_Absolute ed; + + if (GNUNET_OK == + postgres_get_wire_fee (cls, + session, + type, + start_date, + &sd, + &ed, + &wf, + &sig)) + { + if (0 != memcmp (&sig, + master_sig, + sizeof (sig))) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (0 != TALER_amount_cmp (wire_fee, + &wf)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if ( (sd.abs_value_us != start_date.abs_value_us) || + (ed.abs_value_us != end_date.abs_value_us) ) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + /* equal record already exists */ + return GNUNET_NO; + } + + result = GNUNET_PQ_exec_prepared (session->conn, + "insert_wire_fee", + params); + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + if (0 != strcmp ("1", PQcmdTuples (result))) + { + GNUNET_break (0); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + return GNUNET_OK; +} + + /** * Function called to insert wire transfer commit data into the DB. * @@ -5090,6 +5322,8 @@ libtaler_plugin_exchangedb_postgres_init (void *cls) plugin->lookup_wire_transfer = &postgres_lookup_wire_transfer; plugin->wire_lookup_deposit_wtid = &postgres_wire_lookup_deposit_wtid; plugin->insert_aggregation_tracking = &postgres_insert_aggregation_tracking; + plugin->insert_wire_fee = &postgres_insert_wire_fee; + plugin->get_wire_fee = &postgres_get_wire_fee; plugin->wire_prepare_data_insert = &postgres_wire_prepare_data_insert; plugin->wire_prepare_data_mark_finished = &postgres_wire_prepare_data_mark_finished; plugin->wire_prepare_data_get = &postgres_wire_prepare_data_get; diff --git a/src/exchangedb/test_exchangedb.c b/src/exchangedb/test_exchangedb.c index c37a590c3..a29e04513 100644 --- a/src/exchangedb/test_exchangedb.c +++ b/src/exchangedb/test_exchangedb.c @@ -783,6 +783,7 @@ test_melting (struct TALER_EXCHANGEDB_Session *session) static void cb_wt_never (void *cls, const struct TALER_MerchantPublicKeyP *merchant_pub, + const char *wire_method, const struct GNUNET_HashCode *h_wire, struct GNUNET_TIME_Absolute exec_time, const struct GNUNET_HashCode *h_proposal_data, @@ -825,6 +826,7 @@ static struct TALER_WireTransferIdentifierRawP wtid_wt; static void cb_wt_check (void *cls, const struct TALER_MerchantPublicKeyP *merchant_pub, + const char *wire_method, const struct GNUNET_HashCode *h_wire, struct GNUNET_TIME_Absolute exec_time, const struct GNUNET_HashCode *h_proposal_data, @@ -836,6 +838,8 @@ cb_wt_check (void *cls, GNUNET_assert (0 == memcmp (merchant_pub, &merchant_pub_wt, sizeof (struct TALER_MerchantPublicKeyP))); + GNUNET_assert (0 == strcmp (wire_method, + "SEPA")); GNUNET_assert (0 == memcmp (h_wire, &h_wire_wt, sizeof (struct GNUNET_HashCode))); diff --git a/src/include/taler_error_codes.h b/src/include/taler_error_codes.h index 770a8ad67..4b90281f0 100644 --- a/src/include/taler_error_codes.h +++ b/src/include/taler_error_codes.h @@ -779,6 +779,19 @@ enum TALER_ErrorCode */ TALER_EC_TRACK_TRANSFER_WTID_NOT_FOUND = 1702, + /** + * The exchange did not find information about the wire transfer + * fees it charged. This response is + * provided with HTTP status code MHD_HTTP_INTERNAL_SERVER_ERROR. + */ + TALER_EC_TRACK_TRANSFER_WIRE_FEE_NOT_FOUND = 1703, + + /** + * The exchange found a wire fee that was above the total transfer + * value (and thus could not have been charged). This response is + * provided with HTTP status code MHD_HTTP_INTERNAL_SERVER_ERROR. + */ + TALER_EC_TRACK_TRANSFER_WIRE_FEE_INCONSISTENT = 1704, /** * The exchange found internally inconsistent fee data when @@ -1145,7 +1158,7 @@ enum TALER_ErrorCode /** * The backend encountered an error while trying to store the - * h_proposal_data into the database. + * h_proposal_data into the database. * The response is provided with HTTP status code MHD_HTTP_INTERNAL_SERVER_ERROR. */ TALER_EC_PROPOSAL_STORE_DB_ERROR = 2501, diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h index 05256ddb9..3ac4069f3 100644 --- a/src/include/taler_exchange_service.h +++ b/src/include/taler_exchange_service.h @@ -1130,6 +1130,7 @@ struct TALER_EXCHANGE_TrackTransferHandle; * @param execution_time time when the exchange claims to have performed the wire transfer * @param total_amount total amount of the wire transfer, or NULL if the exchange could * not provide any @a wtid (set only if @a http_status is #MHD_HTTP_OK) + * @param wire_fee wire fee that was charged by the exchange * @param details_length length of the @a details array * @param details array with details about the combined transactions */ @@ -1142,6 +1143,7 @@ typedef void const struct GNUNET_HashCode *h_wire, struct GNUNET_TIME_Absolute execution_time, const struct TALER_Amount *total_amount, + const struct TALER_Amount *wire_fee, unsigned int details_length, const struct TALER_TrackTransferDetails *details); diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index fa0c48a8b..e7ba06f4c 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -762,6 +762,7 @@ typedef void * * @param cls closure * @param merchant_pub public key of the merchant (should be same for all callbacks with the same @e cls) + * @param wire_method which wire plugin was used for the transfer? * @param h_wire hash of wire transfer details of the merchant (should be same for all callbacks with the same @e cls) * @param exec_time execution time of the wire transfer (should be same for all callbacks with the same @e cls) * @param h_proposal_data which proposal was this payment about @@ -772,6 +773,7 @@ typedef void typedef void (*TALER_EXCHANGEDB_WireTransferDataCallback)(void *cls, const struct TALER_MerchantPublicKeyP *merchant_pub, + const char *wire_method, const struct GNUNET_HashCode *h_wire, struct GNUNET_TIME_Absolute exec_time, const struct GNUNET_HashCode *h_proposal_data, @@ -1520,6 +1522,54 @@ struct TALER_EXCHANGEDB_Plugin struct GNUNET_TIME_Absolute execution_time); + /** + * Insert wire transfer fee into database. + * + * @param cls closure + * @param session database connection + * @param wire_method which wire method is the fee about? + * @param start_date when does the fee go into effect + * @param end_date when does the fee end being valid + * @param wire_fee how high is the wire transfer fee + * @param master_sig signature over the above by the exchange master key + * @return #GNUNET_OK on success, #GNUNET_NO if the record exists, + * #GNUNET_SYSERR on failure + */ + int + (*insert_wire_fee)(void *cls, + struct TALER_EXCHANGEDB_Session *session, + const char *wire_method, + struct GNUNET_TIME_Absolute start_date, + struct GNUNET_TIME_Absolute end_date, + const struct TALER_Amount *wire_fee, + const struct TALER_MasterSignatureP *master_sig); + + + /** + * Obtain wire fee from database. + * + * @param cls closure + * @param session database connection + * @param type type of wire transfer the fee applies for + * @param date for which date do we want the fee? + * @param[out] start_date when does the fee go into effect + * @param[out] end_date when does the fee end being valid + * @param[out] wire_fee how high is the wire transfer fee + * @param[out] master_sig signature over the above by the exchange master key + * @return #GNUNET_OK on success, #GNUNET_NO if no fee is known + * #GNUNET_SYSERR on failure + */ + int + (*get_wire_fee) (void *cls, + struct TALER_EXCHANGEDB_Session *session, + const char *type, + struct GNUNET_TIME_Absolute date, + struct GNUNET_TIME_Absolute *start_date, + struct GNUNET_TIME_Absolute *end_date, + struct TALER_Amount *wire_fee, + struct TALER_MasterSignatureP *master_sig); + + /** * Function called to insert wire transfer commit data into the DB. * diff --git a/src/include/taler_signatures.h b/src/include/taler_signatures.h index 35967399d..8659deb3a 100644 --- a/src/include/taler_signatures.h +++ b/src/include/taler_signatures.h @@ -1003,6 +1003,11 @@ struct TALER_WireDepositDataPS */ struct TALER_AmountNBO total; + /** + * Wire fee that was charged. + */ + struct TALER_AmountNBO wire_fee; + /** * Public key of the merchant (for all aggregated transactions). */