From a38fa32484286d2895dca10d3f53d3c7599d2f3b Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Mon, 20 Mar 2017 02:29:33 +0100 Subject: 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 0427f12ab..971f6e51f 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 @@ -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;iproperties.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;iproperties.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;idetails.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;iproperties.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;iproperties.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 73ec92db2..74dff9285 100644 --- a/src/auditordb/plugin_auditordb_postgres.c +++ b/src/auditordb/plugin_auditordb_postgres.c @@ -26,13 +26,17 @@ #include #include + +#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 e6c36fed8..ef323f83b 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 44eefe6a6..e0664853a 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)) @@ -564,6 +564,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! -- cgit v1.2.3