implementing #4929

This commit is contained in:
Christian Grothoff 2017-03-04 16:49:33 +01:00
parent f406f96129
commit 6ab67a3a76
No known key found for this signature in database
GPG Key ID: 939E6BE1E29FC3CC
15 changed files with 573 additions and 58 deletions

View File

@ -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);
}

View File

@ -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
},

View File

@ -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? */

View File

@ -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;
}

View File

@ -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),

View File

@ -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);

View File

@ -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/"

View File

@ -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,

View File

@ -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/

View File

@ -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;

View File

@ -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)));

View File

@ -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,

View File

@ -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);

View File

@ -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.
*

View File

@ -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).
*/