summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/auditor/taler-auditor.c1615
-rw-r--r--src/auditordb/plugin_auditordb_postgres.c89
-rw-r--r--src/include/taler_amount_lib.h12
-rw-r--r--src/util/amount.c50
4 files changed, 1027 insertions, 739 deletions
diff --git a/src/auditor/taler-auditor.c b/src/auditor/taler-auditor.c
index 0427f12a..971f6e51 100644
--- a/src/auditor/taler-auditor.c
+++ b/src/auditor/taler-auditor.c
@@ -23,6 +23,14 @@
* the wire transfers from the bank. This needs to be checked separately!
* - Similarly, we do not check that the outgoing wire transfers match those
* given in the 'wire_out' table. This needs to be checked separately!
+ *
+ * KNOWN BUGS:
+ * - resolve HACK! -- need extra serial_id in 'pp' as we go over reserve_out twice!
+ * - risk is not calculated correctly
+ * - calculate, store and report aggregation fee balance!
+ * - error handling if denomination keys are used that are not known to the
+ * auditor is, eh, awful / non-existent. We just throw the DB's constraint
+ * violation back at the user. Great UX.
*/
#include "platform.h"
#include <gnunet/gnunet_util_lib.h>
@@ -251,20 +259,48 @@ static void
report_reserve_balance (const struct TALER_Amount *total_balance,
const struct TALER_Amount *total_fee_balance)
{
- char *balance;
- char *fees;
-
- balance = TALER_amount_to_string (total_balance);
- fees = TALER_amount_to_string (total_fee_balance);
// TODO: implement proper reporting logic writing to file.
GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
"Total escrow balance to be held for reserves: %s\n",
- balance);
+ TALER_amount2s (total_balance));
GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
"Total profits made from reserves: %s\n",
- fees);
- GNUNET_free (fees);
- GNUNET_free (balance);
+ TALER_amount2s (total_fee_balance));
+}
+
+
+/**
+ * Report state of denomination processing.
+ *
+ * @param total_balance total value of outstanding coins
+ * @param total_risk total value of issued coins in active denominations
+ * @param deposit_fees total deposit fees collected
+ * @param melt_fees total melt fees collected
+ * @param refund_fees total refund fees collected
+ */
+static void
+report_denomination_balance (const struct TALER_Amount *total_balance,
+ const struct TALER_Amount *total_risk,
+ const struct TALER_Amount *deposit_fees,
+ const struct TALER_Amount *melt_fees,
+ const struct TALER_Amount *refund_fees)
+{
+ // TODO: implement proper reporting logic writing to file.
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "Final balance for all denominations is %s\n",
+ TALER_amount2s (total_balance));
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "Risk from active operations is %s\n",
+ TALER_amount2s (total_risk));
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "Deposit fee profits are %s\n",
+ TALER_amount2s (deposit_fees));
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "Melt fee profits are %s\n",
+ TALER_amount2s (melt_fees));
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "Refund fee profits are %s\n",
+ TALER_amount2s (refund_fees));
}
@@ -321,6 +357,16 @@ get_denomination_info (const struct TALER_DenominationPublicKey *denom_pub,
*dki = NULL;
return ret;
}
+ {
+ struct TALER_Amount value;
+
+ TALER_amount_ntoh (&value,
+ &dkip->properties.value);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Tracking denomination `%s' (%s)\n",
+ GNUNET_h2s (dh),
+ TALER_amount2s (&value));
+ }
*dki = dkip;
GNUNET_assert (GNUNET_OK ==
GNUNET_CONTAINER_multihashmap_put (denominations,
@@ -346,6 +392,9 @@ free_dk_info (void *cls,
{
struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki = value;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Done with denomination `%s'\n",
+ GNUNET_h2s (key));
GNUNET_free (dki);
return GNUNET_OK;
}
@@ -465,6 +514,10 @@ load_auditor_reserve_summary (struct ReserveSummary *rs)
GNUNET_assert (GNUNET_OK ==
TALER_amount_get_zero (rs->total_in.currency,
&rs->a_withdraw_fee_balance));
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Creating fresh reserve `%s' with starting balance %s\n",
+ TALER_B2S (&rs->reserve_pub),
+ TALER_amount2s (&rs->a_balance));
return GNUNET_OK;
}
rs->had_ri = GNUNET_YES;
@@ -482,6 +535,10 @@ load_auditor_reserve_summary (struct ReserveSummary *rs)
GNUNET_break (0);
return GNUNET_SYSERR;
}
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Auditor remembers reserve `%s' has balance %s\n",
+ TALER_B2S (&rs->reserve_pub),
+ TALER_amount2s (&rs->a_balance));
return GNUNET_OK;
}
@@ -573,6 +630,10 @@ handle_reserve_in (void *cls,
&rs->total_in,
credit));
}
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Additional incoming wire transfer for reserve `%s' of %s\n",
+ TALER_B2S (reserve_pub),
+ TALER_amount2s (credit));
expiry = GNUNET_TIME_absolute_add (execution_date,
TALER_IDLE_RESERVE_EXPIRATION_TIME);
rs->a_expiration_date = GNUNET_TIME_absolute_max (rs->a_expiration_date,
@@ -705,7 +766,10 @@ handle_reserve_out (void *cls,
&rs->total_out,
amount_with_fee));
}
-
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Reserve `%s' reduced by %s from withdraw\n",
+ TALER_B2S (reserve_pub),
+ TALER_amount2s (amount_with_fee));
TALER_amount_ntoh (&withdraw_fee,
&dki->properties.fee_withdraw);
GNUNET_assert (GNUNET_OK ==
@@ -794,6 +858,7 @@ verify_reserve_balance (void *cls,
if (0 == GNUNET_TIME_absolute_get_remaining (rs->a_expiration_date).rel_value_us)
{
/* TODO: handle case where reserve is expired! (#4956) */
+ GNUNET_break (0); /* not implemented */
/* NOTE: we may or may not have seen the wire-back transfer at this time,
as the expiration may have just now happened.
(That is, after we add the table structures and the logic to track
@@ -806,6 +871,10 @@ verify_reserve_balance (void *cls,
/* TODO: balance is zero, drop reserve details (and then do not update/insert) */
if (rs->had_ri)
{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Final balance of reserve `%s' is %s, dropping it\n",
+ TALER_B2S (&rs->reserve_pub),
+ TALER_amount2s (&balance));
ret = adb->del_reserve_info (adb->cls,
asession,
&rs->reserve_pub,
@@ -822,13 +891,21 @@ verify_reserve_balance (void *cls,
goto cleanup;
}
}
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Final balance of reserve `%s' is %s, no need to remember it\n",
+ TALER_B2S (&rs->reserve_pub),
+ TALER_amount2s (&balance));
+ }
ret = GNUNET_OK;
goto cleanup;
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Reserve balance `%s' OK\n",
- TALER_B2S (&rs->reserve_pub));
+ "Remembering final balance of reserve `%s' as %s\n",
+ TALER_B2S (&rs->reserve_pub),
+ TALER_amount2s (&balance));
/* Add withdraw fees we encountered to totals */
if (GNUNET_YES !=
@@ -898,6 +975,8 @@ analyze_reserves (void *cls)
struct ReserveContext rc;
int ret;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Analyzing reserves\n");
ret = adb->get_reserve_summary (adb->cls,
asession,
&master_pub,
@@ -978,6 +1057,678 @@ analyze_reserves (void *cls)
}
+/* *********************** Analyze aggregations ******************** */
+/* This logic checks that the aggregator did the right thing
+ paying each merchant what they were due (and on time). */
+
+
+/**
+ * Information we keep per loaded wire plugin.
+ */
+struct WirePlugin
+{
+
+ /**
+ * Kept in a DLL.
+ */
+ struct WirePlugin *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct WirePlugin *prev;
+
+ /**
+ * Name of the wire method.
+ */
+ char *type;
+
+ /**
+ * Handle to the wire plugin.
+ */
+ struct TALER_WIRE_Plugin *plugin;
+
+};
+
+
+/**
+ * Closure for callbacks during #analyze_merchants().
+ */
+struct AggregationContext
+{
+
+ /**
+ * DLL of wire plugins encountered.
+ */
+ struct WirePlugin *wire_head;
+
+ /**
+ * DLL of wire plugins encountered.
+ */
+ struct WirePlugin *wire_tail;
+
+};
+
+
+/**
+ * Find the relevant wire plugin.
+ *
+ * @param ac context to search
+ * @param type type of the wire plugin to load
+ * @return NULL on error
+ */
+static struct TALER_WIRE_Plugin *
+get_wire_plugin (struct AggregationContext *ac,
+ const char *type)
+{
+ struct WirePlugin *wp;
+ struct TALER_WIRE_Plugin *plugin;
+
+ for (wp = ac->wire_head; NULL != wp; wp = wp->next)
+ if (0 == strcmp (type,
+ wp->type))
+ return wp->plugin;
+ plugin = TALER_WIRE_plugin_load (cfg,
+ type);
+ if (NULL == plugin)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to locate wire plugin for `%s'\n",
+ type);
+ return NULL;
+ }
+ wp = GNUNET_new (struct WirePlugin);
+ wp->type = GNUNET_strdup (type);
+ wp->plugin = plugin;
+ GNUNET_CONTAINER_DLL_insert (ac->wire_head,
+ ac->wire_tail,
+ wp);
+ return plugin;
+}
+
+
+/**
+ * Closure for #wire_transfer_information_cb.
+ */
+struct WireCheckContext
+{
+
+ /**
+ * Corresponding merchant context.
+ */
+ struct AggregationContext *ac;
+
+ /**
+ * Total deposits claimed by all transactions that were aggregated
+ * under the given @e wtid.
+ */
+ struct TALER_Amount total_deposits;
+
+ /**
+ * Hash of the wire transfer details of the receiver.
+ */
+ struct GNUNET_HashCode h_wire;
+
+ /**
+ * Execution time of the wire transfer.
+ */
+ struct GNUNET_TIME_Absolute date;
+
+ /**
+ * Wire method used for the transfer.
+ */
+ const char *method;
+
+ /**
+ * Set to #GNUNET_SYSERR if there are inconsistencies.
+ */
+ int ok;
+
+};
+
+
+/**
+ * Check coin's transaction history for plausibility. Does NOT check
+ * the signatures (those are checked independently), but does calculate
+ * the amounts for the aggregation table and checks that the total
+ * claimed coin value is within the value of the coin's denomination.
+ *
+ * @param coin_pub public key of the coin (for reporting)
+ * @param h_proposal_data hash of the proposal for which we calculate the amount
+ * @param merchant_pub public key of the merchant (who is allowed to issue refunds)
+ * @param dki denomination information about the coin
+ * @param tl_head head of transaction history to verify
+ * @param[out] merchant_gain amount the coin contributes to the wire transfer to the merchant
+ * @param[out] merchant_fees fees the exchange charged the merchant for the transaction(s)
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
+ */
+static int
+check_transaction_history (const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct GNUNET_HashCode *h_proposal_data,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki,
+ const struct TALER_EXCHANGEDB_TransactionList *tl_head,
+ struct TALER_Amount *merchant_gain,
+ struct TALER_Amount *merchant_fees)
+{
+ struct TALER_Amount expenditures;
+ struct TALER_Amount refunds;
+ struct TALER_Amount spent;
+ struct TALER_Amount value;
+ struct TALER_Amount merchant_loss;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Checking transaction history of coin %s\n",
+ TALER_B2S (coin_pub));
+
+ GNUNET_assert (NULL != tl_head);
+ TALER_amount_get_zero (currency,
+ &expenditures);
+ TALER_amount_get_zero (currency,
+ &refunds);
+ TALER_amount_get_zero (currency,
+ merchant_gain);
+ TALER_amount_get_zero (currency,
+ merchant_fees);
+ TALER_amount_get_zero (currency,
+ &merchant_loss);
+ /* Go over transaction history to compute totals; note that we do not
+ know the order, so instead of subtracting we compute positive
+ (deposit, melt) and negative (refund) values separately here,
+ and then subtract the negative from the positive after the loop. */
+ for (const struct TALER_EXCHANGEDB_TransactionList *tl = tl_head;NULL != tl;tl = tl->next)
+ {
+ const struct TALER_Amount *amount_with_fee;
+ const struct TALER_Amount *fee;
+ const struct TALER_AmountNBO *fee_dki;
+ struct TALER_Amount tmp;
+
+ switch (tl->type) {
+ case TALER_EXCHANGEDB_TT_DEPOSIT:
+ amount_with_fee = &tl->details.deposit->amount_with_fee;
+ fee = &tl->details.deposit->deposit_fee;
+ fee_dki = &dki->properties.fee_deposit;
+ if (GNUNET_OK !=
+ TALER_amount_add (&expenditures,
+ &expenditures,
+ amount_with_fee))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ /* Check if this deposit is within the remit of the aggregation
+ we are investigating, if so, include it in the totals. */
+ if ( (0 == memcmp (merchant_pub,
+ &tl->details.deposit->merchant_pub,
+ sizeof (struct TALER_MerchantPublicKeyP))) &&
+ (0 == memcmp (h_proposal_data,
+ &tl->details.deposit->h_proposal_data,
+ sizeof (struct GNUNET_HashCode))) )
+ {
+ struct TALER_Amount amount_without_fee;
+
+ if (GNUNET_OK !=
+ TALER_amount_subtract (&amount_without_fee,
+ amount_with_fee,
+ fee))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_amount_add (merchant_gain,
+ merchant_gain,
+ &amount_without_fee))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Detected applicable deposit of %s\n",
+ TALER_amount2s (&amount_without_fee));
+ if (GNUNET_OK !=
+ TALER_amount_add (merchant_fees,
+ merchant_fees,
+ fee))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ break;
+ case TALER_EXCHANGEDB_TT_REFRESH_MELT:
+ amount_with_fee = &tl->details.melt->amount_with_fee;
+ fee = &tl->details.melt->melt_fee;
+ fee_dki = &dki->properties.fee_refresh;
+ if (GNUNET_OK !=
+ TALER_amount_add (&expenditures,
+ &expenditures,
+ amount_with_fee))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ break;
+ case TALER_EXCHANGEDB_TT_REFUND:
+ amount_with_fee = &tl->details.refund->refund_amount;
+ fee = &tl->details.refund->refund_fee;
+ fee_dki = &dki->properties.fee_refund;
+ if (GNUNET_OK !=
+ TALER_amount_add (&refunds,
+ &refunds,
+ amount_with_fee))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_amount_add (&expenditures,
+ &expenditures,
+ fee))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ /* Check if this refund is within the remit of the aggregation
+ we are investigating, if so, include it in the totals. */
+ if ( (0 == memcmp (merchant_pub,
+ &tl->details.refund->merchant_pub,
+ sizeof (struct TALER_MerchantPublicKeyP))) &&
+ (0 == memcmp (h_proposal_data,
+ &tl->details.refund->h_proposal_data,
+ sizeof (struct GNUNET_HashCode))) )
+ {
+ if (GNUNET_OK !=
+ TALER_amount_add (&merchant_loss,
+ &merchant_loss,
+ amount_with_fee))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Detected applicable refund of %s\n",
+ TALER_amount2s (amount_with_fee));
+ if (GNUNET_OK !=
+ TALER_amount_add (merchant_fees,
+ merchant_fees,
+ fee))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ break;
+ }
+
+ /* Check that the fees given in the transaction list and in dki match */
+ TALER_amount_ntoh (&tmp,
+ fee_dki);
+ if (0 !=
+ TALER_amount_cmp (&tmp,
+ fee))
+ {
+ /* Disagreement in fee structure within DB, should be impossible! */
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ } /* for 'tl' */
+
+ /* Calculate total balance change, i.e. expenditures minus refunds */
+ if (GNUNET_SYSERR ==
+ TALER_amount_subtract (&spent,
+ &expenditures,
+ &refunds))
+ {
+ /* refunds above expenditures? Bad! */
+ report_coin_inconsistency (coin_pub,
+ &expenditures,
+ &refunds,
+ "could not subtract refunded amount from expenditures");
+ return GNUNET_SYSERR;
+ }
+
+ /* Now check that 'spent' is less or equal than total coin value */
+ TALER_amount_ntoh (&value,
+ &dki->properties.value);
+ if (1 == TALER_amount_cmp (&spent,
+ &value))
+ {
+ /* spent > value */
+ report_coin_inconsistency (coin_pub,
+ &spent,
+ &value,
+ "accepted deposits (minus refunds) exceeds denomination value");
+ return GNUNET_SYSERR;
+ }
+
+ /* Finally, update @a merchant_gain by subtracting what he "lost" from refunds */
+ if (GNUNET_SYSERR ==
+ TALER_amount_subtract (merchant_gain,
+ merchant_gain,
+ &merchant_loss))
+ {
+ /* refunds above deposits? Bad! */
+ report_coin_inconsistency (coin_pub,
+ merchant_gain,
+ &merchant_loss,
+ "merchant was granted more refunds than he deposited");
+ return GNUNET_SYSERR;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Coin %s contributes %s to contract %s\n",
+ TALER_B2S (coin_pub),
+ TALER_amount2s (merchant_gain),
+ GNUNET_h2s (h_proposal_data));
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called with the results of the lookup of the
+ * transaction data associated with a wire transfer identifier.
+ *
+ * @param cls a `struct WireCheckContext`
+ * @param rowid which row in the table is the information from (for diagnostics)
+ * @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
+ * @param coin_pub which public key was this payment about
+ * @param coin_value amount contributed by this coin in total (with fee)
+ * @param coin_fee applicable fee for this coin
+ */
+static void
+wire_transfer_information_cb (void *cls,
+ uint64_t rowid,
+ 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,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_Amount *coin_value,
+ const struct TALER_Amount *coin_fee)
+{
+ struct WireCheckContext *wcc = cls;
+ const struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki;
+ struct TALER_Amount contribution;
+ struct TALER_Amount computed_value;
+ struct TALER_Amount computed_fees;
+ struct TALER_Amount coin_value_without_fee;
+ struct TALER_EXCHANGEDB_TransactionList *tl;
+ const struct TALER_CoinPublicInfo *coin;
+
+ /* Obtain coin's transaction history */
+ tl = edb->get_coin_transactions (edb->cls,
+ esession,
+ coin_pub);
+ if (NULL == tl)
+ {
+ wcc->ok = GNUNET_SYSERR;
+ report_row_inconsistency ("aggregation",
+ rowid,
+ "no transaction history for coin claimed in aggregation");
+ return;
+ }
+
+ /* Obtain general denomination information about the coin */
+ coin = NULL;
+ switch (tl->type)
+ {
+ case TALER_EXCHANGEDB_TT_DEPOSIT:
+ coin = &tl->details.deposit->coin;
+ break;
+ case TALER_EXCHANGEDB_TT_REFRESH_MELT:
+ coin = &tl->details.melt->coin;
+ break;
+ case TALER_EXCHANGEDB_TT_REFUND:
+ coin = &tl->details.refund->coin;
+ break;
+ }
+ GNUNET_assert (NULL != coin); /* hard check that switch worked */
+ if (GNUNET_OK !=
+ get_denomination_info (&coin->denom_pub,
+ &dki,
+ NULL))
+ {
+ /* This should be impossible from database constraints */
+ GNUNET_break (0);
+ edb->free_coin_transaction_list (edb->cls,
+ tl);
+ wcc->ok = GNUNET_SYSERR;
+ report_row_inconsistency ("aggregation",
+ rowid,
+ "could not find denomination key for coin claimed in aggregation");
+ return;
+ }
+
+ /* Check transaction history to see if it supports aggregate valuation */
+ check_transaction_history (coin_pub,
+ h_proposal_data,
+ merchant_pub,
+ dki,
+ tl,
+ &computed_value,
+ &computed_fees);
+ if (GNUNET_SYSERR ==
+ TALER_amount_subtract (&coin_value_without_fee,
+ coin_value,
+ coin_fee))
+ {
+ wcc->ok = GNUNET_SYSERR;
+ report_row_inconsistency ("aggregation",
+ rowid,
+ "inconsistent coin value and fee claimed in aggregation");
+ return;
+ }
+ if (0 !=
+ TALER_amount_cmp (&computed_value,
+ &coin_value_without_fee))
+ {
+ wcc->ok = GNUNET_SYSERR;
+ report_row_inconsistency ("aggregation",
+ rowid,
+ "coin transaction history and aggregation disagree about coin's contribution");
+ }
+ if (0 !=
+ TALER_amount_cmp (&computed_fees,
+ coin_fee))
+ {
+ wcc->ok = GNUNET_SYSERR;
+ report_row_inconsistency ("aggregation",
+ rowid,
+ "coin transaction history and aggregation disagree about applicable fees");
+ }
+ edb->free_coin_transaction_list (edb->cls,
+ tl);
+
+ /* Check other details of wire transfer match */
+ if (0 != strcmp (wire_method,
+ wcc->method))
+ {
+ wcc->ok = GNUNET_SYSERR;
+ report_row_inconsistency ("aggregation",
+ rowid,
+ "wire method of aggregate do not match wire transfer");
+ }
+ if (0 != memcmp (h_wire,
+ &wcc->h_wire,
+ sizeof (struct GNUNET_HashCode)))
+ {
+ wcc->ok = GNUNET_SYSERR;
+ report_row_inconsistency ("aggregation",
+ rowid,
+ "account details of aggregate do not match account details of wire transfer");
+ return;
+ }
+ if (exec_time.abs_value_us != wcc->date.abs_value_us)
+ {
+ /* This should be impossible from database constraints */
+ GNUNET_break (0);
+ wcc->ok = GNUNET_SYSERR;
+ report_row_inconsistency ("aggregation",
+ rowid,
+ "date given in aggregate does not match wire transfer date");
+ return;
+ }
+ if (GNUNET_SYSERR ==
+ TALER_amount_subtract (&contribution,
+ coin_value,
+ coin_fee))
+ {
+ wcc->ok = GNUNET_SYSERR;
+ report_row_inconsistency ("aggregation",
+ rowid,
+ "could not calculate contribution of coin");
+ return;
+ }
+
+ /* Add coin's contribution to total aggregate value */
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_add (&wcc->total_deposits,
+ &wcc->total_deposits,
+ &contribution));
+}
+
+
+/**
+ * Check that a wire transfer made by the exchange is valid
+ * (has matching deposits).
+ *
+ * @param cls a `struct AggregationContext`
+ * @param rowid identifier of the respective row in the database
+ * @param date timestamp of the wire transfer (roughly)
+ * @param wtid wire transfer subject
+ * @param wire wire transfer details of the receiver
+ * @param amount amount that was wired
+ */
+static void
+check_wire_out_cb (void *cls,
+ uint64_t rowid,
+ struct GNUNET_TIME_Absolute date,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const json_t *wire,
+ const struct TALER_Amount *amount)
+{
+ struct AggregationContext *ac = cls;
+ struct WireCheckContext wcc;
+ json_t *method;
+ struct TALER_WIRE_Plugin *plugin;
+
+ /* should be monotonically increasing */
+ GNUNET_assert (rowid >= pp.last_wire_out_serial_id);
+ pp.last_wire_out_serial_id = rowid + 1;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Checking wire transfer %s over %s performed on %s\n",
+ TALER_B2S (wtid),
+ TALER_amount2s (amount),
+ GNUNET_STRINGS_absolute_time_to_string (date));
+ wcc.ac = ac;
+ method = json_object_get (wire,
+ "type");
+ if ( (NULL == method) ||
+ (! json_is_string (method)) )
+ {
+ report_row_inconsistency ("wire_out",
+ rowid,
+ "specified wire address lacks type");
+ return;
+ }
+ wcc.method = json_string_value (method);
+ wcc.ok = GNUNET_OK;
+ wcc.date = date;
+ TALER_amount_get_zero (amount->currency,
+ &wcc.total_deposits);
+ TALER_JSON_hash (wire,
+ &wcc.h_wire);
+ edb->lookup_wire_transfer (edb->cls,
+ esession,
+ wtid,
+ &wire_transfer_information_cb,
+ &wcc);
+ if (GNUNET_OK != wcc.ok)
+ {
+ report_row_inconsistency ("wire_out",
+ rowid,
+ "audit of associated transactions failed");
+ }
+ plugin = get_wire_plugin (ac,
+ wcc.method);
+ if (NULL == plugin)
+ {
+ report_row_inconsistency ("wire_out",
+ rowid,
+ "could not load required wire plugin to validate");
+ return;
+ }
+ if (GNUNET_SYSERR ==
+ plugin->amount_round (plugin->cls,
+ &wcc.total_deposits))
+ {
+ report_row_minor_inconsistency ("wire_out",
+ rowid,
+ "wire plugin failed to round given amount");
+ }
+ if (0 != TALER_amount_cmp (amount,
+ &wcc.total_deposits))
+ {
+ report_wire_out_inconsistency (wire,
+ rowid,
+ &wcc.total_deposits,
+ amount,
+ "computed amount inconsistent with wire amount");
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Wire transfer %s is OK\n",
+ TALER_B2S (wtid));
+}
+
+
+/**
+ * Analyze the exchange aggregator's payment processing.
+ *
+ * @param cls closure
+ * @param int #GNUNET_OK on success, #GNUNET_SYSERR on hard errors
+ */
+static int
+analyze_aggregations (void *cls)
+{
+ struct AggregationContext ac;
+ struct WirePlugin *wc;
+ int ret;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Analyzing aggregations\n");
+ ret = GNUNET_OK;
+ ac.wire_head = NULL;
+ ac.wire_tail = NULL;
+ if (GNUNET_SYSERR ==
+ edb->select_wire_out_above_serial_id (edb->cls,
+ esession,
+ pp.last_wire_out_serial_id,
+ &check_wire_out_cb,
+ &ac))
+ {
+ GNUNET_break (0);
+ ret = GNUNET_SYSERR;
+ }
+ while (NULL != (wc = ac.wire_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (ac.wire_head,
+ ac.wire_tail,
+ wc);
+ TALER_WIRE_plugin_unload (wc->plugin);
+ GNUNET_free (wc->type);
+ GNUNET_free (wc);
+ }
+ return ret;
+}
+
+
/* ************************* Analyze coins ******************** */
/* This logic checks that the exchange did the right thing for each
coin, checking deposits, refunds, refresh* and known_coins
@@ -1087,6 +1838,10 @@ init_denomination (const struct GNUNET_HashCode *denom_hash,
if (GNUNET_OK == ret)
{
ds->in_db = GNUNET_YES;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Starting balance for denomination `%s' is %s\n",
+ GNUNET_h2s (denom_hash),
+ TALER_amount2s (&ds->denom_balance));
return GNUNET_OK;
}
if (GNUNET_SYSERR == ret)
@@ -1100,6 +1855,10 @@ init_denomination (const struct GNUNET_HashCode *denom_hash,
GNUNET_assert (GNUNET_OK ==
TALER_amount_get_zero (currency,
&ds->denom_risk));
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Starting balance for denomination `%s' is %s\n",
+ GNUNET_h2s (denom_hash),
+ TALER_amount2s (&ds->denom_balance));
return GNUNET_OK;
}
@@ -1204,6 +1963,10 @@ sync_denomination (void *cls,
(0 != ds->denom_balance.fraction) ) )
{
/* book denom_balance coin expiration profits! */
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Denomination `%s' expired, booking %s in expiration profits\n",
+ GNUNET_h2s (denom_hash),
+ TALER_amount2s (&ds->denom_balance));
if (GNUNET_OK !=
adb->insert_historic_denom_revenue (adb->cls,
asession,
@@ -1221,6 +1984,10 @@ sync_denomination (void *cls,
}
else
{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Final balance for denomination `%s' is %s\n",
+ GNUNET_h2s (denom_hash),
+ TALER_amount2s (&ds->denom_balance));
if (ds->in_db)
ret = adb->update_denomination_balance (adb->cls,
asession,
@@ -1297,6 +2064,10 @@ withdraw_cb (void *cls,
&dh);
TALER_amount_ntoh (&value,
&dki->properties.value);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Issued coin in denomination `%s' of total value %s\n",
+ GNUNET_h2s (&dh),
+ TALER_amount2s (&value));
if (GNUNET_OK !=
TALER_amount_add (&ds->denom_balance,
&ds->denom_balance,
@@ -1305,6 +2076,10 @@ withdraw_cb (void *cls,
GNUNET_break (0);
return GNUNET_SYSERR;
}
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "New balance of denomination `%s' is %s\n",
+ GNUNET_h2s (&dh),
+ TALER_amount2s (&ds->denom_balance));
if (GNUNET_OK !=
TALER_amount_add (&cc->total_denom_balance,
&cc->total_denom_balance,
@@ -1381,6 +2156,11 @@ refresh_session_cb (void *cls,
"invalid signature for coin melt");
return GNUNET_OK;
}
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Melting coin %s in denomination `%s' of value %s\n",
+ TALER_B2S (coin_pub),
+ GNUNET_h2s (&dki->properties.denom_hash),
+ TALER_amount2s (amount_with_fee));
{
struct TALER_DenominationPublicKey new_dp[num_newcoins];
@@ -1422,29 +2202,28 @@ refresh_session_cb (void *cls,
if (err)
return GNUNET_SYSERR;
+ /* calculate total refresh cost */
for (unsigned int i=0;i<num_newcoins;i++)
{
/* update cost of refresh */
+ struct TALER_Amount fee;
+ struct TALER_Amount value;
+
+ TALER_amount_ntoh (&fee,
+ &new_dki[i]->properties.fee_withdraw);
+ TALER_amount_ntoh (&value,
+ &new_dki[i]->properties.value);
+ if ( (GNUNET_OK !=
+ TALER_amount_add (&refresh_cost,
+ &refresh_cost,
+ &fee)) ||
+ (GNUNET_OK !=
+ TALER_amount_add (&refresh_cost,
+ &refresh_cost,
+ &value)) )
{
- struct TALER_Amount fee;
- struct TALER_Amount value;
-
- TALER_amount_ntoh (&fee,
- &new_dki[i]->properties.fee_withdraw);
- TALER_amount_ntoh (&value,
- &new_dki[i]->properties.value);
- if ( (GNUNET_OK !=
- TALER_amount_add (&refresh_cost,
- &refresh_cost,
- &fee)) ||
- (GNUNET_OK !=
- TALER_amount_add (&refresh_cost,
- &refresh_cost,
- &value)) )
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
}
}
@@ -1486,6 +2265,10 @@ refresh_session_cb (void *cls,
&new_dki[i]->properties.denom_hash);
TALER_amount_ntoh (&value,
&new_dki[i]->properties.value);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Created fresh coin in denomination `%s' of value %s\n",
+ GNUNET_h2s (&new_dki[i]->properties.denom_hash),
+ TALER_amount2s (&value));
if (GNUNET_OK !=
TALER_amount_add (&dsi->denom_balance,
&dsi->denom_balance,
@@ -1494,6 +2277,10 @@ refresh_session_cb (void *cls,
GNUNET_break (0);
return GNUNET_SYSERR;
}
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "New balance of denomination `%s' is %s\n",
+ GNUNET_h2s (&new_dki[i]->properties.denom_hash),
+ TALER_amount2s (&dsi->denom_balance));
if (GNUNET_OK !=
TALER_amount_add (&cc->total_denom_balance,
&cc->total_denom_balance,
@@ -1509,7 +2296,7 @@ refresh_session_cb (void *cls,
dso = get_denomination_summary (cc,
dki,
&dki->properties.denom_hash);
- if (GNUNET_OK !=
+ if (GNUNET_SYSERR ==
TALER_amount_subtract (&tmp,
&dso->denom_balance,
amount_with_fee))
@@ -1518,6 +2305,10 @@ refresh_session_cb (void *cls,
return GNUNET_SYSERR;
}
dso->denom_balance = tmp;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "New balance of denomination `%s' after melt is %s\n",
+ GNUNET_h2s (&dki->properties.denom_hash),
+ TALER_amount2s (&dso->denom_balance));
/* update global up melt fees */
{
@@ -1623,12 +2414,17 @@ deposit_cb (void *cls,
"invalid signature for coin deposit");
return GNUNET_OK;
}
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Deposited coin %s in denomination `%s' of value %s\n",
+ TALER_B2S (coin_pub),
+ GNUNET_h2s (&dki->properties.denom_hash),
+ TALER_amount2s (amount_with_fee));
/* update old coin's denomination balance */
ds = get_denomination_summary (cc,
dki,
&dki->properties.denom_hash);
- if (GNUNET_OK !=
+ if (GNUNET_SYSERR ==
TALER_amount_subtract (&tmp,
&ds->denom_balance,
amount_with_fee))
@@ -1637,6 +2433,10 @@ deposit_cb (void *cls,
return GNUNET_SYSERR;
}
ds->denom_balance = tmp;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "New balance of denomination `%s' after deposit is %s\n",
+ GNUNET_h2s (&dki->properties.denom_hash),
+ TALER_amount2s (&ds->denom_balance));
/* update global up melt fees */
{
@@ -1740,6 +2540,12 @@ refund_cb (void *cls,
return GNUNET_OK;
}
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Refunding coin %s in denomination `%s' value %s\n",
+ TALER_B2S (coin_pub),
+ GNUNET_h2s (&dki->properties.denom_hash),
+ TALER_amount2s (amount_with_fee));
+
/* update coin's denomination balance */
ds = get_denomination_summary (cc,
dki,
@@ -1752,6 +2558,10 @@ refund_cb (void *cls,
GNUNET_break (0);
return GNUNET_SYSERR;
}
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "New balance of denomination `%s' after refund is %s\n",
+ GNUNET_h2s (&dki->properties.denom_hash),
+ TALER_amount2s (&ds->denom_balance));
/* update total refund fee balance */
if (GNUNET_OK !=
@@ -1779,6 +2589,9 @@ analyze_coins (void *cls)
struct CoinContext cc;
int dret;
+ pp.last_reserve_out_serial_id = 0; // HACK! FIXME!
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Analyzing coins\n");
/* setup 'cc' */
cc.ret = GNUNET_OK;
cc.denom_summaries = GNUNET_CONTAINER_multihashmap_create (256,
@@ -1827,6 +2640,18 @@ analyze_coins (void *cls)
return GNUNET_SYSERR;
}
+ /* process refunds */
+ if (GNUNET_SYSERR ==
+ edb->select_refunds_above_serial_id (edb->cls,
+ esession,
+ pp.last_refund_serial_id,
+ &refund_cb,
+ &cc))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
/* process refreshs */
if (GNUNET_SYSERR ==
edb->select_refreshs_above_serial_id (edb->cls,
@@ -1851,18 +2676,6 @@ analyze_coins (void *cls)
return GNUNET_SYSERR;
}
- /* process refunds */
- if (GNUNET_SYSERR ==
- edb->select_refunds_above_serial_id (edb->cls,
- esession,
- pp.last_refund_serial_id,
- &refund_cb,
- &cc))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
-
/* sync 'cc' back to disk */
GNUNET_CONTAINER_multihashmap_iterate (cc.denom_summaries,
&sync_denomination,
@@ -1887,6 +2700,11 @@ analyze_coins (void *cls)
&cc.melt_fee_balance,
&cc.refund_fee_balance,
&cc.risk);
+ report_denomination_balance (&cc.total_denom_balance,
+ &cc.risk,
+ &cc.deposit_fee_balance,
+ &cc.melt_fee_balance,
+ &cc.refund_fee_balance);
if (GNUNET_OK != dret)
{
GNUNET_break (0);
@@ -1897,641 +2715,6 @@ analyze_coins (void *cls)
}
-/* ************************* Analyze merchants ******************** */
-/* This logic checks that the aggregator did the right thing
- paying each merchant what they were due (and on time). */
-
-
-/**
- * Information we keep per loaded wire plugin.
- */
-struct WirePlugin
-{
-
- /**
- * Kept in a DLL.
- */
- struct WirePlugin *next;
-
- /**
- * Kept in a DLL.
- */
- struct WirePlugin *prev;
-
- /**
- * Name of the wire method.
- */
- char *type;
-
- /**
- * Handle to the wire plugin.
- */
- struct TALER_WIRE_Plugin *plugin;
-
-};
-
-
-/**
- * Closure for callbacks during #analyze_merchants().
- */
-struct AggregationContext
-{
-
- /**
- * DLL of wire plugins encountered.
- */
- struct WirePlugin *wire_head;
-
- /**
- * DLL of wire plugins encountered.
- */
- struct WirePlugin *wire_tail;
-
-};
-
-
-/**
- * Find the relevant wire plugin.
- *
- * @param ac context to search
- * @param type type of the wire plugin to load
- * @return NULL on error
- */
-static struct TALER_WIRE_Plugin *
-get_wire_plugin (struct AggregationContext *ac,
- const char *type)
-{
- struct WirePlugin *wp;
- struct TALER_WIRE_Plugin *plugin;
-
- for (wp = ac->wire_head; NULL != wp; wp = wp->next)
- if (0 == strcmp (type,
- wp->type))
- return wp->plugin;
- plugin = TALER_WIRE_plugin_load (cfg,
- type);
- if (NULL == plugin)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to locate wire plugin for `%s'\n",
- type);
- return NULL;
- }
- wp = GNUNET_new (struct WirePlugin);
- wp->type = GNUNET_strdup (type);
- wp->plugin = plugin;
- GNUNET_CONTAINER_DLL_insert (ac->wire_head,
- ac->wire_tail,
- wp);
- return plugin;
-}
-
-
-/**
- * Closure for #wire_transfer_information_cb.
- */
-struct WireCheckContext
-{
-
- /**
- * Corresponding merchant context.
- */
- struct AggregationContext *ac;
-
- /**
- * Total deposits claimed by all transactions that were aggregated
- * under the given @e wtid.
- */
- struct TALER_Amount total_deposits;
-
- /**
- * Hash of the wire transfer details of the receiver.
- */
- struct GNUNET_HashCode h_wire;
-
- /**
- * Execution time of the wire transfer.
- */
- struct GNUNET_TIME_Absolute date;
-
- /**
- * Wire method used for the transfer.
- */
- const char *method;
-
- /**
- * Set to #GNUNET_SYSERR if there are inconsistencies.
- */
- int ok;
-
-};
-
-
-/**
- * Check coin's transaction history for plausibility. Does NOT check
- * the signatures (those are checked independently), but does calculate
- * the amounts for the aggregation table and checks that the total
- * claimed coin value is within the value of the coin's denomination.
- *
- * @param coin_pub public key of the coin (for reporting)
- * @param h_proposal_data hash of the proposal for which we calculate the amount
- * @param merchant_pub public key of the merchant (who is allowed to issue refunds)
- * @param dki denomination information about the coin
- * @param tl_head head of transaction history to verify
- * @param[out] merchant_gain amount the coin contributes to the wire transfer to the merchant
- * @param[out] merchant_fees fees the exchange charged the merchant for the transaction(s)
- * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
- */
-static int
-check_transaction_history (const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct GNUNET_HashCode *h_proposal_data,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki,
- const struct TALER_EXCHANGEDB_TransactionList *tl_head,
- struct TALER_Amount *merchant_gain,
- struct TALER_Amount *merchant_fees)
-{
- struct TALER_Amount expenditures;
- struct TALER_Amount refunds;
- struct TALER_Amount spent;
- struct TALER_Amount value;
- struct TALER_Amount merchant_loss;
-
- GNUNET_assert (NULL != tl_head);
- TALER_amount_get_zero (currency,
- &expenditures);
- TALER_amount_get_zero (currency,
- &refunds);
- TALER_amount_get_zero (currency,
- merchant_gain);
- TALER_amount_get_zero (currency,
- merchant_fees);
- TALER_amount_get_zero (currency,
- &merchant_loss);
- /* Go over transaction history to compute totals; note that we do not
- know the order, so instead of subtracting we compute positive
- (deposit, melt) and negative (refund) values separately here,
- and then subtract the negative from the positive after the loop. */
- for (const struct TALER_EXCHANGEDB_TransactionList *tl = tl_head;NULL != tl;tl = tl->next)
- {
- const struct TALER_Amount *amount_with_fee;
- const struct TALER_Amount *fee;
- const struct TALER_AmountNBO *fee_dki;
- struct TALER_Amount tmp;
-
- // FIXME:
- // - for refunds/deposits that apply to this merchant and this contract
- // we need to update the total expenditures/refunds/fees
- // - for all other operations, we need to update the per-coin totals
- // and at the end check that they do not exceed the value of the coin!
- switch (tl->type) {
- case TALER_EXCHANGEDB_TT_DEPOSIT:
- amount_with_fee = &tl->details.deposit->amount_with_fee;
- fee = &tl->details.deposit->deposit_fee;
- fee_dki = &dki->properties.fee_deposit;
- if (GNUNET_OK !=
- TALER_amount_add (&expenditures,
- &expenditures,
- amount_with_fee))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- /* Check if this deposit is within the remit of the aggregation
- we are investigating, if so, include it in the totals. */
- if ( (0 == memcmp (merchant_pub,
- &tl->details.deposit->merchant_pub,
- sizeof (struct TALER_MerchantPublicKeyP))) &&
- (0 == memcmp (h_proposal_data,
- &tl->details.deposit->h_proposal_data,
- sizeof (struct GNUNET_HashCode))) )
- {
- struct TALER_Amount amount_without_fee;
-
- if (GNUNET_OK !=
- TALER_amount_subtract (&amount_without_fee,
- amount_with_fee,
- fee))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_amount_add (merchant_gain,
- merchant_gain,
- &amount_without_fee))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_amount_add (merchant_fees,
- merchant_fees,
- fee))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- }
- break;
- case TALER_EXCHANGEDB_TT_REFRESH_MELT:
- amount_with_fee = &tl->details.melt->amount_with_fee;
- fee = &tl->details.melt->melt_fee;
- fee_dki = &dki->properties.fee_refresh;
- if (GNUNET_OK !=
- TALER_amount_add (&expenditures,
- &expenditures,
- amount_with_fee))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- break;
- case TALER_EXCHANGEDB_TT_REFUND:
- amount_with_fee = &tl->details.refund->refund_amount;
- fee = &tl->details.refund->refund_fee;
- fee_dki = &dki->properties.fee_refund;
- if (GNUNET_OK !=
- TALER_amount_add (&refunds,
- &refunds,
- amount_with_fee))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_amount_add (&expenditures,
- &expenditures,
- fee))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- /* Check if this refund is within the remit of the aggregation
- we are investigating, if so, include it in the totals. */
- if ( (0 == memcmp (merchant_pub,
- &tl->details.refund->merchant_pub,
- sizeof (struct TALER_MerchantPublicKeyP))) &&
- (0 == memcmp (h_proposal_data,
- &tl->details.refund->h_proposal_data,
- sizeof (struct GNUNET_HashCode))) )
- {
- if (GNUNET_OK !=
- TALER_amount_add (&merchant_loss,
- &merchant_loss,
- amount_with_fee))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_amount_add (merchant_fees,
- merchant_fees,
- fee))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- }
- break;
- }
-
- /* Check that the fees given in the transaction list and in dki match */
- TALER_amount_ntoh (&tmp,
- fee_dki);
- if (0 !=
- TALER_amount_cmp (&tmp,
- fee))
- {
- /* Disagreement in fee structure within DB, should be impossible! */
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- } /* for 'tl' */
-
- /* Calculate total balance change, i.e. expenditures minus refunds */
- if (GNUNET_SYSERR ==
- TALER_amount_subtract (&spent,
- &expenditures,
- &refunds))
- {
- /* refunds above expenditures? Bad! */
- report_coin_inconsistency (coin_pub,
- &expenditures,
- &refunds,
- "could not subtract refunded amount from expenditures");
- return GNUNET_SYSERR;
- }
-
- /* Now check that 'spent' is less or equal than total coin value */
- TALER_amount_ntoh (&value,
- &dki->properties.value);
- if (1 == TALER_amount_cmp (&spent,
- &value))
- {
- /* spent > value */
- report_coin_inconsistency (coin_pub,
- &spent,
- &value,
- "accepted deposits (minus refunds) exceeds denomination value");
- return GNUNET_SYSERR;
- }
-
- /* Finally, update @a merchant_gain by subtracting what he "lost" from refunds */
- if (GNUNET_SYSERR ==
- TALER_amount_subtract (merchant_gain,
- merchant_gain,
- &merchant_loss))
- {
- /* refunds above deposits? Bad! */
- report_coin_inconsistency (coin_pub,
- merchant_gain,
- &merchant_loss,
- "merchant was granted more refunds than he deposited");
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Function called with the results of the lookup of the
- * transaction data associated with a wire transfer identifier.
- *
- * @param cls a `struct WireCheckContext`
- * @param rowid which row in the table is the information from (for diagnostics)
- * @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
- * @param coin_pub which public key was this payment about
- * @param coin_value amount contributed by this coin in total (with fee)
- * @param coin_fee applicable fee for this coin
- */
-static void
-wire_transfer_information_cb (void *cls,
- uint64_t rowid,
- 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,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_Amount *coin_value,
- const struct TALER_Amount *coin_fee)
-{
- struct WireCheckContext *wcc = cls;
- const struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki;
- struct TALER_Amount contribution;
- struct TALER_Amount computed_value;
- struct TALER_Amount computed_fees;
- struct TALER_EXCHANGEDB_TransactionList *tl;
- const struct TALER_CoinPublicInfo *coin;
-
- /* Obtain coin's transaction history */
- tl = edb->get_coin_transactions (edb->cls,
- esession,
- coin_pub);
- if (NULL == tl)
- {
- wcc->ok = GNUNET_SYSERR;
- report_row_inconsistency ("aggregation",
- rowid,
- "no transaction history for coin claimed in aggregation");
- return;
- }
-
- /* Obtain general denomination information about the coin */
- coin = NULL;
- switch (tl->type)
- {
- case TALER_EXCHANGEDB_TT_DEPOSIT:
- coin = &tl->details.deposit->coin;
- break;
- case TALER_EXCHANGEDB_TT_REFRESH_MELT:
- coin = &tl->details.melt->coin;
- break;
- case TALER_EXCHANGEDB_TT_REFUND:
- coin = &tl->details.refund->coin;
- break;
- }
- GNUNET_assert (NULL != coin); /* hard check that switch worked */
- if (GNUNET_OK !=
- get_denomination_info (&coin->denom_pub,
- &dki,
- NULL))
- {
- /* This should be impossible from database constraints */
- GNUNET_break (0);
- edb->free_coin_transaction_list (edb->cls,
- tl);
- wcc->ok = GNUNET_SYSERR;
- report_row_inconsistency ("aggregation",
- rowid,
- "could not find denomination key for coin claimed in aggregation");
- return;
- }
-
- /* Check transaction history to see if it supports aggregate valuation */
- check_transaction_history (coin_pub,
- h_proposal_data,
- merchant_pub,
- dki,
- tl,
- &computed_value,
- &computed_fees);
- if (0 !=
- TALER_amount_cmp (&computed_value,
- coin_value))
- {
- wcc->ok = GNUNET_SYSERR;
- report_row_inconsistency ("aggregation",
- rowid,
- "coin transaction history and aggregation disagree about coin's contribution");
- }
- if (0 !=
- TALER_amount_cmp (&computed_fees,
- coin_fee))
- {
- wcc->ok = GNUNET_SYSERR;
- report_row_inconsistency ("aggregation",
- rowid,
- "coin transaction history and aggregation disagree about applicable fees");
- }
- edb->free_coin_transaction_list (edb->cls,
- tl);
-
- /* Check other details of wire transfer match */
- if (0 != strcmp (wire_method,
- wcc->method))
- {
- wcc->ok = GNUNET_SYSERR;
- report_row_inconsistency ("aggregation",
- rowid,
- "wire method of aggregate do not match wire transfer");
- }
- if (0 != memcmp (h_wire,
- &wcc->h_wire,
- sizeof (struct GNUNET_HashCode)))
- {
- wcc->ok = GNUNET_SYSERR;
- report_row_inconsistency ("aggregation",
- rowid,
- "account details of aggregate do not match account details of wire transfer");
- return;
- }
- if (exec_time.abs_value_us != wcc->date.abs_value_us)
- {
- /* This should be impossible from database constraints */
- GNUNET_break (0);
- wcc->ok = GNUNET_SYSERR;
- report_row_inconsistency ("aggregation",
- rowid,
- "date given in aggregate does not match wire transfer date");
- return;
- }
- if (GNUNET_SYSERR ==
- TALER_amount_subtract (&contribution,
- coin_value,
- coin_fee))
- {
- wcc->ok = GNUNET_SYSERR;
- report_row_inconsistency ("aggregation",
- rowid,
- "could not calculate contribution of coin");
- return;
- }
-
- /* Add coin's contribution to total aggregate value */
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&wcc->total_deposits,
- &wcc->total_deposits,
- &contribution));
-}
-
-
-/**
- * Check that a wire transfer made by the exchange is valid
- * (has matching deposits).
- *
- * @param cls a `struct AggregationContext`
- * @param rowid identifier of the respective row in the database
- * @param date timestamp of the wire transfer (roughly)
- * @param wtid wire transfer subject
- * @param wire wire transfer details of the receiver
- * @param amount amount that was wired
- */
-static void
-check_wire_out_cb (void *cls,
- uint64_t rowid,
- struct GNUNET_TIME_Absolute date,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- const json_t *wire,
- const struct TALER_Amount *amount)
-{
- struct AggregationContext *ac = cls;
- struct WireCheckContext wcc;
- json_t *method;
- struct TALER_WIRE_Plugin *plugin;
-
- wcc.ac = ac;
- method = json_object_get (wire,
- "type");
- if ( (NULL == method) ||
- (! json_is_string (method)) )
- {
- report_row_inconsistency ("wire_out",
- rowid,
- "specified wire address lacks type");
- return;
- }
- wcc.method = json_string_value (method);
- wcc.ok = GNUNET_OK;
- wcc.date = date;
- TALER_amount_get_zero (amount->currency,
- &wcc.total_deposits);
- TALER_JSON_hash (wire,
- &wcc.h_wire);
- edb->lookup_wire_transfer (edb->cls,
- esession,
- wtid,
- &wire_transfer_information_cb,
- &wcc);
- if (GNUNET_OK != wcc.ok)
- {
- report_row_inconsistency ("wire_out",
- rowid,
- "audit of associated transactions failed");
- }
- plugin = get_wire_plugin (ac,
- wcc.method);
- if (NULL == plugin)
- {
- report_row_inconsistency ("wire_out",
- rowid,
- "could not load required wire plugin to validate");
- return;
- }
- if (GNUNET_SYSERR ==
- plugin->amount_round (plugin->cls,
- &wcc.total_deposits))
- {
- report_row_minor_inconsistency ("wire_out",
- rowid,
- "wire plugin failed to round given amount");
- }
- if (0 != TALER_amount_cmp (amount,
- &wcc.total_deposits))
- {
- report_wire_out_inconsistency (wire,
- rowid,
- &wcc.total_deposits,
- amount,
- "computed amount inconsistent with wire amount");
- }
-}
-
-
-/**
- * Analyze the exchange aggregator's payment processing.
- *
- * @param cls closure
- * @param int #GNUNET_OK on success, #GNUNET_SYSERR on hard errors
- */
-static int
-analyze_aggregations (void *cls)
-{
- struct AggregationContext ac;
- struct WirePlugin *wc;
- int ret;
-
- ret = GNUNET_OK;
- ac.wire_head = NULL;
- ac.wire_tail = NULL;
- if (GNUNET_SYSERR ==
- edb->select_wire_out_above_serial_id (edb->cls,
- esession,
- pp.last_wire_out_serial_id,
- &check_wire_out_cb,
- &ac))
- {
- GNUNET_break (0);
- ret = GNUNET_SYSERR;
- }
- while (NULL != (wc = ac.wire_head))
- {
- GNUNET_CONTAINER_DLL_remove (ac.wire_head,
- ac.wire_tail,
- wc);
- TALER_WIRE_plugin_unload (wc->plugin);
- GNUNET_free (wc->type);
- GNUNET_free (wc);
- }
- return ret;
-}
-
-
/* *************************** General transaction logic ****************** */
/**
@@ -2559,28 +2742,18 @@ incremental_processing (Analysis analysis,
void *analysis_cls)
{
int ret;
+ int have_pp;
- if (! restart)
- {
- ret = adb->get_auditor_progress (adb->cls,
- asession,
- &master_pub,
- &pp);
- }
- else
- {
- ret = GNUNET_NO;
- GNUNET_break (GNUNET_OK ==
- adb->drop_tables (adb->cls));
- GNUNET_break (GNUNET_OK ==
- adb->create_tables (adb->cls));
- }
- if (GNUNET_SYSERR == ret)
+ have_pp = adb->get_auditor_progress (adb->cls,
+ asession,
+ &master_pub,
+ &pp);
+ if (GNUNET_SYSERR == have_pp)
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
- if (GNUNET_NO == ret)
+ if (GNUNET_NO == have_pp)
{
GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
_("First analysis using this auditor, starting audit from scratch\n"));
@@ -2588,7 +2761,7 @@ incremental_processing (Analysis analysis,
else
{
GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
- _("Resuming audit at %llu/%llu/%llu/%llu/%llu/%llu\n\n"),
+ _("Resuming audit at %llu/%llu/%llu/%llu/%llu/%llu\n"),
(unsigned long long) pp.last_reserve_in_serial_id,
(unsigned long long) pp.last_reserve_out_serial_id,
(unsigned long long) pp.last_deposit_serial_id,
@@ -2603,17 +2776,23 @@ incremental_processing (Analysis analysis,
"Analysis phase failed, not recording progress\n");
return GNUNET_SYSERR;
}
- ret = adb->update_auditor_progress (adb->cls,
- asession,
- &master_pub,
- &pp);
+ if (GNUNET_YES == have_pp)
+ ret = adb->update_auditor_progress (adb->cls,
+ asession,
+ &master_pub,
+ &pp);
+ else
+ ret = adb->insert_auditor_progress (adb->cls,
+ asession,
+ &master_pub,
+ &pp);
if (GNUNET_OK != ret)
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
- _("Resuming audit at %llu/%llu/%llu/%llu/%llu/%llu\n\n"),
+ _("Concluded audit step at %llu/%llu/%llu/%llu/%llu/%llu\n\n"),
(unsigned long long) pp.last_reserve_in_serial_id,
(unsigned long long) pp.last_reserve_out_serial_id,
(unsigned long long) pp.last_deposit_serial_id,
@@ -2717,10 +2896,10 @@ setup_sessions_and_run ()
transact (&analyze_reserves,
NULL);
- transact (&analyze_coins,
- NULL);
transact (&analyze_aggregations,
NULL);
+ transact (&analyze_coins,
+ NULL);
}
@@ -2738,6 +2917,8 @@ run (void *cls,
const char *cfgfile,
const struct GNUNET_CONFIGURATION_Handle *c)
{
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Launching auditor\n");
cfg = c;
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (cfg,
@@ -2768,7 +2949,31 @@ run (void *cls,
TALER_EXCHANGEDB_plugin_unload (edb);
return;
}
+ if (restart)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Full audit restart requested, dropping old audit data.\n");
+ GNUNET_break (GNUNET_OK ==
+ adb->drop_tables (adb->cls));
+ TALER_AUDITORDB_plugin_unload (adb);
+ if (NULL ==
+ (adb = TALER_AUDITORDB_plugin_load (cfg)))
+ {
+ fprintf (stderr,
+ "Failed to initialize auditor database plugin after drop.\n");
+ global_ret = 1;
+ TALER_EXCHANGEDB_plugin_unload (edb);
+ return;
+ }
+ GNUNET_break (GNUNET_OK ==
+ adb->create_tables (adb->cls));
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Starting audit\n");
setup_sessions_and_run ();
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Audit complete\n");
TALER_AUDITORDB_plugin_unload (adb);
TALER_EXCHANGEDB_plugin_unload (edb);
}
diff --git a/src/auditordb/plugin_auditordb_postgres.c b/src/auditordb/plugin_auditordb_postgres.c
index 73ec92db..74dff928 100644
--- a/src/auditordb/plugin_auditordb_postgres.c
+++ b/src/auditordb/plugin_auditordb_postgres.c
@@ -26,13 +26,17 @@
#include <pthread.h>
#include <libpq-fe.h>
+
+#define LOG(kind,...) GNUNET_log_from (kind, "taler-auditordb-postgres", __VA_ARGS__)
+
+
/**
* Log a query error.
*
* @param result PQ result object of the query that failed
*/
#define QUERY_ERR(result) \
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Query failed at %s:%u: %s (%s)\n", __FILE__, __LINE__, PQresultErrorMessage (result), PQresStatus (PQresultStatus (result)))
+ LOG (GNUNET_ERROR_TYPE_ERROR, "Query failed at %s:%u: %s (%s)\n", __FILE__, __LINE__, PQresultErrorMessage (result), PQresStatus (PQresultStatus (result)))
/**
@@ -42,7 +46,7 @@
*/
#define BREAK_DB_ERR(result) do { \
GNUNET_break (0); \
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Database failure: %s (%s)\n", PQresultErrorMessage (result), PQresStatus (PQresultStatus (result))); \
+ LOG (GNUNET_ERROR_TYPE_ERROR, "Database failure: %s (%s)\n", PQresultErrorMessage (result), PQresStatus (PQresultStatus (result))); \
} while (0)
@@ -152,10 +156,9 @@ static void
pq_notice_processor_cb (void *arg,
const char *message)
{
- GNUNET_log_from (GNUNET_ERROR_TYPE_INFO,
- "pq",
- "%s",
- message);
+ LOG (GNUNET_ERROR_TYPE_INFO,
+ "%s",
+ message);
}
@@ -205,10 +208,30 @@ postgres_drop_tables (void *cls)
conn = connect_to_postgres (pc);
if (NULL == conn)
return GNUNET_SYSERR;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Dropping ALL tables\n");
+ LOG (GNUNET_ERROR_TYPE_INFO,
+ "Dropping ALL tables\n");
+ /* TODO: we probably need a bit more fine-grained control
+ over drops for the '-r' option of taler-auditor; also,
+ for the testcase, we currently fail to drop the
+ auditor_denominations table... */
+ SQLEXEC_ (conn,
+ "DROP TABLE IF EXISTS predicted_result;");
+ SQLEXEC_ (conn,
+ "DROP TABLE IF EXISTS historic_ledger;");
+ SQLEXEC_ (conn,
+ "DROP TABLE IF EXISTS historic_losses;");
+ SQLEXEC_ (conn,
+ "DROP TABLE IF EXISTS historic_denomination_revenue;");
+ SQLEXEC_ (conn,
+ "DROP TABLE IF EXISTS balance_summary;");
+ SQLEXEC_ (conn,
+ "DROP TABLE IF EXISTS denomination_pending;");
+ SQLEXEC_ (conn,
+ "DROP TABLE IF EXISTS auditor_reserve_balance;");
+ SQLEXEC_ (conn,
+ "DROP TABLE IF EXISTS auditor_reserves;");
SQLEXEC_ (conn,
- "DROP TABLE IF EXISTS test;");
+ "DROP TABLE IF EXISTS auditor_progress;");
PQfinish (conn);
return GNUNET_OK;
SQLEXEC_fail:
@@ -944,7 +967,7 @@ postgres_start (void *cls,
PQresultStatus (result))
{
TALER_LOG_ERROR ("Failed to start transaction: %s\n",
- PQresultErrorMessage (result));
+ PQresultErrorMessage (result));
GNUNET_break (0);
PQclear (result);
return GNUNET_SYSERR;
@@ -1016,9 +1039,9 @@ postgres_commit (void *cls,
PQclear (result);
return GNUNET_NO;
}
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Database commit failure: %s\n",
- sqlstate);
+ LOG (GNUNET_ERROR_TYPE_ERROR,
+ "Database commit failure: %s\n",
+ sqlstate);
PQclear (result);
return GNUNET_SYSERR;
}
@@ -1175,8 +1198,8 @@ postgres_select_denomination_info (void *cls,
int nrows = PQntuples (result);
if (0 == nrows)
{
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "postgres_select_denomination_info() returned 0 matching rows\n");
+ LOG (GNUNET_ERROR_TYPE_DEBUG,
+ "postgres_select_denomination_info() returned 0 matching rows\n");
PQclear (result);
return GNUNET_NO;
}
@@ -1357,8 +1380,8 @@ postgres_get_auditor_progress (void *cls,
int nrows = PQntuples (result);
if (0 == nrows)
{
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "postgres_get_auditor_progress() returned 0 matching rows\n");
+ LOG (GNUNET_ERROR_TYPE_DEBUG,
+ "postgres_get_auditor_progress() returned 0 matching rows\n");
PQclear (result);
return GNUNET_NO;
}
@@ -1574,8 +1597,8 @@ postgres_get_reserve_info (void *cls,
int nrows = PQntuples (result);
if (0 == nrows)
{
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "postgres_get_reserve_info() returned 0 matching rows\n");
+ LOG (GNUNET_ERROR_TYPE_DEBUG,
+ "postgres_get_reserve_info() returned 0 matching rows\n");
PQclear (result);
return GNUNET_NO;
}
@@ -1732,8 +1755,8 @@ postgres_get_reserve_summary (void *cls,
int nrows = PQntuples (result);
if (0 == nrows)
{
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "postgres_get_reserve_summary() returned 0 matching rows\n");
+ LOG (GNUNET_ERROR_TYPE_DEBUG,
+ "postgres_get_reserve_summary() returned 0 matching rows\n");
PQclear (result);
return GNUNET_NO;
}
@@ -1882,8 +1905,8 @@ postgres_get_denomination_balance (void *cls,
int nrows = PQntuples (result);
if (0 == nrows)
{
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "postgres_get_denomination_balance() returned 0 matching rows\n");
+ LOG (GNUNET_ERROR_TYPE_DEBUG,
+ "postgres_get_denomination_balance() returned 0 matching rows\n");
PQclear (result);
return GNUNET_NO;
}
@@ -2068,8 +2091,8 @@ postgres_get_balance_summary (void *cls,
int nrows = PQntuples (result);
if (0 == nrows)
{
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "postgres_get_balance_summary() returned 0 matching rows\n");
+ LOG (GNUNET_ERROR_TYPE_DEBUG,
+ "postgres_get_balance_summary() returned 0 matching rows\n");
PQclear (result);
return GNUNET_NO;
}
@@ -2183,8 +2206,8 @@ postgres_select_historic_denom_revenue (void *cls,
int nrows = PQntuples (result);
if (0 == nrows)
{
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "postgres_select_historic_denom_revenue() returned 0 matching rows\n");
+ LOG (GNUNET_ERROR_TYPE_DEBUG,
+ "postgres_select_historic_denom_revenue() returned 0 matching rows\n");
PQclear (result);
return GNUNET_NO;
}
@@ -2315,8 +2338,8 @@ postgres_select_historic_losses (void *cls,
int nrows = PQntuples (result);
if (0 == nrows)
{
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "postgres_select_historic_losses() returned 0 matching rows\n");
+ LOG (GNUNET_ERROR_TYPE_DEBUG,
+ "postgres_select_historic_losses() returned 0 matching rows\n");
PQclear (result);
return GNUNET_NO;
}
@@ -2444,8 +2467,8 @@ postgres_select_historic_reserve_revenue (void *cls,
int nrows = PQntuples (result);
if (0 == nrows)
{
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "postgres_select_historic_reserve_revenue() returned 0 matching rows\n");
+ LOG (GNUNET_ERROR_TYPE_DEBUG,
+ "postgres_select_historic_reserve_revenue() returned 0 matching rows\n");
PQclear (result);
return GNUNET_NO;
}
@@ -2605,8 +2628,8 @@ postgres_get_predicted_balance (void *cls,
int nrows = PQntuples (result);
if (0 == nrows)
{
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "postgres_get_predicted_balance() returned 0 matching rows\n");
+ LOG (GNUNET_ERROR_TYPE_DEBUG,
+ "postgres_get_predicted_balance() returned 0 matching rows\n");
PQclear (result);
return GNUNET_NO;
}
diff --git a/src/include/taler_amount_lib.h b/src/include/taler_amount_lib.h
index e6c36fed..ef323f83 100644
--- a/src/include/taler_amount_lib.h
+++ b/src/include/taler_amount_lib.h
@@ -297,6 +297,18 @@ TALER_amount_normalize (struct TALER_Amount *amount);
char *
TALER_amount_to_string (const struct TALER_Amount *amount);
+
+/**
+ * Convert amount to string.
+ *
+ * @param amount amount to convert to string
+ * @return statically allocated buffer with string representation,
+ * NULL if the @a amount was invalid
+ */
+const char *
+TALER_amount2s (const struct TALER_Amount *amount);
+
+
#if 0 /* keep Emacsens' auto-indent happy */
{
#endif
diff --git a/src/util/amount.c b/src/util/amount.c
index 44eefe6a..e0664853 100644
--- a/src/util/amount.c
+++ b/src/util/amount.c
@@ -529,9 +529,9 @@ char *
TALER_amount_to_string (const struct TALER_Amount *amount)
{
char *result;
+ unsigned int i;
uint32_t n;
char tail[TALER_AMOUNT_FRAC_LEN + 1];
- unsigned int i;
struct TALER_Amount norm;
if (GNUNET_YES != TALER_amount_is_valid (amount))
@@ -565,6 +565,54 @@ TALER_amount_to_string (const struct TALER_Amount *amount)
/**
+ * Convert amount to string.
+ *
+ * @param amount amount to convert to string
+ * @return statically allocated buffer with string representation,
+ * NULL if the @a amount was invalid
+ */
+const char *
+TALER_amount2s (const struct TALER_Amount *amount)
+{
+ static char result[TALER_AMOUNT_FRAC_LEN + TALER_CURRENCY_LEN + 3 + 12];
+ unsigned int i;
+ uint32_t n;
+ char tail[TALER_AMOUNT_FRAC_LEN + 1];
+ struct TALER_Amount norm;
+
+ if (GNUNET_YES != TALER_amount_is_valid (amount))
+ return NULL;
+ norm = *amount;
+ GNUNET_break (GNUNET_SYSERR !=
+ TALER_amount_normalize (&norm));
+ if (0 != (n = norm.fraction))
+ {
+ for (i = 0; (i < TALER_AMOUNT_FRAC_LEN) && (0 != n); i++)
+ {
+ tail[i] = '0' + (n / (TALER_AMOUNT_FRAC_BASE / 10));
+ n = (n * 10) % (TALER_AMOUNT_FRAC_BASE);
+ }
+ tail[i] = '\0';
+ GNUNET_snprintf (result,
+ sizeof (result),
+ "%s:%llu.%s",
+ norm.currency,
+ (unsigned long long) norm.value,
+ tail);
+ }
+ else
+ {
+ GNUNET_snprintf (result,
+ sizeof (result),
+ "%s:%llu",
+ norm.currency,
+ (unsigned long long) norm.value);
+ }
+ return result;
+}
+
+
+/**
* Divide an amount by a float. Note that this function
* may introduce a rounding error!
*