[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[GNUnet-SVN] [taler-exchange] branch master updated: fixing misc auditor
From: |
gnunet |
Subject: |
[GNUnet-SVN] [taler-exchange] branch master updated: fixing misc auditor issues |
Date: |
Mon, 20 Mar 2017 02:29:36 +0100 |
This is an automated email from the git hooks/post-receive script.
grothoff pushed a commit to branch master
in repository exchange.
The following commit(s) were added to refs/heads/master by this push:
new a38fa32 fixing misc auditor issues
a38fa32 is described below
commit a38fa32484286d2895dca10d3f53d3c7599d2f3b
Author: Christian Grothoff <address@hidden>
AuthorDate: Mon Mar 20 02:29:33 2017 +0100
fixing misc auditor issues
---
src/auditor/taler-auditor.c | 2773 ++++++++++++++++-------------
src/auditordb/plugin_auditordb_postgres.c | 89 +-
src/include/taler_amount_lib.h | 12 +
src/util/amount.c | 50 +-
4 files changed, 1606 insertions(+), 1318 deletions(-)
diff --git a/src/auditor/taler-auditor.c b/src/auditor/taler-auditor.c
index 0427f12..971f6e5 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,1557 +1057,1661 @@ analyze_reserves (void *cls)
}
-/* ************************* Analyze coins ******************** */
-/* This logic checks that the exchange did the right thing for each
- coin, checking deposits, refunds, refresh* and known_coins
- tables */
+/* *********************** Analyze aggregations ******************** */
+/* This logic checks that the aggregator did the right thing
+ paying each merchant what they were due (and on time). */
/**
- * Summary data we keep per denomination.
+ * Information we keep per loaded wire plugin.
*/
-struct DenominationSummary
+struct WirePlugin
{
+
/**
- * Total value of outstanding (not deposited) coins issued with this
- * denomination key.
+ * Kept in a DLL.
*/
- struct TALER_Amount denom_balance;
+ struct WirePlugin *next;
/**
- * Total value of coins issued with this denomination key.
+ * Kept in a DLL.
*/
- struct TALER_Amount denom_risk;
+ struct WirePlugin *prev;
/**
- * Denomination key information for this denomination.
+ * Name of the wire method.
*/
- const struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki;
+ char *type;
/**
- * #GNUNET_YES if this record already existed in the DB.
- * Used to decide between insert/update in
- * #sync_denomination().
+ * Handle to the wire plugin.
*/
- int in_db;
+ struct TALER_WIRE_Plugin *plugin;
+
};
/**
- * Closure for callbacks during #analyze_coins().
+ * Closure for callbacks during #analyze_merchants().
*/
-struct CoinContext
+struct AggregationContext
{
/**
- * Map for tracking information about denominations.
+ * DLL of wire plugins encountered.
*/
- struct GNUNET_CONTAINER_MultiHashMap *denom_summaries;
+ struct WirePlugin *wire_head;
/**
- * Total outstanding balances across all denomination keys.
+ * DLL of wire plugins encountered.
*/
- struct TALER_Amount total_denom_balance;
+ 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
+{
/**
- * Total deposit fees earned so far.
+ * Corresponding merchant context.
*/
- struct TALER_Amount deposit_fee_balance;
+ struct AggregationContext *ac;
/**
- * Total melt fees earned so far.
+ * Total deposits claimed by all transactions that were aggregated
+ * under the given @e wtid.
*/
- struct TALER_Amount melt_fee_balance;
+ struct TALER_Amount total_deposits;
/**
- * Total refund fees earned so far.
+ * Hash of the wire transfer details of the receiver.
*/
- struct TALER_Amount refund_fee_balance;
+ struct GNUNET_HashCode h_wire;
/**
- * Current financial risk of the exchange operator with respect
- * to key compromise.
- *
- * TODO: not yet properly used!
+ * Execution time of the wire transfer.
*/
- struct TALER_Amount risk;
+ struct GNUNET_TIME_Absolute date;
/**
- * Current write/replace offset in the circular @e summaries buffer.
+ * Wire method used for the transfer.
*/
- unsigned int summaries_off;
+ const char *method;
/**
- * #GNUNET_OK as long as we are fine to commit the result to the #adb.
+ * Set to #GNUNET_SYSERR if there are inconsistencies.
*/
- int ret;
+ int ok;
};
/**
- * Initialize information about denomination from the database.
+ * 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 denom_hash hash of the public key of the denomination
- * @param[out] ds summary to initialize
+ * @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
-init_denomination (const struct GNUNET_HashCode *denom_hash,
- struct DenominationSummary *ds)
+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)
{
- int ret;
+ struct TALER_Amount expenditures;
+ struct TALER_Amount refunds;
+ struct TALER_Amount spent;
+ struct TALER_Amount value;
+ struct TALER_Amount merchant_loss;
- ret = adb->get_denomination_balance (adb->cls,
- asession,
- denom_hash,
- &ds->denom_balance,
- &ds->denom_risk);
- if (GNUNET_OK == ret)
- {
- ds->in_db = GNUNET_YES;
- return GNUNET_OK;
- }
- if (GNUNET_SYSERR == ret)
+ 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)
{
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (currency,
- &ds->denom_balance));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (currency,
- &ds->denom_risk));
- return GNUNET_OK;
-}
+ 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;
-/**
- * Obtain the denomination summary for the given @a dh
- *
- * @param cc our execution context
- * @param dki denomination key information for @a dh
- * @param dh the denomination hash to use for the lookup
- * @return NULL on error
- */
-static struct DenominationSummary *
-get_denomination_summary (struct CoinContext *cc,
- const struct
TALER_EXCHANGEDB_DenominationKeyInformationP *dki,
- const struct GNUNET_HashCode *dh)
-{
- struct DenominationSummary *ds;
-
- ds = GNUNET_CONTAINER_multihashmap_get (cc->denom_summaries,
- dh);
- if (NULL != ds)
- return ds;
- ds = GNUNET_new (struct DenominationSummary);
- ds->dki = dki;
- if (GNUNET_OK !=
- init_denomination (dh,
- ds))
- {
- GNUNET_break (0);
- GNUNET_free (ds);
- return NULL;
- }
- GNUNET_assert (GNUNET_OK ==
- GNUNET_CONTAINER_multihashmap_put (cc->denom_summaries,
- dh,
- ds,
-
GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
- return ds;
-}
-
-
-/**
- * Write information about the current knowledge about a denomination key
- * back to the database and update our global reporting data about the
- * denomination. Also remove and free the memory of @a value.
- *
- * @param cls the `struct CoinContext`
- * @param key the hash of the denomination key
- * @param value a `struct DenominationSummary`
- * @return #GNUNET_OK (continue to iterate)
- */
-static int
-sync_denomination (void *cls,
- const struct GNUNET_HashCode *denom_hash,
- void *value)
-{
- struct CoinContext *cc = cls;
- struct DenominationSummary *ds = value;
- const struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki = ds->dki;
- struct GNUNET_TIME_Absolute now;
- struct GNUNET_TIME_Absolute expire_deposit;
- struct GNUNET_TIME_Absolute expire_deposit_grace;
- int ret;
-
- now = GNUNET_TIME_absolute_get ();
- expire_deposit = GNUNET_TIME_absolute_ntoh (dki->properties.expire_deposit);
- /* add day grace period to deal with clocks not being perfectly synchronized
*/
- expire_deposit_grace = GNUNET_TIME_absolute_add (expire_deposit,
- DEPOSIT_GRACE_PERIOD);
- if (now.abs_value_us > expire_deposit_grace.abs_value_us)
- {
- /* Denominationkey has expired, book remaining balance of
- outstanding coins as revenue; and reduce cc->risk exposure. */
- if (ds->in_db)
- ret = adb->del_denomination_balance (adb->cls,
- asession,
- denom_hash);
- else
- ret = GNUNET_OK;
- if ( (GNUNET_OK == ret) &&
- ( (0 != ds->denom_risk.value) ||
- (0 != ds->denom_risk.fraction) ) )
- {
- /* The denomination expired and carried a balance; we can now
- book the remaining balance as profit, and reduce our risk
- exposure by the accumulated risk of the denomination. */
- if (GNUNET_SYSERR ==
- TALER_amount_subtract (&cc->risk,
- &cc->risk,
- &ds->denom_risk))
- {
- /* Holy smokes, our risk assessment was inconsistent!
- This is really, really bad. */
- GNUNET_break (0);
- cc->ret = GNUNET_SYSERR;
- return GNUNET_OK;
- }
- }
- if ( (GNUNET_OK == ret) &&
- ( (0 != ds->denom_balance.value) ||
- (0 != ds->denom_balance.fraction) ) )
- {
- /* book denom_balance coin expiration profits! */
- if (GNUNET_OK !=
- adb->insert_historic_denom_revenue (adb->cls,
- asession,
- &master_pub,
- denom_hash,
- expire_deposit,
- &ds->denom_balance))
- {
- /* Failed to store profits? Bad database */
- GNUNET_break (0);
- cc->ret = GNUNET_SYSERR;
- return GNUNET_OK;
- }
- }
- }
- else
- {
- if (ds->in_db)
- ret = adb->update_denomination_balance (adb->cls,
- asession,
- denom_hash,
- &ds->denom_balance,
- &ds->denom_risk);
- else
- ret = adb->insert_denomination_balance (adb->cls,
- asession,
- denom_hash,
- &ds->denom_balance,
- &ds->denom_risk);
- }
- if (GNUNET_OK != ret)
- {
- GNUNET_break (0);
- cc->ret = GNUNET_SYSERR;
- }
- GNUNET_assert (GNUNET_YES ==
- GNUNET_CONTAINER_multihashmap_remove (cc->denom_summaries,
- denom_hash,
- ds));
- GNUNET_free (ds);
- return GNUNET_OK;
-}
-
-
-/**
- * Function called with details about all withdraw operations.
- * Updates the denomination balance and the overall balance as
- * we now have additional coins that have been issued.
- *
- * Note that the signature was already checked in
- * #handle_reserve_out(), so we do not check it again here.
- *
- * @param cls our `struct CoinContext`
- * @param rowid unique serial ID for the refresh session in our DB
- * @param h_blind_ev blinded hash of the coin's public key
- * @param denom_pub public denomination key of the deposited coin
- * @param denom_sig signature over the deposited coin
- * @param reserve_pub public key of the reserve
- * @param reserve_sig signature over the withdraw operation (verified
elsewhere)
- * @param execution_date when did the wallet withdraw the coin
- * @param amount_with_fee amount that was withdrawn
- * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
- */
-static int
-withdraw_cb (void *cls,
- uint64_t rowid,
- const struct GNUNET_HashCode *h_blind_ev,
- const struct TALER_DenominationPublicKey *denom_pub,
- const struct TALER_DenominationSignature *denom_sig,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const struct TALER_ReserveSignatureP *reserve_sig,
- struct GNUNET_TIME_Absolute execution_date,
- const struct TALER_Amount *amount_with_fee)
-{
- struct CoinContext *cc = cls;
- struct DenominationSummary *ds;
- struct GNUNET_HashCode dh;
- const struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki;
- struct TALER_Amount value;
-
- if (GNUNET_OK !=
- get_denomination_info (denom_pub,
- &dki,
- &dh))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- ds = get_denomination_summary (cc,
- dki,
- &dh);
- TALER_amount_ntoh (&value,
- &dki->properties.value);
- if (GNUNET_OK !=
- TALER_amount_add (&ds->denom_balance,
- &ds->denom_balance,
- &value))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_amount_add (&cc->total_denom_balance,
- &cc->total_denom_balance,
- &value))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Function called with details about coins that were melted, with the
- * goal of auditing the refresh's execution. Verifies the signature
- * and updates our information about coins outstanding (the old coin's
- * denomination has less, the fresh coins increased outstanding
- * balances).
- *
- * @param cls closure
- * @param rowid unique serial ID for the refresh session in our DB
- * @param denom_pub denomination public key of @a coin_pub
- * @param coin_pub public key of the coin
- * @param coin_sig signature from the coin
- * @param amount_with_fee amount that was deposited including fee
- * @param num_newcoins how many coins were issued
- * @param noreveal_index which index was picked by the exchange in
cut-and-choose
- * @param session_hash what is the session hash
- * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
- */
-static int
-refresh_session_cb (void *cls,
- uint64_t rowid,
- const struct TALER_DenominationPublicKey *denom_pub,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_CoinSpendSignatureP *coin_sig,
- const struct TALER_Amount *amount_with_fee,
- uint16_t num_newcoins,
- uint16_t noreveal_index,
- const struct GNUNET_HashCode *session_hash)
-{
- struct CoinContext *cc = cls;
- struct TALER_RefreshMeltCoinAffirmationPS rmc;
- const struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki;
- struct DenominationSummary *dso;
- struct TALER_Amount amount_without_fee;
- struct TALER_Amount tmp;
-
- if (GNUNET_OK !=
- get_denomination_info (denom_pub,
- &dki,
- NULL))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
-
- /* verify melt signature */
- rmc.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT);
- rmc.purpose.size = htonl (sizeof (rmc));
- rmc.session_hash = *session_hash;
- TALER_amount_hton (&rmc.amount_with_fee,
- amount_with_fee);
- rmc.melt_fee = dki->properties.fee_refresh;
- rmc.coin_pub = *coin_pub;
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_MELT,
- &rmc.purpose,
- &coin_sig->eddsa_signature,
- &coin_pub->eddsa_pub))
- {
- report_row_inconsistency ("melt",
- rowid,
- "invalid signature for coin melt");
- return GNUNET_OK;
- }
-
- {
- struct TALER_DenominationPublicKey new_dp[num_newcoins];
- const struct TALER_EXCHANGEDB_DenominationKeyInformationP
*new_dki[num_newcoins];
- struct TALER_Amount refresh_cost;
- int err;
-
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (amount_with_fee->currency,
- &refresh_cost));
-
- if (GNUNET_OK !=
- edb->get_refresh_order (edb->cls,
- esession,
- session_hash,
- num_newcoins,
- new_dp))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- /* Update outstanding amounts for all new coin's denominations, and check
- that the resulting amounts are consistent with the value being
refreshed. */
- err = GNUNET_NO;
- for (unsigned int i=0;i<num_newcoins;i++)
- {
- /* lookup new coin denomination key */
- if (GNUNET_OK !=
- get_denomination_info (&new_dp[i],
- &new_dki[i],
- NULL))
- {
- GNUNET_break (0);
- err = GNUNET_YES;
- }
- GNUNET_CRYPTO_rsa_public_key_free (new_dp[i].rsa_public_key);
- new_dp[i].rsa_public_key = NULL;
- }
- if (err)
- return GNUNET_SYSERR;
-
- 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)) )
+ 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;
}
}
- }
-
- /* compute contribution of old coin */
- {
- struct TALER_Amount melt_fee;
-
- TALER_amount_ntoh (&melt_fee,
- &dki->properties.fee_refresh);
+ 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_subtract (&amount_without_fee,
- amount_with_fee,
- &melt_fee))
+ TALER_amount_add (&expenditures,
+ &expenditures,
+ amount_with_fee))
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
- }
-
- /* check old coin covers complete expenses */
- if (1 == TALER_amount_cmp (&refresh_cost,
- &amount_without_fee))
- {
- /* refresh_cost > amount_without_fee */
- report_row_inconsistency ("melt",
- rowid,
- "refresh costs exceed value of melt");
- return GNUNET_OK;
- }
-
- /* update outstanding denomination amounts */
- for (unsigned int i=0;i<num_newcoins;i++)
- {
- struct DenominationSummary *dsi;
- struct TALER_Amount value;
-
- dsi = get_denomination_summary (cc,
- new_dki[i],
- &new_dki[i]->properties.denom_hash);
- TALER_amount_ntoh (&value,
- &new_dki[i]->properties.value);
+ 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 (&dsi->denom_balance,
- &dsi->denom_balance,
- &value))
+ TALER_amount_add (&refunds,
+ &refunds,
+ amount_with_fee))
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
if (GNUNET_OK !=
- TALER_amount_add (&cc->total_denom_balance,
- &cc->total_denom_balance,
- &value))
+ 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;
}
- }
-
- /* update old coin's denomination balance */
- dso = get_denomination_summary (cc,
- dki,
- &dki->properties.denom_hash);
- if (GNUNET_OK !=
- TALER_amount_subtract (&tmp,
- &dso->denom_balance,
- amount_with_fee))
- {
- report_emergency (dki);
- return GNUNET_SYSERR;
- }
- dso->denom_balance = tmp;
-
- /* update global up melt fees */
- {
- struct TALER_Amount rfee;
-
- TALER_amount_ntoh (&rfee,
- &dki->properties.fee_refresh);
- if (GNUNET_OK !=
- TALER_amount_add (&cc->melt_fee_balance,
- &cc->melt_fee_balance,
- &rfee))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- }
-
- /* We're good! */
- return GNUNET_OK;
-}
+ /* 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' */
-/**
- * Function called with details about deposits that have been made,
- * with the goal of auditing the deposit's execution.
- *
- * As a side-effect, #get_coin_summary will report
- * inconsistencies in the deposited coin's balance.
- *
- * @param cls closure
- * @param rowid unique serial ID for the deposit in our DB
- * @param timestamp when did the deposit happen
- * @param merchant_pub public key of the merchant
- * @param denom_pub denomination public key of @a coin_pub
- * @param coin_pub public key of the coin
- * @param coin_sig signature from the coin
- * @param amount_with_fee amount that was deposited including fee
- * @param h_proposal_data hash of the proposal data known to merchant and
customer
- * @param refund_deadline by which the merchant adviced that he might want
- * to get a refund
- * @param wire_deadline by which the merchant adviced that he would like the
- * wire transfer to be executed
- * @param receiver_wire_account wire details for the merchant, NULL from
iterate_matching_deposits()
- * @param done flag set if the deposit was already executed (or not)
- * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
- */
-static int
-deposit_cb (void *cls,
- uint64_t rowid,
- struct GNUNET_TIME_Absolute timestamp,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct TALER_DenominationPublicKey *denom_pub,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_CoinSpendSignatureP *coin_sig,
- const struct TALER_Amount *amount_with_fee,
- const struct GNUNET_HashCode *h_proposal_data,
- struct GNUNET_TIME_Absolute refund_deadline,
- struct GNUNET_TIME_Absolute wire_deadline,
- const json_t *receiver_wire_account,
- int done)
-{
- struct CoinContext *cc = cls;
- const struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki;
- struct DenominationSummary *ds;
- struct TALER_DepositRequestPS dr;
- struct TALER_Amount tmp;
-
- if (GNUNET_OK !=
- get_denomination_info (denom_pub,
- &dki,
- NULL))
+ /* Calculate total balance change, i.e. expenditures minus refunds */
+ if (GNUNET_SYSERR ==
+ TALER_amount_subtract (&spent,
+ &expenditures,
+ &refunds))
{
- GNUNET_break (0);
+ /* refunds above expenditures? Bad! */
+ report_coin_inconsistency (coin_pub,
+ &expenditures,
+ &refunds,
+ "could not subtract refunded amount from
expenditures");
return GNUNET_SYSERR;
}
- /* Verify deposit signature */
- dr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT);
- dr.purpose.size = htonl (sizeof (dr));
- dr.h_proposal_data = *h_proposal_data;
- if (GNUNET_OK !=
- TALER_JSON_hash (receiver_wire_account,
- &dr.h_wire))
+ /* 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))
{
- GNUNET_break (0);
+ /* spent > value */
+ report_coin_inconsistency (coin_pub,
+ &spent,
+ &value,
+ "accepted deposits (minus refunds) exceeds
denomination value");
return GNUNET_SYSERR;
}
- dr.timestamp = GNUNET_TIME_absolute_hton (timestamp);
- dr.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline);
- TALER_amount_hton (&dr.amount_with_fee,
- amount_with_fee);
- dr.deposit_fee = dki->properties.fee_deposit;
- dr.merchant = *merchant_pub;
- dr.coin_pub = *coin_pub;
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_DEPOSIT,
- &dr.purpose,
- &coin_sig->eddsa_signature,
- &coin_pub->eddsa_pub))
- {
- report_row_inconsistency ("deposit",
- rowid,
- "invalid signature for coin deposit");
- return GNUNET_OK;
- }
- /* update old coin's denomination balance */
- ds = get_denomination_summary (cc,
- dki,
- &dki->properties.denom_hash);
- if (GNUNET_OK !=
- TALER_amount_subtract (&tmp,
- &ds->denom_balance,
- amount_with_fee))
+ /* Finally, update @a merchant_gain by subtracting what he "lost" from
refunds */
+ if (GNUNET_SYSERR ==
+ TALER_amount_subtract (merchant_gain,
+ merchant_gain,
+ &merchant_loss))
{
- report_emergency (dki);
+ /* refunds above deposits? Bad! */
+ report_coin_inconsistency (coin_pub,
+ merchant_gain,
+ &merchant_loss,
+ "merchant was granted more refunds than he
deposited");
return GNUNET_SYSERR;
}
- ds->denom_balance = tmp;
-
- /* update global up melt fees */
- {
- struct TALER_Amount dfee;
-
- TALER_amount_ntoh (&dfee,
- &dki->properties.fee_deposit);
- if (GNUNET_OK !=
- TALER_amount_add (&cc->deposit_fee_balance,
- &cc->deposit_fee_balance,
- &dfee))
- {
- GNUNET_break (0);
- 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 details about coins that were refunding,
- * with the goal of auditing the refund's execution. Adds the
- * refunded amount back to the outstanding balance of the respective
- * denomination.
- *
- * As a side-effect, #get_coin_summary will report
- * inconsistencies in the refunded coin's balance.
+ * Function called with the results of the lookup of the
+ * transaction data associated with a wire transfer identifier.
*
- * @param cls closure
- * @param rowid unique serial ID for the refund in our DB
- * @param denom_pub denomination public key of @a coin_pub
- * @param coin_pub public key of the coin
- * @param merchant_pub public key of the merchant
- * @param merchant_sig signature of the merchant
- * @param h_proposal_data hash of the proposal data known to merchant and
customer
- * @param rtransaction_id refund transaction ID chosen by the merchant
- * @param amount_with_fee amount that was deposited including fee
- * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ * @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 int
-refund_cb (void *cls,
- uint64_t rowid,
- const struct TALER_DenominationPublicKey *denom_pub,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct TALER_MerchantSignatureP *merchant_sig,
- const struct GNUNET_HashCode *h_proposal_data,
- uint64_t rtransaction_id,
- const struct TALER_Amount *amount_with_fee)
+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 CoinContext *cc = cls;
+ struct WireCheckContext *wcc = cls;
const struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki;
- struct DenominationSummary *ds;
- struct TALER_RefundRequestPS rr;
- struct TALER_Amount amount_without_fee;
- struct TALER_Amount refund_fee;
+ 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 (denom_pub,
+ get_denomination_info (&coin->denom_pub,
&dki,
NULL))
{
+ /* This should be impossible from database constraints */
GNUNET_break (0);
- return GNUNET_SYSERR;
+ 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;
}
- /* verify refund signature */
- rr.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND);
- rr.purpose.size = htonl (sizeof (rr));
- rr.h_proposal_data = *h_proposal_data;
- rr.coin_pub = *coin_pub;
- rr.merchant = *merchant_pub;
- rr.rtransaction_id = GNUNET_htonll (rtransaction_id);
- TALER_amount_hton (&rr.refund_amount,
- amount_with_fee);
- rr.refund_fee = dki->properties.fee_refund;
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_REFUND,
- &rr.purpose,
- &merchant_sig->eddsa_sig,
- &merchant_pub->eddsa_pub))
+ /* 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))
{
- report_row_inconsistency ("refund",
+ wcc->ok = GNUNET_SYSERR;
+ report_row_inconsistency ("aggregation",
rowid,
- "invalid signature for refund");
- return GNUNET_OK;
+ "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");
}
-
- TALER_amount_ntoh (&refund_fee,
- &dki->properties.fee_refund);
- if (GNUNET_OK !=
- TALER_amount_subtract (&amount_without_fee,
- amount_with_fee,
- &refund_fee))
+ if (0 != memcmp (h_wire,
+ &wcc->h_wire,
+ sizeof (struct GNUNET_HashCode)))
{
- report_row_inconsistency ("refund",
+ wcc->ok = GNUNET_SYSERR;
+ report_row_inconsistency ("aggregation",
rowid,
- "refunded amount smaller than refund fee");
- return GNUNET_OK;
+ "account details of aggregate do not match
account details of wire transfer");
+ return;
}
-
- /* update coin's denomination balance */
- ds = get_denomination_summary (cc,
- dki,
- &dki->properties.denom_hash);
- if (GNUNET_OK !=
- TALER_amount_add (&ds->denom_balance,
- &ds->denom_balance,
- &amount_without_fee))
+ if (exec_time.abs_value_us != wcc->date.abs_value_us)
{
+ /* This should be impossible from database constraints */
GNUNET_break (0);
- return GNUNET_SYSERR;
+ wcc->ok = GNUNET_SYSERR;
+ report_row_inconsistency ("aggregation",
+ rowid,
+ "date given in aggregate does not match wire
transfer date");
+ return;
}
-
- /* update total refund fee balance */
- if (GNUNET_OK !=
- TALER_amount_add (&cc->refund_fee_balance,
- &cc->refund_fee_balance,
- &refund_fee))
+ if (GNUNET_SYSERR ==
+ TALER_amount_subtract (&contribution,
+ coin_value,
+ coin_fee))
{
- GNUNET_break (0);
- return GNUNET_SYSERR;
+ wcc->ok = GNUNET_SYSERR;
+ report_row_inconsistency ("aggregation",
+ rowid,
+ "could not calculate contribution of coin");
+ return;
}
- return GNUNET_OK;
+ /* Add coin's contribution to total aggregate value */
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_add (&wcc->total_deposits,
+ &wcc->total_deposits,
+ &contribution));
}
/**
- * Analyze the exchange's processing of coins.
+ * Check that a wire transfer made by the exchange is valid
+ * (has matching deposits).
*
- * @param cls closure
- * @param int #GNUNET_OK on success, #GNUNET_SYSERR on hard errors
+ * @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 int
-analyze_coins (void *cls)
+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 CoinContext cc;
- int dret;
+ struct AggregationContext *ac = cls;
+ struct WireCheckContext wcc;
+ json_t *method;
+ struct TALER_WIRE_Plugin *plugin;
- /* setup 'cc' */
- cc.ret = GNUNET_OK;
- cc.denom_summaries = GNUNET_CONTAINER_multihashmap_create (256,
- GNUNET_NO);
- dret = adb->get_balance_summary (adb->cls,
- asession,
- &master_pub,
- &cc.total_denom_balance,
- &cc.deposit_fee_balance,
- &cc.melt_fee_balance,
- &cc.refund_fee_balance,
- &cc.risk);
- if (GNUNET_SYSERR == dret)
+ /* 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)) )
{
- GNUNET_break (0);
- return GNUNET_SYSERR;
+ report_row_inconsistency ("wire_out",
+ rowid,
+ "specified wire address lacks type");
+ return;
}
- if (GNUNET_NO == dret)
+ 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)
{
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (currency,
- &cc.total_denom_balance));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (currency,
- &cc.deposit_fee_balance));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (currency,
- &cc.melt_fee_balance));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (currency,
- &cc.refund_fee_balance));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (currency,
- &cc.risk));
+ report_row_inconsistency ("wire_out",
+ rowid,
+ "audit of associated transactions failed");
}
-
- /* process withdrawals */
- if (GNUNET_SYSERR ==
- edb->select_reserves_out_above_serial_id (edb->cls,
- esession,
- pp.last_reserve_out_serial_id,
- &withdraw_cb,
- &cc))
+ plugin = get_wire_plugin (ac,
+ wcc.method);
+ if (NULL == plugin)
{
- GNUNET_break (0);
- return GNUNET_SYSERR;
+ report_row_inconsistency ("wire_out",
+ rowid,
+ "could not load required wire plugin to
validate");
+ return;
}
-
- /* process refreshs */
if (GNUNET_SYSERR ==
- edb->select_refreshs_above_serial_id (edb->cls,
- esession,
- pp.last_melt_serial_id,
- &refresh_session_cb,
- &cc))
+ plugin->amount_round (plugin->cls,
+ &wcc.total_deposits))
{
- GNUNET_break (0);
- return GNUNET_SYSERR;
+ report_row_minor_inconsistency ("wire_out",
+ rowid,
+ "wire plugin failed to round given
amount");
}
-
- /* process deposits */
- if (GNUNET_SYSERR ==
- edb->select_deposits_above_serial_id (edb->cls,
- esession,
- pp.last_deposit_serial_id,
- &deposit_cb,
- &cc))
+ if (0 != TALER_amount_cmp (amount,
+ &wcc.total_deposits))
{
- GNUNET_break (0);
- return GNUNET_SYSERR;
+ 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));
+}
- /* process refunds */
+
+/**
+ * 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_refunds_above_serial_id (edb->cls,
- esession,
- pp.last_refund_serial_id,
- &refund_cb,
- &cc))
+ edb->select_wire_out_above_serial_id (edb->cls,
+ esession,
+ pp.last_wire_out_serial_id,
+ &check_wire_out_cb,
+ &ac))
{
GNUNET_break (0);
- return GNUNET_SYSERR;
+ ret = GNUNET_SYSERR;
}
-
- /* sync 'cc' back to disk */
- GNUNET_CONTAINER_multihashmap_iterate (cc.denom_summaries,
- &sync_denomination,
- &cc);
- GNUNET_CONTAINER_multihashmap_destroy (cc.denom_summaries);
-
- if (GNUNET_YES == dret)
- dret = adb->update_balance_summary (adb->cls,
- asession,
- &master_pub,
- &cc.total_denom_balance,
- &cc.deposit_fee_balance,
- &cc.melt_fee_balance,
- &cc.refund_fee_balance,
- &cc.risk);
- else
- dret = adb->insert_balance_summary (adb->cls,
- asession,
- &master_pub,
- &cc.total_denom_balance,
- &cc.deposit_fee_balance,
- &cc.melt_fee_balance,
- &cc.refund_fee_balance,
- &cc.risk);
- if (GNUNET_OK != dret)
+ while (NULL != (wc = ac.wire_head))
{
- GNUNET_break (0);
- return GNUNET_SYSERR;
+ 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 cc.ret;
+ return ret;
}
-/* ************************* Analyze merchants ******************** */
-/* This logic checks that the aggregator did the right thing
- paying each merchant what they were due (and on time). */
+/* ************************* Analyze coins ******************** */
+/* This logic checks that the exchange did the right thing for each
+ coin, checking deposits, refunds, refresh* and known_coins
+ tables */
/**
- * Information we keep per loaded wire plugin.
+ * Summary data we keep per denomination.
*/
-struct WirePlugin
+struct DenominationSummary
{
-
/**
- * Kept in a DLL.
+ * Total value of outstanding (not deposited) coins issued with this
+ * denomination key.
*/
- struct WirePlugin *next;
+ struct TALER_Amount denom_balance;
/**
- * Kept in a DLL.
+ * Total value of coins issued with this denomination key.
*/
- struct WirePlugin *prev;
+ struct TALER_Amount denom_risk;
/**
- * Name of the wire method.
+ * Denomination key information for this denomination.
*/
- char *type;
+ const struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki;
/**
- * Handle to the wire plugin.
+ * #GNUNET_YES if this record already existed in the DB.
+ * Used to decide between insert/update in
+ * #sync_denomination().
*/
- struct TALER_WIRE_Plugin *plugin;
-
+ int in_db;
};
/**
- * Closure for callbacks during #analyze_merchants().
+ * Closure for callbacks during #analyze_coins().
*/
-struct AggregationContext
+struct CoinContext
{
/**
- * DLL of wire plugins encountered.
+ * Map for tracking information about denominations.
*/
- struct WirePlugin *wire_head;
+ struct GNUNET_CONTAINER_MultiHashMap *denom_summaries;
/**
- * DLL of wire plugins encountered.
+ * Total outstanding balances across all denomination keys.
*/
- struct WirePlugin *wire_tail;
+ struct TALER_Amount total_denom_balance;
+
+ /**
+ * Total deposit fees earned so far.
+ */
+ struct TALER_Amount deposit_fee_balance;
+
+ /**
+ * Total melt fees earned so far.
+ */
+ struct TALER_Amount melt_fee_balance;
+
+ /**
+ * Total refund fees earned so far.
+ */
+ struct TALER_Amount refund_fee_balance;
+
+ /**
+ * Current financial risk of the exchange operator with respect
+ * to key compromise.
+ *
+ * TODO: not yet properly used!
+ */
+ struct TALER_Amount risk;
+
+ /**
+ * Current write/replace offset in the circular @e summaries buffer.
+ */
+ unsigned int summaries_off;
+
+ /**
+ * #GNUNET_OK as long as we are fine to commit the result to the #adb.
+ */
+ int ret;
};
/**
- * Find the relevant wire plugin.
+ * Initialize information about denomination from the database.
*
- * @param ac context to search
- * @param type type of the wire plugin to load
+ * @param denom_hash hash of the public key of the denomination
+ * @param[out] ds summary to initialize
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
+ */
+static int
+init_denomination (const struct GNUNET_HashCode *denom_hash,
+ struct DenominationSummary *ds)
+{
+ int ret;
+
+ ret = adb->get_denomination_balance (adb->cls,
+ asession,
+ denom_hash,
+ &ds->denom_balance,
+ &ds->denom_risk);
+ 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)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (currency,
+ &ds->denom_balance));
+ 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;
+}
+
+
+/**
+ * Obtain the denomination summary for the given @a dh
+ *
+ * @param cc our execution context
+ * @param dki denomination key information for @a dh
+ * @param dh the denomination hash to use for the lookup
* @return NULL on error
*/
-static struct TALER_WIRE_Plugin *
-get_wire_plugin (struct AggregationContext *ac,
- const char *type)
+static struct DenominationSummary *
+get_denomination_summary (struct CoinContext *cc,
+ const struct
TALER_EXCHANGEDB_DenominationKeyInformationP *dki,
+ const struct GNUNET_HashCode *dh)
{
- struct WirePlugin *wp;
- struct TALER_WIRE_Plugin *plugin;
+ struct DenominationSummary *ds;
- 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)
+ ds = GNUNET_CONTAINER_multihashmap_get (cc->denom_summaries,
+ dh);
+ if (NULL != ds)
+ return ds;
+ ds = GNUNET_new (struct DenominationSummary);
+ ds->dki = dki;
+ if (GNUNET_OK !=
+ init_denomination (dh,
+ ds))
+ {
+ GNUNET_break (0);
+ GNUNET_free (ds);
+ return NULL;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap_put (cc->denom_summaries,
+ dh,
+ ds,
+
GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+ return ds;
+}
+
+
+/**
+ * Write information about the current knowledge about a denomination key
+ * back to the database and update our global reporting data about the
+ * denomination. Also remove and free the memory of @a value.
+ *
+ * @param cls the `struct CoinContext`
+ * @param key the hash of the denomination key
+ * @param value a `struct DenominationSummary`
+ * @return #GNUNET_OK (continue to iterate)
+ */
+static int
+sync_denomination (void *cls,
+ const struct GNUNET_HashCode *denom_hash,
+ void *value)
+{
+ struct CoinContext *cc = cls;
+ struct DenominationSummary *ds = value;
+ const struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki = ds->dki;
+ struct GNUNET_TIME_Absolute now;
+ struct GNUNET_TIME_Absolute expire_deposit;
+ struct GNUNET_TIME_Absolute expire_deposit_grace;
+ int ret;
+
+ now = GNUNET_TIME_absolute_get ();
+ expire_deposit = GNUNET_TIME_absolute_ntoh (dki->properties.expire_deposit);
+ /* add day grace period to deal with clocks not being perfectly synchronized
*/
+ expire_deposit_grace = GNUNET_TIME_absolute_add (expire_deposit,
+ DEPOSIT_GRACE_PERIOD);
+ if (now.abs_value_us > expire_deposit_grace.abs_value_us)
+ {
+ /* Denominationkey has expired, book remaining balance of
+ outstanding coins as revenue; and reduce cc->risk exposure. */
+ if (ds->in_db)
+ ret = adb->del_denomination_balance (adb->cls,
+ asession,
+ denom_hash);
+ else
+ ret = GNUNET_OK;
+ if ( (GNUNET_OK == ret) &&
+ ( (0 != ds->denom_risk.value) ||
+ (0 != ds->denom_risk.fraction) ) )
+ {
+ /* The denomination expired and carried a balance; we can now
+ book the remaining balance as profit, and reduce our risk
+ exposure by the accumulated risk of the denomination. */
+ if (GNUNET_SYSERR ==
+ TALER_amount_subtract (&cc->risk,
+ &cc->risk,
+ &ds->denom_risk))
+ {
+ /* Holy smokes, our risk assessment was inconsistent!
+ This is really, really bad. */
+ GNUNET_break (0);
+ cc->ret = GNUNET_SYSERR;
+ return GNUNET_OK;
+ }
+ }
+ if ( (GNUNET_OK == ret) &&
+ ( (0 != ds->denom_balance.value) ||
+ (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,
+ &master_pub,
+ denom_hash,
+ expire_deposit,
+ &ds->denom_balance))
+ {
+ /* Failed to store profits? Bad database */
+ GNUNET_break (0);
+ cc->ret = GNUNET_SYSERR;
+ return GNUNET_OK;
+ }
+ }
+ }
+ 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,
+ denom_hash,
+ &ds->denom_balance,
+ &ds->denom_risk);
+ else
+ ret = adb->insert_denomination_balance (adb->cls,
+ asession,
+ denom_hash,
+ &ds->denom_balance,
+ &ds->denom_risk);
+ }
+ if (GNUNET_OK != ret)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to locate wire plugin for `%s'\n",
- type);
- return NULL;
+ GNUNET_break (0);
+ cc->ret = GNUNET_SYSERR;
}
- 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;
+ GNUNET_assert (GNUNET_YES ==
+ GNUNET_CONTAINER_multihashmap_remove (cc->denom_summaries,
+ denom_hash,
+ ds));
+ GNUNET_free (ds);
+ return GNUNET_OK;
}
/**
- * Closure for #wire_transfer_information_cb.
+ * Function called with details about all withdraw operations.
+ * Updates the denomination balance and the overall balance as
+ * we now have additional coins that have been issued.
+ *
+ * Note that the signature was already checked in
+ * #handle_reserve_out(), so we do not check it again here.
+ *
+ * @param cls our `struct CoinContext`
+ * @param rowid unique serial ID for the refresh session in our DB
+ * @param h_blind_ev blinded hash of the coin's public key
+ * @param denom_pub public denomination key of the deposited coin
+ * @param denom_sig signature over the deposited coin
+ * @param reserve_pub public key of the reserve
+ * @param reserve_sig signature over the withdraw operation (verified
elsewhere)
+ * @param execution_date when did the wallet withdraw the coin
+ * @param amount_with_fee amount that was withdrawn
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-struct WireCheckContext
+static int
+withdraw_cb (void *cls,
+ uint64_t rowid,
+ const struct GNUNET_HashCode *h_blind_ev,
+ const struct TALER_DenominationPublicKey *denom_pub,
+ const struct TALER_DenominationSignature *denom_sig,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_ReserveSignatureP *reserve_sig,
+ struct GNUNET_TIME_Absolute execution_date,
+ const struct TALER_Amount *amount_with_fee)
{
+ struct CoinContext *cc = cls;
+ struct DenominationSummary *ds;
+ struct GNUNET_HashCode dh;
+ const struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki;
+ struct TALER_Amount value;
- /**
- * 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;
-
-};
+ if (GNUNET_OK !=
+ get_denomination_info (denom_pub,
+ &dki,
+ &dh))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ ds = get_denomination_summary (cc,
+ dki,
+ &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,
+ &value))
+ {
+ 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,
+ &value))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_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.
+ * Function called with details about coins that were melted, with the
+ * goal of auditing the refresh's execution. Verifies the signature
+ * and updates our information about coins outstanding (the old coin's
+ * denomination has less, the fresh coins increased outstanding
+ * balances).
*
- * @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
+ * @param cls closure
+ * @param rowid unique serial ID for the refresh session in our DB
+ * @param denom_pub denomination public key of @a coin_pub
+ * @param coin_pub public key of the coin
+ * @param coin_sig signature from the coin
+ * @param amount_with_fee amount that was deposited including fee
+ * @param num_newcoins how many coins were issued
+ * @param noreveal_index which index was picked by the exchange in
cut-and-choose
+ * @param session_hash what is the session hash
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
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)
+refresh_session_cb (void *cls,
+ uint64_t rowid,
+ const struct TALER_DenominationPublicKey *denom_pub,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_CoinSpendSignatureP *coin_sig,
+ const struct TALER_Amount *amount_with_fee,
+ uint16_t num_newcoins,
+ uint16_t noreveal_index,
+ const struct GNUNET_HashCode *session_hash)
{
- struct TALER_Amount expenditures;
- struct TALER_Amount refunds;
- struct TALER_Amount spent;
- struct TALER_Amount value;
- struct TALER_Amount merchant_loss;
+ struct CoinContext *cc = cls;
+ struct TALER_RefreshMeltCoinAffirmationPS rmc;
+ const struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki;
+ struct DenominationSummary *dso;
+ struct TALER_Amount amount_without_fee;
+ struct TALER_Amount tmp;
- 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)
+ if (GNUNET_OK !=
+ get_denomination_info (denom_pub,
+ &dki,
+ NULL))
{
- const struct TALER_Amount *amount_with_fee;
- const struct TALER_Amount *fee;
- const struct TALER_AmountNBO *fee_dki;
- struct TALER_Amount tmp;
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
- // 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;
+ /* verify melt signature */
+ rmc.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT);
+ rmc.purpose.size = htonl (sizeof (rmc));
+ rmc.session_hash = *session_hash;
+ TALER_amount_hton (&rmc.amount_with_fee,
+ amount_with_fee);
+ rmc.melt_fee = dki->properties.fee_refresh;
+ rmc.coin_pub = *coin_pub;
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_MELT,
+ &rmc.purpose,
+ &coin_sig->eddsa_signature,
+ &coin_pub->eddsa_pub))
+ {
+ report_row_inconsistency ("melt",
+ rowid,
+ "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];
+ const struct TALER_EXCHANGEDB_DenominationKeyInformationP
*new_dki[num_newcoins];
+ struct TALER_Amount refresh_cost;
+ int err;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (amount_with_fee->currency,
+ &refresh_cost));
+
+ if (GNUNET_OK !=
+ edb->get_refresh_order (edb->cls,
+ esession,
+ session_hash,
+ num_newcoins,
+ new_dp))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ /* Update outstanding amounts for all new coin's denominations, and check
+ that the resulting amounts are consistent with the value being
refreshed. */
+ err = GNUNET_NO;
+ for (unsigned int i=0;i<num_newcoins;i++)
+ {
+ /* lookup new coin denomination key */
if (GNUNET_OK !=
- TALER_amount_add (&expenditures,
- &expenditures,
- amount_with_fee))
+ get_denomination_info (&new_dp[i],
+ &new_dki[i],
+ NULL))
{
GNUNET_break (0);
- return GNUNET_SYSERR;
+ err = GNUNET_YES;
}
- /* 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;
+ GNUNET_CRYPTO_rsa_public_key_free (new_dp[i].rsa_public_key);
+ new_dp[i].rsa_public_key = NULL;
+ }
+ if (err)
+ return GNUNET_SYSERR;
- 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))
+ /* 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)) )
{
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;
+ }
+
+ /* compute contribution of old coin */
+ {
+ struct TALER_Amount melt_fee;
+
+ TALER_amount_ntoh (&melt_fee,
+ &dki->properties.fee_refresh);
if (GNUNET_OK !=
- TALER_amount_add (&refunds,
- &refunds,
- amount_with_fee))
+ TALER_amount_subtract (&amount_without_fee,
+ amount_with_fee,
+ &melt_fee))
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
+ }
+
+ /* check old coin covers complete expenses */
+ if (1 == TALER_amount_cmp (&refresh_cost,
+ &amount_without_fee))
+ {
+ /* refresh_cost > amount_without_fee */
+ report_row_inconsistency ("melt",
+ rowid,
+ "refresh costs exceed value of melt");
+ return GNUNET_OK;
+ }
+
+ /* update outstanding denomination amounts */
+ for (unsigned int i=0;i<num_newcoins;i++)
+ {
+ struct DenominationSummary *dsi;
+ struct TALER_Amount value;
+
+ dsi = get_denomination_summary (cc,
+ new_dki[i],
+ &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 (&expenditures,
- &expenditures,
- fee))
+ TALER_amount_add (&dsi->denom_balance,
+ &dsi->denom_balance,
+ &value))
{
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))) )
+ 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,
+ &value))
{
- 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;
- }
+ 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))
+ /* update old coin's denomination balance */
+ dso = get_denomination_summary (cc,
+ dki,
+ &dki->properties.denom_hash);
+ if (GNUNET_SYSERR ==
+ TALER_amount_subtract (&tmp,
+ &dso->denom_balance,
+ amount_with_fee))
+ {
+ report_emergency (dki);
+ 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 */
+ {
+ struct TALER_Amount rfee;
+
+ TALER_amount_ntoh (&rfee,
+ &dki->properties.fee_refresh);
+ if (GNUNET_OK !=
+ TALER_amount_add (&cc->melt_fee_balance,
+ &cc->melt_fee_balance,
+ &rfee))
{
- /* 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))
+ /* We're good! */
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called with details about deposits that have been made,
+ * with the goal of auditing the deposit's execution.
+ *
+ * As a side-effect, #get_coin_summary will report
+ * inconsistencies in the deposited coin's balance.
+ *
+ * @param cls closure
+ * @param rowid unique serial ID for the deposit in our DB
+ * @param timestamp when did the deposit happen
+ * @param merchant_pub public key of the merchant
+ * @param denom_pub denomination public key of @a coin_pub
+ * @param coin_pub public key of the coin
+ * @param coin_sig signature from the coin
+ * @param amount_with_fee amount that was deposited including fee
+ * @param h_proposal_data hash of the proposal data known to merchant and
customer
+ * @param refund_deadline by which the merchant adviced that he might want
+ * to get a refund
+ * @param wire_deadline by which the merchant adviced that he would like the
+ * wire transfer to be executed
+ * @param receiver_wire_account wire details for the merchant, NULL from
iterate_matching_deposits()
+ * @param done flag set if the deposit was already executed (or not)
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+static int
+deposit_cb (void *cls,
+ uint64_t rowid,
+ struct GNUNET_TIME_Absolute timestamp,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_DenominationPublicKey *denom_pub,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_CoinSpendSignatureP *coin_sig,
+ const struct TALER_Amount *amount_with_fee,
+ const struct GNUNET_HashCode *h_proposal_data,
+ struct GNUNET_TIME_Absolute refund_deadline,
+ struct GNUNET_TIME_Absolute wire_deadline,
+ const json_t *receiver_wire_account,
+ int done)
+{
+ struct CoinContext *cc = cls;
+ const struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki;
+ struct DenominationSummary *ds;
+ struct TALER_DepositRequestPS dr;
+ struct TALER_Amount tmp;
+
+ if (GNUNET_OK !=
+ get_denomination_info (denom_pub,
+ &dki,
+ NULL))
{
- /* refunds above expenditures? Bad! */
- report_coin_inconsistency (coin_pub,
- &expenditures,
- &refunds,
- "could not subtract refunded amount from
expenditures");
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ /* Verify deposit signature */
+ dr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT);
+ dr.purpose.size = htonl (sizeof (dr));
+ dr.h_proposal_data = *h_proposal_data;
+ if (GNUNET_OK !=
+ TALER_JSON_hash (receiver_wire_account,
+ &dr.h_wire))
+ {
+ GNUNET_break (0);
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))
+ dr.timestamp = GNUNET_TIME_absolute_hton (timestamp);
+ dr.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline);
+ TALER_amount_hton (&dr.amount_with_fee,
+ amount_with_fee);
+ dr.deposit_fee = dki->properties.fee_deposit;
+ dr.merchant = *merchant_pub;
+ dr.coin_pub = *coin_pub;
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_DEPOSIT,
+ &dr.purpose,
+ &coin_sig->eddsa_signature,
+ &coin_pub->eddsa_pub))
{
- /* spent > value */
- report_coin_inconsistency (coin_pub,
- &spent,
- &value,
- "accepted deposits (minus refunds) exceeds
denomination value");
- return GNUNET_SYSERR;
+ report_row_inconsistency ("deposit",
+ rowid,
+ "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));
- /* Finally, update @a merchant_gain by subtracting what he "lost" from
refunds */
+ /* update old coin's denomination balance */
+ ds = get_denomination_summary (cc,
+ dki,
+ &dki->properties.denom_hash);
if (GNUNET_SYSERR ==
- TALER_amount_subtract (merchant_gain,
- merchant_gain,
- &merchant_loss))
+ TALER_amount_subtract (&tmp,
+ &ds->denom_balance,
+ amount_with_fee))
{
- /* refunds above deposits? Bad! */
- report_coin_inconsistency (coin_pub,
- merchant_gain,
- &merchant_loss,
- "merchant was granted more refunds than he
deposited");
+ report_emergency (dki);
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 */
+ {
+ struct TALER_Amount dfee;
+
+ TALER_amount_ntoh (&dfee,
+ &dki->properties.fee_deposit);
+ if (GNUNET_OK !=
+ TALER_amount_add (&cc->deposit_fee_balance,
+ &cc->deposit_fee_balance,
+ &dfee))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ }
+
return GNUNET_OK;
}
/**
- * Function called with the results of the lookup of the
- * transaction data associated with a wire transfer identifier.
+ * Function called with details about coins that were refunding,
+ * with the goal of auditing the refund's execution. Adds the
+ * refunded amount back to the outstanding balance of the respective
+ * denomination.
*
- * @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
+ * As a side-effect, #get_coin_summary will report
+ * inconsistencies in the refunded coin's balance.
+ *
+ * @param cls closure
+ * @param rowid unique serial ID for the refund in our DB
+ * @param denom_pub denomination public key of @a coin_pub
+ * @param coin_pub public key of the coin
+ * @param merchant_pub public key of the merchant
+ * @param merchant_sig signature of the merchant
+ * @param h_proposal_data hash of the proposal data known to merchant and
customer
+ * @param rtransaction_id refund transaction ID chosen by the merchant
+ * @param amount_with_fee amount that was deposited including fee
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-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)
+static int
+refund_cb (void *cls,
+ uint64_t rowid,
+ const struct TALER_DenominationPublicKey *denom_pub,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_MerchantSignatureP *merchant_sig,
+ const struct GNUNET_HashCode *h_proposal_data,
+ uint64_t rtransaction_id,
+ const struct TALER_Amount *amount_with_fee)
{
- struct WireCheckContext *wcc = cls;
+ struct CoinContext *cc = 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;
- }
+ struct DenominationSummary *ds;
+ struct TALER_RefundRequestPS rr;
+ struct TALER_Amount amount_without_fee;
+ struct TALER_Amount refund_fee;
- /* 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,
+ get_denomination_info (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;
+ return GNUNET_SYSERR;
}
- /* 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))
+ /* verify refund signature */
+ rr.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND);
+ rr.purpose.size = htonl (sizeof (rr));
+ rr.h_proposal_data = *h_proposal_data;
+ rr.coin_pub = *coin_pub;
+ rr.merchant = *merchant_pub;
+ rr.rtransaction_id = GNUNET_htonll (rtransaction_id);
+ TALER_amount_hton (&rr.refund_amount,
+ amount_with_fee);
+ rr.refund_fee = dki->properties.fee_refund;
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_REFUND,
+ &rr.purpose,
+ &merchant_sig->eddsa_sig,
+ &merchant_pub->eddsa_pub))
{
- wcc->ok = GNUNET_SYSERR;
- report_row_inconsistency ("aggregation",
+ report_row_inconsistency ("refund",
rowid,
- "coin transaction history and aggregation
disagree about applicable fees");
+ "invalid signature for refund");
+ return GNUNET_OK;
}
- 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)))
+ TALER_amount_ntoh (&refund_fee,
+ &dki->properties.fee_refund);
+ if (GNUNET_OK !=
+ TALER_amount_subtract (&amount_without_fee,
+ amount_with_fee,
+ &refund_fee))
{
- wcc->ok = GNUNET_SYSERR;
- report_row_inconsistency ("aggregation",
+ report_row_inconsistency ("refund",
rowid,
- "account details of aggregate do not match
account details of wire transfer");
- return;
+ "refunded amount smaller than refund fee");
+ return GNUNET_OK;
}
- if (exec_time.abs_value_us != wcc->date.abs_value_us)
+
+ 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,
+ &dki->properties.denom_hash);
+ if (GNUNET_OK !=
+ TALER_amount_add (&ds->denom_balance,
+ &ds->denom_balance,
+ &amount_without_fee))
{
- /* 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;
+ return GNUNET_SYSERR;
}
- if (GNUNET_SYSERR ==
- TALER_amount_subtract (&contribution,
- coin_value,
- coin_fee))
+ 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 !=
+ TALER_amount_add (&cc->refund_fee_balance,
+ &cc->refund_fee_balance,
+ &refund_fee))
{
- wcc->ok = GNUNET_SYSERR;
- report_row_inconsistency ("aggregation",
- rowid,
- "could not calculate contribution of coin");
- return;
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
}
- /* Add coin's contribution to total aggregate value */
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_add (&wcc->total_deposits,
- &wcc->total_deposits,
- &contribution));
+ return GNUNET_OK;
}
/**
- * Check that a wire transfer made by the exchange is valid
- * (has matching deposits).
+ * Analyze the exchange's processing of coins.
*
- * @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
+ * @param cls closure
+ * @param int #GNUNET_OK on success, #GNUNET_SYSERR on hard errors
*/
-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)
+static int
+analyze_coins (void *cls)
{
- struct AggregationContext *ac = cls;
- struct WireCheckContext wcc;
- json_t *method;
- struct TALER_WIRE_Plugin *plugin;
+ struct CoinContext cc;
+ int dret;
- wcc.ac = ac;
- method = json_object_get (wire,
- "type");
- if ( (NULL == method) ||
- (! json_is_string (method)) )
+ 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,
+ GNUNET_NO);
+ dret = adb->get_balance_summary (adb->cls,
+ asession,
+ &master_pub,
+ &cc.total_denom_balance,
+ &cc.deposit_fee_balance,
+ &cc.melt_fee_balance,
+ &cc.refund_fee_balance,
+ &cc.risk);
+ if (GNUNET_SYSERR == dret)
{
- report_row_inconsistency ("wire_out",
- rowid,
- "specified wire address lacks type");
- return;
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
}
- 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)
+ if (GNUNET_NO == dret)
{
- report_row_inconsistency ("wire_out",
- rowid,
- "audit of associated transactions failed");
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (currency,
+ &cc.total_denom_balance));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (currency,
+ &cc.deposit_fee_balance));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (currency,
+ &cc.melt_fee_balance));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (currency,
+ &cc.refund_fee_balance));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (currency,
+ &cc.risk));
}
- plugin = get_wire_plugin (ac,
- wcc.method);
- if (NULL == plugin)
+
+ /* process withdrawals */
+ if (GNUNET_SYSERR ==
+ edb->select_reserves_out_above_serial_id (edb->cls,
+ esession,
+ pp.last_reserve_out_serial_id,
+ &withdraw_cb,
+ &cc))
{
- report_row_inconsistency ("wire_out",
- rowid,
- "could not load required wire plugin to
validate");
- return;
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
}
+
+ /* process refunds */
if (GNUNET_SYSERR ==
- plugin->amount_round (plugin->cls,
- &wcc.total_deposits))
+ edb->select_refunds_above_serial_id (edb->cls,
+ esession,
+ pp.last_refund_serial_id,
+ &refund_cb,
+ &cc))
{
- report_row_minor_inconsistency ("wire_out",
- rowid,
- "wire plugin failed to round given
amount");
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
}
- if (0 != TALER_amount_cmp (amount,
- &wcc.total_deposits))
+
+ /* process refreshs */
+ if (GNUNET_SYSERR ==
+ edb->select_refreshs_above_serial_id (edb->cls,
+ esession,
+ pp.last_melt_serial_id,
+ &refresh_session_cb,
+ &cc))
{
- report_wire_out_inconsistency (wire,
- rowid,
- &wcc.total_deposits,
- amount,
- "computed amount inconsistent with wire
amount");
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
}
-}
-
-
-/**
- * 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;
+ /* process deposits */
if (GNUNET_SYSERR ==
- edb->select_wire_out_above_serial_id (edb->cls,
+ edb->select_deposits_above_serial_id (edb->cls,
esession,
- pp.last_wire_out_serial_id,
- &check_wire_out_cb,
- &ac))
+ pp.last_deposit_serial_id,
+ &deposit_cb,
+ &cc))
{
GNUNET_break (0);
- ret = GNUNET_SYSERR;
+ return GNUNET_SYSERR;
}
- while (NULL != (wc = ac.wire_head))
+
+ /* sync 'cc' back to disk */
+ GNUNET_CONTAINER_multihashmap_iterate (cc.denom_summaries,
+ &sync_denomination,
+ &cc);
+ GNUNET_CONTAINER_multihashmap_destroy (cc.denom_summaries);
+
+ if (GNUNET_YES == dret)
+ dret = adb->update_balance_summary (adb->cls,
+ asession,
+ &master_pub,
+ &cc.total_denom_balance,
+ &cc.deposit_fee_balance,
+ &cc.melt_fee_balance,
+ &cc.refund_fee_balance,
+ &cc.risk);
+ else
+ dret = adb->insert_balance_summary (adb->cls,
+ asession,
+ &master_pub,
+ &cc.total_denom_balance,
+ &cc.deposit_fee_balance,
+ &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_CONTAINER_DLL_remove (ac.wire_head,
- ac.wire_tail,
- wc);
- TALER_WIRE_plugin_unload (wc->plugin);
- GNUNET_free (wc->type);
- GNUNET_free (wc);
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
}
- return ret;
+
+ return cc.ret;
}
@@ -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 73ec92d..74dff92 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 e6c36fe..ef323f8 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 44eefe6..e066485 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!
*
--
To stop receiving notification emails like this one, please contact
address@hidden
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [GNUnet-SVN] [taler-exchange] branch master updated: fixing misc auditor issues,
gnunet <=