diff options
Diffstat (limited to 'src/auditor')
-rw-r--r-- | src/auditor/taler-auditor.c | 1615 |
1 files changed, 910 insertions, 705 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 <gnunet/gnunet_util_lib.h> @@ -251,20 +259,48 @@ static void report_reserve_balance (const struct TALER_Amount *total_balance, const struct TALER_Amount *total_fee_balance) { - char *balance; - char *fees; - - balance = TALER_amount_to_string (total_balance); - fees = TALER_amount_to_string (total_fee_balance); // TODO: implement proper reporting logic writing to file. GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, "Total escrow balance to be held for reserves: %s\n", - balance); + TALER_amount2s (total_balance)); GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, "Total profits made from reserves: %s\n", - fees); - GNUNET_free (fees); - GNUNET_free (balance); + TALER_amount2s (total_fee_balance)); +} + + +/** + * Report state of denomination processing. + * + * @param total_balance total value of outstanding coins + * @param total_risk total value of issued coins in active denominations + * @param deposit_fees total deposit fees collected + * @param melt_fees total melt fees collected + * @param refund_fees total refund fees collected + */ +static void +report_denomination_balance (const struct TALER_Amount *total_balance, + const struct TALER_Amount *total_risk, + const struct TALER_Amount *deposit_fees, + const struct TALER_Amount *melt_fees, + const struct TALER_Amount *refund_fees) +{ + // TODO: implement proper reporting logic writing to file. + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Final balance for all denominations is %s\n", + TALER_amount2s (total_balance)); + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Risk from active operations is %s\n", + TALER_amount2s (total_risk)); + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Deposit fee profits are %s\n", + TALER_amount2s (deposit_fees)); + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Melt fee profits are %s\n", + TALER_amount2s (melt_fees)); + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Refund fee profits are %s\n", + TALER_amount2s (refund_fees)); } @@ -321,6 +357,16 @@ get_denomination_info (const struct TALER_DenominationPublicKey *denom_pub, *dki = NULL; return ret; } + { + struct TALER_Amount value; + + TALER_amount_ntoh (&value, + &dkip->properties.value); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Tracking denomination `%s' (%s)\n", + GNUNET_h2s (dh), + TALER_amount2s (&value)); + } *dki = dkip; GNUNET_assert (GNUNET_OK == GNUNET_CONTAINER_multihashmap_put (denominations, @@ -346,6 +392,9 @@ free_dk_info (void *cls, { struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki = value; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Done with denomination `%s'\n", + GNUNET_h2s (key)); GNUNET_free (dki); return GNUNET_OK; } @@ -465,6 +514,10 @@ load_auditor_reserve_summary (struct ReserveSummary *rs) GNUNET_assert (GNUNET_OK == TALER_amount_get_zero (rs->total_in.currency, &rs->a_withdraw_fee_balance)); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Creating fresh reserve `%s' with starting balance %s\n", + TALER_B2S (&rs->reserve_pub), + TALER_amount2s (&rs->a_balance)); return GNUNET_OK; } rs->had_ri = GNUNET_YES; @@ -482,6 +535,10 @@ load_auditor_reserve_summary (struct ReserveSummary *rs) GNUNET_break (0); return GNUNET_SYSERR; } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Auditor remembers reserve `%s' has balance %s\n", + TALER_B2S (&rs->reserve_pub), + TALER_amount2s (&rs->a_balance)); return GNUNET_OK; } @@ -573,6 +630,10 @@ handle_reserve_in (void *cls, &rs->total_in, credit)); } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Additional incoming wire transfer for reserve `%s' of %s\n", + TALER_B2S (reserve_pub), + TALER_amount2s (credit)); expiry = GNUNET_TIME_absolute_add (execution_date, TALER_IDLE_RESERVE_EXPIRATION_TIME); rs->a_expiration_date = GNUNET_TIME_absolute_max (rs->a_expiration_date, @@ -705,7 +766,10 @@ handle_reserve_out (void *cls, &rs->total_out, amount_with_fee)); } - + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Reserve `%s' reduced by %s from withdraw\n", + TALER_B2S (reserve_pub), + TALER_amount2s (amount_with_fee)); TALER_amount_ntoh (&withdraw_fee, &dki->properties.fee_withdraw); GNUNET_assert (GNUNET_OK == @@ -794,6 +858,7 @@ verify_reserve_balance (void *cls, if (0 == GNUNET_TIME_absolute_get_remaining (rs->a_expiration_date).rel_value_us) { /* TODO: handle case where reserve is expired! (#4956) */ + GNUNET_break (0); /* not implemented */ /* NOTE: we may or may not have seen the wire-back transfer at this time, as the expiration may have just now happened. (That is, after we add the table structures and the logic to track @@ -806,6 +871,10 @@ verify_reserve_balance (void *cls, /* TODO: balance is zero, drop reserve details (and then do not update/insert) */ if (rs->had_ri) { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Final balance of reserve `%s' is %s, dropping it\n", + TALER_B2S (&rs->reserve_pub), + TALER_amount2s (&balance)); ret = adb->del_reserve_info (adb->cls, asession, &rs->reserve_pub, @@ -822,13 +891,21 @@ verify_reserve_balance (void *cls, goto cleanup; } } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Final balance of reserve `%s' is %s, no need to remember it\n", + TALER_B2S (&rs->reserve_pub), + TALER_amount2s (&balance)); + } ret = GNUNET_OK; goto cleanup; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Reserve balance `%s' OK\n", - TALER_B2S (&rs->reserve_pub)); + "Remembering final balance of reserve `%s' as %s\n", + TALER_B2S (&rs->reserve_pub), + TALER_amount2s (&balance)); /* Add withdraw fees we encountered to totals */ if (GNUNET_YES != @@ -898,6 +975,8 @@ analyze_reserves (void *cls) struct ReserveContext rc; int ret; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Analyzing reserves\n"); ret = adb->get_reserve_summary (adb->cls, asession, &master_pub, @@ -978,6 +1057,678 @@ analyze_reserves (void *cls) } +/* *********************** Analyze aggregations ******************** */ +/* This logic checks that the aggregator did the right thing + paying each merchant what they were due (and on time). */ + + +/** + * Information we keep per loaded wire plugin. + */ +struct WirePlugin +{ + + /** + * Kept in a DLL. + */ + struct WirePlugin *next; + + /** + * Kept in a DLL. + */ + struct WirePlugin *prev; + + /** + * Name of the wire method. + */ + char *type; + + /** + * Handle to the wire plugin. + */ + struct TALER_WIRE_Plugin *plugin; + +}; + + +/** + * Closure for callbacks during #analyze_merchants(). + */ +struct AggregationContext +{ + + /** + * DLL of wire plugins encountered. + */ + struct WirePlugin *wire_head; + + /** + * DLL of wire plugins encountered. + */ + struct WirePlugin *wire_tail; + +}; + + +/** + * Find the relevant wire plugin. + * + * @param ac context to search + * @param type type of the wire plugin to load + * @return NULL on error + */ +static struct TALER_WIRE_Plugin * +get_wire_plugin (struct AggregationContext *ac, + const char *type) +{ + struct WirePlugin *wp; + struct TALER_WIRE_Plugin *plugin; + + for (wp = ac->wire_head; NULL != wp; wp = wp->next) + if (0 == strcmp (type, + wp->type)) + return wp->plugin; + plugin = TALER_WIRE_plugin_load (cfg, + type); + if (NULL == plugin) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to locate wire plugin for `%s'\n", + type); + return NULL; + } + wp = GNUNET_new (struct WirePlugin); + wp->type = GNUNET_strdup (type); + wp->plugin = plugin; + GNUNET_CONTAINER_DLL_insert (ac->wire_head, + ac->wire_tail, + wp); + return plugin; +} + + +/** + * Closure for #wire_transfer_information_cb. + */ +struct WireCheckContext +{ + + /** + * Corresponding merchant context. + */ + struct AggregationContext *ac; + + /** + * Total deposits claimed by all transactions that were aggregated + * under the given @e wtid. + */ + struct TALER_Amount total_deposits; + + /** + * Hash of the wire transfer details of the receiver. + */ + struct GNUNET_HashCode h_wire; + + /** + * Execution time of the wire transfer. + */ + struct GNUNET_TIME_Absolute date; + + /** + * Wire method used for the transfer. + */ + const char *method; + + /** + * Set to #GNUNET_SYSERR if there are inconsistencies. + */ + int ok; + +}; + + +/** + * Check coin's transaction history for plausibility. Does NOT check + * the signatures (those are checked independently), but does calculate + * the amounts for the aggregation table and checks that the total + * claimed coin value is within the value of the coin's denomination. + * + * @param coin_pub public key of the coin (for reporting) + * @param h_proposal_data hash of the proposal for which we calculate the amount + * @param merchant_pub public key of the merchant (who is allowed to issue refunds) + * @param dki denomination information about the coin + * @param tl_head head of transaction history to verify + * @param[out] merchant_gain amount the coin contributes to the wire transfer to the merchant + * @param[out] merchant_fees fees the exchange charged the merchant for the transaction(s) + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +static int +check_transaction_history (const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct GNUNET_HashCode *h_proposal_data, + const struct TALER_MerchantPublicKeyP *merchant_pub, + const struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki, + const struct TALER_EXCHANGEDB_TransactionList *tl_head, + struct TALER_Amount *merchant_gain, + struct TALER_Amount *merchant_fees) +{ + struct TALER_Amount expenditures; + struct TALER_Amount refunds; + struct TALER_Amount spent; + struct TALER_Amount value; + struct TALER_Amount merchant_loss; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Checking transaction history of coin %s\n", + TALER_B2S (coin_pub)); + + GNUNET_assert (NULL != tl_head); + TALER_amount_get_zero (currency, + &expenditures); + TALER_amount_get_zero (currency, + &refunds); + TALER_amount_get_zero (currency, + merchant_gain); + TALER_amount_get_zero (currency, + merchant_fees); + TALER_amount_get_zero (currency, + &merchant_loss); + /* Go over transaction history to compute totals; note that we do not + know the order, so instead of subtracting we compute positive + (deposit, melt) and negative (refund) values separately here, + and then subtract the negative from the positive after the loop. */ + for (const struct TALER_EXCHANGEDB_TransactionList *tl = tl_head;NULL != tl;tl = tl->next) + { + const struct TALER_Amount *amount_with_fee; + const struct TALER_Amount *fee; + const struct TALER_AmountNBO *fee_dki; + struct TALER_Amount tmp; + + switch (tl->type) { + case TALER_EXCHANGEDB_TT_DEPOSIT: + amount_with_fee = &tl->details.deposit->amount_with_fee; + fee = &tl->details.deposit->deposit_fee; + fee_dki = &dki->properties.fee_deposit; + if (GNUNET_OK != + TALER_amount_add (&expenditures, + &expenditures, + amount_with_fee)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + /* Check if this deposit is within the remit of the aggregation + we are investigating, if so, include it in the totals. */ + if ( (0 == memcmp (merchant_pub, + &tl->details.deposit->merchant_pub, + sizeof (struct TALER_MerchantPublicKeyP))) && + (0 == memcmp (h_proposal_data, + &tl->details.deposit->h_proposal_data, + sizeof (struct GNUNET_HashCode))) ) + { + struct TALER_Amount amount_without_fee; + + if (GNUNET_OK != + TALER_amount_subtract (&amount_without_fee, + amount_with_fee, + fee)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_amount_add (merchant_gain, + merchant_gain, + &amount_without_fee)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Detected applicable deposit of %s\n", + TALER_amount2s (&amount_without_fee)); + if (GNUNET_OK != + TALER_amount_add (merchant_fees, + merchant_fees, + fee)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + } + break; + case TALER_EXCHANGEDB_TT_REFRESH_MELT: + amount_with_fee = &tl->details.melt->amount_with_fee; + fee = &tl->details.melt->melt_fee; + fee_dki = &dki->properties.fee_refresh; + if (GNUNET_OK != + TALER_amount_add (&expenditures, + &expenditures, + amount_with_fee)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + break; + case TALER_EXCHANGEDB_TT_REFUND: + amount_with_fee = &tl->details.refund->refund_amount; + fee = &tl->details.refund->refund_fee; + fee_dki = &dki->properties.fee_refund; + if (GNUNET_OK != + TALER_amount_add (&refunds, + &refunds, + amount_with_fee)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_amount_add (&expenditures, + &expenditures, + fee)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + /* Check if this refund is within the remit of the aggregation + we are investigating, if so, include it in the totals. */ + if ( (0 == memcmp (merchant_pub, + &tl->details.refund->merchant_pub, + sizeof (struct TALER_MerchantPublicKeyP))) && + (0 == memcmp (h_proposal_data, + &tl->details.refund->h_proposal_data, + sizeof (struct GNUNET_HashCode))) ) + { + if (GNUNET_OK != + TALER_amount_add (&merchant_loss, + &merchant_loss, + amount_with_fee)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Detected applicable refund of %s\n", + TALER_amount2s (amount_with_fee)); + if (GNUNET_OK != + TALER_amount_add (merchant_fees, + merchant_fees, + fee)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + } + break; + } + + /* Check that the fees given in the transaction list and in dki match */ + TALER_amount_ntoh (&tmp, + fee_dki); + if (0 != + TALER_amount_cmp (&tmp, + fee)) + { + /* Disagreement in fee structure within DB, should be impossible! */ + GNUNET_break (0); + return GNUNET_SYSERR; + } + } /* for 'tl' */ + + /* Calculate total balance change, i.e. expenditures minus refunds */ + if (GNUNET_SYSERR == + TALER_amount_subtract (&spent, + &expenditures, + &refunds)) + { + /* refunds above expenditures? Bad! */ + report_coin_inconsistency (coin_pub, + &expenditures, + &refunds, + "could not subtract refunded amount from expenditures"); + return GNUNET_SYSERR; + } + + /* Now check that 'spent' is less or equal than total coin value */ + TALER_amount_ntoh (&value, + &dki->properties.value); + if (1 == TALER_amount_cmp (&spent, + &value)) + { + /* spent > value */ + report_coin_inconsistency (coin_pub, + &spent, + &value, + "accepted deposits (minus refunds) exceeds denomination value"); + return GNUNET_SYSERR; + } + + /* Finally, update @a merchant_gain by subtracting what he "lost" from refunds */ + if (GNUNET_SYSERR == + TALER_amount_subtract (merchant_gain, + merchant_gain, + &merchant_loss)) + { + /* refunds above deposits? Bad! */ + report_coin_inconsistency (coin_pub, + merchant_gain, + &merchant_loss, + "merchant was granted more refunds than he deposited"); + return GNUNET_SYSERR; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Coin %s contributes %s to contract %s\n", + TALER_B2S (coin_pub), + TALER_amount2s (merchant_gain), + GNUNET_h2s (h_proposal_data)); + return GNUNET_OK; +} + + +/** + * Function called with the results of the lookup of the + * transaction data associated with a wire transfer identifier. + * + * @param cls a `struct WireCheckContext` + * @param rowid which row in the table is the information from (for diagnostics) + * @param merchant_pub public key of the merchant (should be same for all callbacks with the same @e cls) + * @param wire_method which wire plugin was used for the transfer? + * @param h_wire hash of wire transfer details of the merchant (should be same for all callbacks with the same @e cls) + * @param exec_time execution time of the wire transfer (should be same for all callbacks with the same @e cls) + * @param h_proposal_data which proposal was this payment about + * @param coin_pub which public key was this payment about + * @param coin_value amount contributed by this coin in total (with fee) + * @param coin_fee applicable fee for this coin + */ +static void +wire_transfer_information_cb (void *cls, + uint64_t rowid, + const struct TALER_MerchantPublicKeyP *merchant_pub, + const char *wire_method, + const struct GNUNET_HashCode *h_wire, + struct GNUNET_TIME_Absolute exec_time, + const struct GNUNET_HashCode *h_proposal_data, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_Amount *coin_value, + const struct TALER_Amount *coin_fee) +{ + struct WireCheckContext *wcc = cls; + const struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki; + struct TALER_Amount contribution; + struct TALER_Amount computed_value; + struct TALER_Amount computed_fees; + struct TALER_Amount coin_value_without_fee; + struct TALER_EXCHANGEDB_TransactionList *tl; + const struct TALER_CoinPublicInfo *coin; + + /* Obtain coin's transaction history */ + tl = edb->get_coin_transactions (edb->cls, + esession, + coin_pub); + if (NULL == tl) + { + wcc->ok = GNUNET_SYSERR; + report_row_inconsistency ("aggregation", + rowid, + "no transaction history for coin claimed in aggregation"); + return; + } + + /* Obtain general denomination information about the coin */ + coin = NULL; + switch (tl->type) + { + case TALER_EXCHANGEDB_TT_DEPOSIT: + coin = &tl->details.deposit->coin; + break; + case TALER_EXCHANGEDB_TT_REFRESH_MELT: + coin = &tl->details.melt->coin; + break; + case TALER_EXCHANGEDB_TT_REFUND: + coin = &tl->details.refund->coin; + break; + } + GNUNET_assert (NULL != coin); /* hard check that switch worked */ + if (GNUNET_OK != + get_denomination_info (&coin->denom_pub, + &dki, + NULL)) + { + /* This should be impossible from database constraints */ + GNUNET_break (0); + edb->free_coin_transaction_list (edb->cls, + tl); + wcc->ok = GNUNET_SYSERR; + report_row_inconsistency ("aggregation", + rowid, + "could not find denomination key for coin claimed in aggregation"); + return; + } + + /* Check transaction history to see if it supports aggregate valuation */ + check_transaction_history (coin_pub, + h_proposal_data, + merchant_pub, + dki, + tl, + &computed_value, + &computed_fees); + if (GNUNET_SYSERR == + TALER_amount_subtract (&coin_value_without_fee, + coin_value, + coin_fee)) + { + wcc->ok = GNUNET_SYSERR; + report_row_inconsistency ("aggregation", + rowid, + "inconsistent coin value and fee claimed in aggregation"); + return; + } + if (0 != + TALER_amount_cmp (&computed_value, + &coin_value_without_fee)) + { + wcc->ok = GNUNET_SYSERR; + report_row_inconsistency ("aggregation", + rowid, + "coin transaction history and aggregation disagree about coin's contribution"); + } + if (0 != + TALER_amount_cmp (&computed_fees, + coin_fee)) + { + wcc->ok = GNUNET_SYSERR; + report_row_inconsistency ("aggregation", + rowid, + "coin transaction history and aggregation disagree about applicable fees"); + } + edb->free_coin_transaction_list (edb->cls, + tl); + + /* Check other details of wire transfer match */ + if (0 != strcmp (wire_method, + wcc->method)) + { + wcc->ok = GNUNET_SYSERR; + report_row_inconsistency ("aggregation", + rowid, + "wire method of aggregate do not match wire transfer"); + } + if (0 != memcmp (h_wire, + &wcc->h_wire, + sizeof (struct GNUNET_HashCode))) + { + wcc->ok = GNUNET_SYSERR; + report_row_inconsistency ("aggregation", + rowid, + "account details of aggregate do not match account details of wire transfer"); + return; + } + if (exec_time.abs_value_us != wcc->date.abs_value_us) + { + /* This should be impossible from database constraints */ + GNUNET_break (0); + wcc->ok = GNUNET_SYSERR; + report_row_inconsistency ("aggregation", + rowid, + "date given in aggregate does not match wire transfer date"); + return; + } + if (GNUNET_SYSERR == + TALER_amount_subtract (&contribution, + coin_value, + coin_fee)) + { + wcc->ok = GNUNET_SYSERR; + report_row_inconsistency ("aggregation", + rowid, + "could not calculate contribution of coin"); + return; + } + + /* Add coin's contribution to total aggregate value */ + GNUNET_assert (GNUNET_OK == + TALER_amount_add (&wcc->total_deposits, + &wcc->total_deposits, + &contribution)); +} + + +/** + * Check that a wire transfer made by the exchange is valid + * (has matching deposits). + * + * @param cls a `struct AggregationContext` + * @param rowid identifier of the respective row in the database + * @param date timestamp of the wire transfer (roughly) + * @param wtid wire transfer subject + * @param wire wire transfer details of the receiver + * @param amount amount that was wired + */ +static void +check_wire_out_cb (void *cls, + uint64_t rowid, + struct GNUNET_TIME_Absolute date, + const struct TALER_WireTransferIdentifierRawP *wtid, + const json_t *wire, + const struct TALER_Amount *amount) +{ + struct AggregationContext *ac = cls; + struct WireCheckContext wcc; + json_t *method; + struct TALER_WIRE_Plugin *plugin; + + /* should be monotonically increasing */ + GNUNET_assert (rowid >= pp.last_wire_out_serial_id); + pp.last_wire_out_serial_id = rowid + 1; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Checking wire transfer %s over %s performed on %s\n", + TALER_B2S (wtid), + TALER_amount2s (amount), + GNUNET_STRINGS_absolute_time_to_string (date)); + wcc.ac = ac; + method = json_object_get (wire, + "type"); + if ( (NULL == method) || + (! json_is_string (method)) ) + { + report_row_inconsistency ("wire_out", + rowid, + "specified wire address lacks type"); + return; + } + wcc.method = json_string_value (method); + wcc.ok = GNUNET_OK; + wcc.date = date; + TALER_amount_get_zero (amount->currency, + &wcc.total_deposits); + TALER_JSON_hash (wire, + &wcc.h_wire); + edb->lookup_wire_transfer (edb->cls, + esession, + wtid, + &wire_transfer_information_cb, + &wcc); + if (GNUNET_OK != wcc.ok) + { + report_row_inconsistency ("wire_out", + rowid, + "audit of associated transactions failed"); + } + plugin = get_wire_plugin (ac, + wcc.method); + if (NULL == plugin) + { + report_row_inconsistency ("wire_out", + rowid, + "could not load required wire plugin to validate"); + return; + } + if (GNUNET_SYSERR == + plugin->amount_round (plugin->cls, + &wcc.total_deposits)) + { + report_row_minor_inconsistency ("wire_out", + rowid, + "wire plugin failed to round given amount"); + } + if (0 != TALER_amount_cmp (amount, + &wcc.total_deposits)) + { + report_wire_out_inconsistency (wire, + rowid, + &wcc.total_deposits, + amount, + "computed amount inconsistent with wire amount"); + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Wire transfer %s is OK\n", + TALER_B2S (wtid)); +} + + +/** + * Analyze the exchange aggregator's payment processing. + * + * @param cls closure + * @param int #GNUNET_OK on success, #GNUNET_SYSERR on hard errors + */ +static int +analyze_aggregations (void *cls) +{ + struct AggregationContext ac; + struct WirePlugin *wc; + int ret; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Analyzing aggregations\n"); + ret = GNUNET_OK; + ac.wire_head = NULL; + ac.wire_tail = NULL; + if (GNUNET_SYSERR == + edb->select_wire_out_above_serial_id (edb->cls, + esession, + pp.last_wire_out_serial_id, + &check_wire_out_cb, + &ac)) + { + GNUNET_break (0); + ret = GNUNET_SYSERR; + } + while (NULL != (wc = ac.wire_head)) + { + GNUNET_CONTAINER_DLL_remove (ac.wire_head, + ac.wire_tail, + wc); + TALER_WIRE_plugin_unload (wc->plugin); + GNUNET_free (wc->type); + GNUNET_free (wc); + } + return ret; +} + + /* ************************* Analyze coins ******************** */ /* This logic checks that the exchange did the right thing for each coin, checking deposits, refunds, refresh* and known_coins @@ -1087,6 +1838,10 @@ init_denomination (const struct GNUNET_HashCode *denom_hash, if (GNUNET_OK == ret) { ds->in_db = GNUNET_YES; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Starting balance for denomination `%s' is %s\n", + GNUNET_h2s (denom_hash), + TALER_amount2s (&ds->denom_balance)); return GNUNET_OK; } if (GNUNET_SYSERR == ret) @@ -1100,6 +1855,10 @@ init_denomination (const struct GNUNET_HashCode *denom_hash, GNUNET_assert (GNUNET_OK == TALER_amount_get_zero (currency, &ds->denom_risk)); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Starting balance for denomination `%s' is %s\n", + GNUNET_h2s (denom_hash), + TALER_amount2s (&ds->denom_balance)); return GNUNET_OK; } @@ -1204,6 +1963,10 @@ sync_denomination (void *cls, (0 != ds->denom_balance.fraction) ) ) { /* book denom_balance coin expiration profits! */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Denomination `%s' expired, booking %s in expiration profits\n", + GNUNET_h2s (denom_hash), + TALER_amount2s (&ds->denom_balance)); if (GNUNET_OK != adb->insert_historic_denom_revenue (adb->cls, asession, @@ -1221,6 +1984,10 @@ sync_denomination (void *cls, } else { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Final balance for denomination `%s' is %s\n", + GNUNET_h2s (denom_hash), + TALER_amount2s (&ds->denom_balance)); if (ds->in_db) ret = adb->update_denomination_balance (adb->cls, asession, @@ -1297,6 +2064,10 @@ withdraw_cb (void *cls, &dh); TALER_amount_ntoh (&value, &dki->properties.value); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Issued coin in denomination `%s' of total value %s\n", + GNUNET_h2s (&dh), + TALER_amount2s (&value)); if (GNUNET_OK != TALER_amount_add (&ds->denom_balance, &ds->denom_balance, @@ -1305,6 +2076,10 @@ withdraw_cb (void *cls, GNUNET_break (0); return GNUNET_SYSERR; } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "New balance of denomination `%s' is %s\n", + GNUNET_h2s (&dh), + TALER_amount2s (&ds->denom_balance)); if (GNUNET_OK != TALER_amount_add (&cc->total_denom_balance, &cc->total_denom_balance, @@ -1381,6 +2156,11 @@ refresh_session_cb (void *cls, "invalid signature for coin melt"); return GNUNET_OK; } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Melting coin %s in denomination `%s' of value %s\n", + TALER_B2S (coin_pub), + GNUNET_h2s (&dki->properties.denom_hash), + TALER_amount2s (amount_with_fee)); { struct TALER_DenominationPublicKey new_dp[num_newcoins]; @@ -1422,29 +2202,28 @@ refresh_session_cb (void *cls, if (err) return GNUNET_SYSERR; + /* calculate total refresh cost */ for (unsigned int i=0;i<num_newcoins;i++) { /* update cost of refresh */ + struct TALER_Amount fee; + struct TALER_Amount value; + + TALER_amount_ntoh (&fee, + &new_dki[i]->properties.fee_withdraw); + TALER_amount_ntoh (&value, + &new_dki[i]->properties.value); + if ( (GNUNET_OK != + TALER_amount_add (&refresh_cost, + &refresh_cost, + &fee)) || + (GNUNET_OK != + TALER_amount_add (&refresh_cost, + &refresh_cost, + &value)) ) { - struct TALER_Amount fee; - struct TALER_Amount value; - - TALER_amount_ntoh (&fee, - &new_dki[i]->properties.fee_withdraw); - TALER_amount_ntoh (&value, - &new_dki[i]->properties.value); - if ( (GNUNET_OK != - TALER_amount_add (&refresh_cost, - &refresh_cost, - &fee)) || - (GNUNET_OK != - TALER_amount_add (&refresh_cost, - &refresh_cost, - &value)) ) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } + GNUNET_break (0); + return GNUNET_SYSERR; } } @@ -1486,6 +2265,10 @@ refresh_session_cb (void *cls, &new_dki[i]->properties.denom_hash); TALER_amount_ntoh (&value, &new_dki[i]->properties.value); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Created fresh coin in denomination `%s' of value %s\n", + GNUNET_h2s (&new_dki[i]->properties.denom_hash), + TALER_amount2s (&value)); if (GNUNET_OK != TALER_amount_add (&dsi->denom_balance, &dsi->denom_balance, @@ -1494,6 +2277,10 @@ refresh_session_cb (void *cls, GNUNET_break (0); return GNUNET_SYSERR; } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "New balance of denomination `%s' is %s\n", + GNUNET_h2s (&new_dki[i]->properties.denom_hash), + TALER_amount2s (&dsi->denom_balance)); if (GNUNET_OK != TALER_amount_add (&cc->total_denom_balance, &cc->total_denom_balance, @@ -1509,7 +2296,7 @@ refresh_session_cb (void *cls, dso = get_denomination_summary (cc, dki, &dki->properties.denom_hash); - if (GNUNET_OK != + if (GNUNET_SYSERR == TALER_amount_subtract (&tmp, &dso->denom_balance, amount_with_fee)) @@ -1518,6 +2305,10 @@ refresh_session_cb (void *cls, return GNUNET_SYSERR; } dso->denom_balance = tmp; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "New balance of denomination `%s' after melt is %s\n", + GNUNET_h2s (&dki->properties.denom_hash), + TALER_amount2s (&dso->denom_balance)); /* update global up melt fees */ { @@ -1623,12 +2414,17 @@ deposit_cb (void *cls, "invalid signature for coin deposit"); return GNUNET_OK; } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Deposited coin %s in denomination `%s' of value %s\n", + TALER_B2S (coin_pub), + GNUNET_h2s (&dki->properties.denom_hash), + TALER_amount2s (amount_with_fee)); /* update old coin's denomination balance */ ds = get_denomination_summary (cc, dki, &dki->properties.denom_hash); - if (GNUNET_OK != + if (GNUNET_SYSERR == TALER_amount_subtract (&tmp, &ds->denom_balance, amount_with_fee)) @@ -1637,6 +2433,10 @@ deposit_cb (void *cls, return GNUNET_SYSERR; } ds->denom_balance = tmp; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "New balance of denomination `%s' after deposit is %s\n", + GNUNET_h2s (&dki->properties.denom_hash), + TALER_amount2s (&ds->denom_balance)); /* update global up melt fees */ { @@ -1740,6 +2540,12 @@ refund_cb (void *cls, return GNUNET_OK; } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Refunding coin %s in denomination `%s' value %s\n", + TALER_B2S (coin_pub), + GNUNET_h2s (&dki->properties.denom_hash), + TALER_amount2s (amount_with_fee)); + /* update coin's denomination balance */ ds = get_denomination_summary (cc, dki, @@ -1752,6 +2558,10 @@ refund_cb (void *cls, GNUNET_break (0); return GNUNET_SYSERR; } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "New balance of denomination `%s' after refund is %s\n", + GNUNET_h2s (&dki->properties.denom_hash), + TALER_amount2s (&ds->denom_balance)); /* update total refund fee balance */ if (GNUNET_OK != @@ -1779,6 +2589,9 @@ analyze_coins (void *cls) struct CoinContext cc; int dret; + pp.last_reserve_out_serial_id = 0; // HACK! FIXME! + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Analyzing coins\n"); /* setup 'cc' */ cc.ret = GNUNET_OK; cc.denom_summaries = GNUNET_CONTAINER_multihashmap_create (256, @@ -1827,6 +2640,18 @@ analyze_coins (void *cls) return GNUNET_SYSERR; } + /* process refunds */ + if (GNUNET_SYSERR == + edb->select_refunds_above_serial_id (edb->cls, + esession, + pp.last_refund_serial_id, + &refund_cb, + &cc)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + /* process refreshs */ if (GNUNET_SYSERR == edb->select_refreshs_above_serial_id (edb->cls, @@ -1851,18 +2676,6 @@ analyze_coins (void *cls) return GNUNET_SYSERR; } - /* process refunds */ - if (GNUNET_SYSERR == - edb->select_refunds_above_serial_id (edb->cls, - esession, - pp.last_refund_serial_id, - &refund_cb, - &cc)) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - /* sync 'cc' back to disk */ GNUNET_CONTAINER_multihashmap_iterate (cc.denom_summaries, &sync_denomination, @@ -1887,6 +2700,11 @@ analyze_coins (void *cls) &cc.melt_fee_balance, &cc.refund_fee_balance, &cc.risk); + report_denomination_balance (&cc.total_denom_balance, + &cc.risk, + &cc.deposit_fee_balance, + &cc.melt_fee_balance, + &cc.refund_fee_balance); if (GNUNET_OK != dret) { GNUNET_break (0); @@ -1897,641 +2715,6 @@ analyze_coins (void *cls) } -/* ************************* Analyze merchants ******************** */ -/* This logic checks that the aggregator did the right thing - paying each merchant what they were due (and on time). */ - - -/** - * Information we keep per loaded wire plugin. - */ -struct WirePlugin -{ - - /** - * Kept in a DLL. - */ - struct WirePlugin *next; - - /** - * Kept in a DLL. - */ - struct WirePlugin *prev; - - /** - * Name of the wire method. - */ - char *type; - - /** - * Handle to the wire plugin. - */ - struct TALER_WIRE_Plugin *plugin; - -}; - - -/** - * Closure for callbacks during #analyze_merchants(). - */ -struct AggregationContext -{ - - /** - * DLL of wire plugins encountered. - */ - struct WirePlugin *wire_head; - - /** - * DLL of wire plugins encountered. - */ - struct WirePlugin *wire_tail; - -}; - - -/** - * Find the relevant wire plugin. - * - * @param ac context to search - * @param type type of the wire plugin to load - * @return NULL on error - */ -static struct TALER_WIRE_Plugin * -get_wire_plugin (struct AggregationContext *ac, - const char *type) -{ - struct WirePlugin *wp; - struct TALER_WIRE_Plugin *plugin; - - for (wp = ac->wire_head; NULL != wp; wp = wp->next) - if (0 == strcmp (type, - wp->type)) - return wp->plugin; - plugin = TALER_WIRE_plugin_load (cfg, - type); - if (NULL == plugin) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed to locate wire plugin for `%s'\n", - type); - return NULL; - } - wp = GNUNET_new (struct WirePlugin); - wp->type = GNUNET_strdup (type); - wp->plugin = plugin; - GNUNET_CONTAINER_DLL_insert (ac->wire_head, - ac->wire_tail, - wp); - return plugin; -} - - -/** - * Closure for #wire_transfer_information_cb. - */ -struct WireCheckContext -{ - - /** - * Corresponding merchant context. - */ - struct AggregationContext *ac; - - /** - * Total deposits claimed by all transactions that were aggregated - * under the given @e wtid. - */ - struct TALER_Amount total_deposits; - - /** - * Hash of the wire transfer details of the receiver. - */ - struct GNUNET_HashCode h_wire; - - /** - * Execution time of the wire transfer. - */ - struct GNUNET_TIME_Absolute date; - - /** - * Wire method used for the transfer. - */ - const char *method; - - /** - * Set to #GNUNET_SYSERR if there are inconsistencies. - */ - int ok; - -}; - - -/** - * Check coin's transaction history for plausibility. Does NOT check - * the signatures (those are checked independently), but does calculate - * the amounts for the aggregation table and checks that the total - * claimed coin value is within the value of the coin's denomination. - * - * @param coin_pub public key of the coin (for reporting) - * @param h_proposal_data hash of the proposal for which we calculate the amount - * @param merchant_pub public key of the merchant (who is allowed to issue refunds) - * @param dki denomination information about the coin - * @param tl_head head of transaction history to verify - * @param[out] merchant_gain amount the coin contributes to the wire transfer to the merchant - * @param[out] merchant_fees fees the exchange charged the merchant for the transaction(s) - * @return #GNUNET_OK on success, #GNUNET_SYSERR on error - */ -static int -check_transaction_history (const struct TALER_CoinSpendPublicKeyP *coin_pub, - const struct GNUNET_HashCode *h_proposal_data, - const struct TALER_MerchantPublicKeyP *merchant_pub, - const struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki, - const struct TALER_EXCHANGEDB_TransactionList *tl_head, - struct TALER_Amount *merchant_gain, - struct TALER_Amount *merchant_fees) -{ - struct TALER_Amount expenditures; - struct TALER_Amount refunds; - struct TALER_Amount spent; - struct TALER_Amount value; - struct TALER_Amount merchant_loss; - - GNUNET_assert (NULL != tl_head); - TALER_amount_get_zero (currency, - &expenditures); - TALER_amount_get_zero (currency, - &refunds); - TALER_amount_get_zero (currency, - merchant_gain); - TALER_amount_get_zero (currency, - merchant_fees); - TALER_amount_get_zero (currency, - &merchant_loss); - /* Go over transaction history to compute totals; note that we do not - know the order, so instead of subtracting we compute positive - (deposit, melt) and negative (refund) values separately here, - and then subtract the negative from the positive after the loop. */ - for (const struct TALER_EXCHANGEDB_TransactionList *tl = tl_head;NULL != tl;tl = tl->next) - { - const struct TALER_Amount *amount_with_fee; - const struct TALER_Amount *fee; - const struct TALER_AmountNBO *fee_dki; - struct TALER_Amount tmp; - - // FIXME: - // - for refunds/deposits that apply to this merchant and this contract - // we need to update the total expenditures/refunds/fees - // - for all other operations, we need to update the per-coin totals - // and at the end check that they do not exceed the value of the coin! - switch (tl->type) { - case TALER_EXCHANGEDB_TT_DEPOSIT: - amount_with_fee = &tl->details.deposit->amount_with_fee; - fee = &tl->details.deposit->deposit_fee; - fee_dki = &dki->properties.fee_deposit; - if (GNUNET_OK != - TALER_amount_add (&expenditures, - &expenditures, - amount_with_fee)) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - /* Check if this deposit is within the remit of the aggregation - we are investigating, if so, include it in the totals. */ - if ( (0 == memcmp (merchant_pub, - &tl->details.deposit->merchant_pub, - sizeof (struct TALER_MerchantPublicKeyP))) && - (0 == memcmp (h_proposal_data, - &tl->details.deposit->h_proposal_data, - sizeof (struct GNUNET_HashCode))) ) - { - struct TALER_Amount amount_without_fee; - - if (GNUNET_OK != - TALER_amount_subtract (&amount_without_fee, - amount_with_fee, - fee)) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_amount_add (merchant_gain, - merchant_gain, - &amount_without_fee)) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_amount_add (merchant_fees, - merchant_fees, - fee)) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - } - break; - case TALER_EXCHANGEDB_TT_REFRESH_MELT: - amount_with_fee = &tl->details.melt->amount_with_fee; - fee = &tl->details.melt->melt_fee; - fee_dki = &dki->properties.fee_refresh; - if (GNUNET_OK != - TALER_amount_add (&expenditures, - &expenditures, - amount_with_fee)) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - break; - case TALER_EXCHANGEDB_TT_REFUND: - amount_with_fee = &tl->details.refund->refund_amount; - fee = &tl->details.refund->refund_fee; - fee_dki = &dki->properties.fee_refund; - if (GNUNET_OK != - TALER_amount_add (&refunds, - &refunds, - amount_with_fee)) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_amount_add (&expenditures, - &expenditures, - fee)) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - /* Check if this refund is within the remit of the aggregation - we are investigating, if so, include it in the totals. */ - if ( (0 == memcmp (merchant_pub, - &tl->details.refund->merchant_pub, - sizeof (struct TALER_MerchantPublicKeyP))) && - (0 == memcmp (h_proposal_data, - &tl->details.refund->h_proposal_data, - sizeof (struct GNUNET_HashCode))) ) - { - if (GNUNET_OK != - TALER_amount_add (&merchant_loss, - &merchant_loss, - amount_with_fee)) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_amount_add (merchant_fees, - merchant_fees, - fee)) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - } - break; - } - - /* Check that the fees given in the transaction list and in dki match */ - TALER_amount_ntoh (&tmp, - fee_dki); - if (0 != - TALER_amount_cmp (&tmp, - fee)) - { - /* Disagreement in fee structure within DB, should be impossible! */ - GNUNET_break (0); - return GNUNET_SYSERR; - } - } /* for 'tl' */ - - /* Calculate total balance change, i.e. expenditures minus refunds */ - if (GNUNET_SYSERR == - TALER_amount_subtract (&spent, - &expenditures, - &refunds)) - { - /* refunds above expenditures? Bad! */ - report_coin_inconsistency (coin_pub, - &expenditures, - &refunds, - "could not subtract refunded amount from expenditures"); - return GNUNET_SYSERR; - } - - /* Now check that 'spent' is less or equal than total coin value */ - TALER_amount_ntoh (&value, - &dki->properties.value); - if (1 == TALER_amount_cmp (&spent, - &value)) - { - /* spent > value */ - report_coin_inconsistency (coin_pub, - &spent, - &value, - "accepted deposits (minus refunds) exceeds denomination value"); - return GNUNET_SYSERR; - } - - /* Finally, update @a merchant_gain by subtracting what he "lost" from refunds */ - if (GNUNET_SYSERR == - TALER_amount_subtract (merchant_gain, - merchant_gain, - &merchant_loss)) - { - /* refunds above deposits? Bad! */ - report_coin_inconsistency (coin_pub, - merchant_gain, - &merchant_loss, - "merchant was granted more refunds than he deposited"); - return GNUNET_SYSERR; - } - return GNUNET_OK; -} - - -/** - * Function called with the results of the lookup of the - * transaction data associated with a wire transfer identifier. - * - * @param cls a `struct WireCheckContext` - * @param rowid which row in the table is the information from (for diagnostics) - * @param merchant_pub public key of the merchant (should be same for all callbacks with the same @e cls) - * @param wire_method which wire plugin was used for the transfer? - * @param h_wire hash of wire transfer details of the merchant (should be same for all callbacks with the same @e cls) - * @param exec_time execution time of the wire transfer (should be same for all callbacks with the same @e cls) - * @param h_proposal_data which proposal was this payment about - * @param coin_pub which public key was this payment about - * @param coin_value amount contributed by this coin in total (with fee) - * @param coin_fee applicable fee for this coin - */ -static void -wire_transfer_information_cb (void *cls, - uint64_t rowid, - const struct TALER_MerchantPublicKeyP *merchant_pub, - const char *wire_method, - const struct GNUNET_HashCode *h_wire, - struct GNUNET_TIME_Absolute exec_time, - const struct GNUNET_HashCode *h_proposal_data, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - const struct TALER_Amount *coin_value, - const struct TALER_Amount *coin_fee) -{ - struct WireCheckContext *wcc = cls; - const struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki; - struct TALER_Amount contribution; - struct TALER_Amount computed_value; - struct TALER_Amount computed_fees; - struct TALER_EXCHANGEDB_TransactionList *tl; - const struct TALER_CoinPublicInfo *coin; - - /* Obtain coin's transaction history */ - tl = edb->get_coin_transactions (edb->cls, - esession, - coin_pub); - if (NULL == tl) - { - wcc->ok = GNUNET_SYSERR; - report_row_inconsistency ("aggregation", - rowid, - "no transaction history for coin claimed in aggregation"); - return; - } - - /* Obtain general denomination information about the coin */ - coin = NULL; - switch (tl->type) - { - case TALER_EXCHANGEDB_TT_DEPOSIT: - coin = &tl->details.deposit->coin; - break; - case TALER_EXCHANGEDB_TT_REFRESH_MELT: - coin = &tl->details.melt->coin; - break; - case TALER_EXCHANGEDB_TT_REFUND: - coin = &tl->details.refund->coin; - break; - } - GNUNET_assert (NULL != coin); /* hard check that switch worked */ - if (GNUNET_OK != - get_denomination_info (&coin->denom_pub, - &dki, - NULL)) - { - /* This should be impossible from database constraints */ - GNUNET_break (0); - edb->free_coin_transaction_list (edb->cls, - tl); - wcc->ok = GNUNET_SYSERR; - report_row_inconsistency ("aggregation", - rowid, - "could not find denomination key for coin claimed in aggregation"); - return; - } - - /* Check transaction history to see if it supports aggregate valuation */ - check_transaction_history (coin_pub, - h_proposal_data, - merchant_pub, - dki, - tl, - &computed_value, - &computed_fees); - if (0 != - TALER_amount_cmp (&computed_value, - coin_value)) - { - wcc->ok = GNUNET_SYSERR; - report_row_inconsistency ("aggregation", - rowid, - "coin transaction history and aggregation disagree about coin's contribution"); - } - if (0 != - TALER_amount_cmp (&computed_fees, - coin_fee)) - { - wcc->ok = GNUNET_SYSERR; - report_row_inconsistency ("aggregation", - rowid, - "coin transaction history and aggregation disagree about applicable fees"); - } - edb->free_coin_transaction_list (edb->cls, - tl); - - /* Check other details of wire transfer match */ - if (0 != strcmp (wire_method, - wcc->method)) - { - wcc->ok = GNUNET_SYSERR; - report_row_inconsistency ("aggregation", - rowid, - "wire method of aggregate do not match wire transfer"); - } - if (0 != memcmp (h_wire, - &wcc->h_wire, - sizeof (struct GNUNET_HashCode))) - { - wcc->ok = GNUNET_SYSERR; - report_row_inconsistency ("aggregation", - rowid, - "account details of aggregate do not match account details of wire transfer"); - return; - } - if (exec_time.abs_value_us != wcc->date.abs_value_us) - { - /* This should be impossible from database constraints */ - GNUNET_break (0); - wcc->ok = GNUNET_SYSERR; - report_row_inconsistency ("aggregation", - rowid, - "date given in aggregate does not match wire transfer date"); - return; - } - if (GNUNET_SYSERR == - TALER_amount_subtract (&contribution, - coin_value, - coin_fee)) - { - wcc->ok = GNUNET_SYSERR; - report_row_inconsistency ("aggregation", - rowid, - "could not calculate contribution of coin"); - return; - } - - /* Add coin's contribution to total aggregate value */ - GNUNET_assert (GNUNET_OK == - TALER_amount_add (&wcc->total_deposits, - &wcc->total_deposits, - &contribution)); -} - - -/** - * Check that a wire transfer made by the exchange is valid - * (has matching deposits). - * - * @param cls a `struct AggregationContext` - * @param rowid identifier of the respective row in the database - * @param date timestamp of the wire transfer (roughly) - * @param wtid wire transfer subject - * @param wire wire transfer details of the receiver - * @param amount amount that was wired - */ -static void -check_wire_out_cb (void *cls, - uint64_t rowid, - struct GNUNET_TIME_Absolute date, - const struct TALER_WireTransferIdentifierRawP *wtid, - const json_t *wire, - const struct TALER_Amount *amount) -{ - struct AggregationContext *ac = cls; - struct WireCheckContext wcc; - json_t *method; - struct TALER_WIRE_Plugin *plugin; - - wcc.ac = ac; - method = json_object_get (wire, - "type"); - if ( (NULL == method) || - (! json_is_string (method)) ) - { - report_row_inconsistency ("wire_out", - rowid, - "specified wire address lacks type"); - return; - } - wcc.method = json_string_value (method); - wcc.ok = GNUNET_OK; - wcc.date = date; - TALER_amount_get_zero (amount->currency, - &wcc.total_deposits); - TALER_JSON_hash (wire, - &wcc.h_wire); - edb->lookup_wire_transfer (edb->cls, - esession, - wtid, - &wire_transfer_information_cb, - &wcc); - if (GNUNET_OK != wcc.ok) - { - report_row_inconsistency ("wire_out", - rowid, - "audit of associated transactions failed"); - } - plugin = get_wire_plugin (ac, - wcc.method); - if (NULL == plugin) - { - report_row_inconsistency ("wire_out", - rowid, - "could not load required wire plugin to validate"); - return; - } - if (GNUNET_SYSERR == - plugin->amount_round (plugin->cls, - &wcc.total_deposits)) - { - report_row_minor_inconsistency ("wire_out", - rowid, - "wire plugin failed to round given amount"); - } - if (0 != TALER_amount_cmp (amount, - &wcc.total_deposits)) - { - report_wire_out_inconsistency (wire, - rowid, - &wcc.total_deposits, - amount, - "computed amount inconsistent with wire amount"); - } -} - - -/** - * Analyze the exchange aggregator's payment processing. - * - * @param cls closure - * @param int #GNUNET_OK on success, #GNUNET_SYSERR on hard errors - */ -static int -analyze_aggregations (void *cls) -{ - struct AggregationContext ac; - struct WirePlugin *wc; - int ret; - - ret = GNUNET_OK; - ac.wire_head = NULL; - ac.wire_tail = NULL; - if (GNUNET_SYSERR == - edb->select_wire_out_above_serial_id (edb->cls, - esession, - pp.last_wire_out_serial_id, - &check_wire_out_cb, - &ac)) - { - GNUNET_break (0); - ret = GNUNET_SYSERR; - } - while (NULL != (wc = ac.wire_head)) - { - GNUNET_CONTAINER_DLL_remove (ac.wire_head, - ac.wire_tail, - wc); - TALER_WIRE_plugin_unload (wc->plugin); - GNUNET_free (wc->type); - GNUNET_free (wc); - } - return ret; -} - - /* *************************** General transaction logic ****************** */ /** @@ -2559,28 +2742,18 @@ incremental_processing (Analysis analysis, void *analysis_cls) { int ret; + int have_pp; - if (! restart) - { - ret = adb->get_auditor_progress (adb->cls, - asession, - &master_pub, - &pp); - } - else - { - ret = GNUNET_NO; - GNUNET_break (GNUNET_OK == - adb->drop_tables (adb->cls)); - GNUNET_break (GNUNET_OK == - adb->create_tables (adb->cls)); - } - if (GNUNET_SYSERR == ret) + have_pp = adb->get_auditor_progress (adb->cls, + asession, + &master_pub, + &pp); + if (GNUNET_SYSERR == have_pp) { GNUNET_break (0); return GNUNET_SYSERR; } - if (GNUNET_NO == ret) + if (GNUNET_NO == have_pp) { GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, _("First analysis using this auditor, starting audit from scratch\n")); @@ -2588,7 +2761,7 @@ incremental_processing (Analysis analysis, else { GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, - _("Resuming audit at %llu/%llu/%llu/%llu/%llu/%llu\n\n"), + _("Resuming audit at %llu/%llu/%llu/%llu/%llu/%llu\n"), (unsigned long long) pp.last_reserve_in_serial_id, (unsigned long long) pp.last_reserve_out_serial_id, (unsigned long long) pp.last_deposit_serial_id, @@ -2603,17 +2776,23 @@ incremental_processing (Analysis analysis, "Analysis phase failed, not recording progress\n"); return GNUNET_SYSERR; } - ret = adb->update_auditor_progress (adb->cls, - asession, - &master_pub, - &pp); + if (GNUNET_YES == have_pp) + ret = adb->update_auditor_progress (adb->cls, + asession, + &master_pub, + &pp); + else + ret = adb->insert_auditor_progress (adb->cls, + asession, + &master_pub, + &pp); if (GNUNET_OK != ret) { GNUNET_break (0); return GNUNET_SYSERR; } GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, - _("Resuming audit at %llu/%llu/%llu/%llu/%llu/%llu\n\n"), + _("Concluded audit step at %llu/%llu/%llu/%llu/%llu/%llu\n\n"), (unsigned long long) pp.last_reserve_in_serial_id, (unsigned long long) pp.last_reserve_out_serial_id, (unsigned long long) pp.last_deposit_serial_id, @@ -2717,10 +2896,10 @@ setup_sessions_and_run () transact (&analyze_reserves, NULL); - transact (&analyze_coins, - NULL); transact (&analyze_aggregations, NULL); + transact (&analyze_coins, + NULL); } @@ -2738,6 +2917,8 @@ run (void *cls, const char *cfgfile, const struct GNUNET_CONFIGURATION_Handle *c) { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Launching auditor\n"); cfg = c; if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, @@ -2768,7 +2949,31 @@ run (void *cls, TALER_EXCHANGEDB_plugin_unload (edb); return; } + if (restart) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Full audit restart requested, dropping old audit data.\n"); + GNUNET_break (GNUNET_OK == + adb->drop_tables (adb->cls)); + TALER_AUDITORDB_plugin_unload (adb); + if (NULL == + (adb = TALER_AUDITORDB_plugin_load (cfg))) + { + fprintf (stderr, + "Failed to initialize auditor database plugin after drop.\n"); + global_ret = 1; + TALER_EXCHANGEDB_plugin_unload (edb); + return; + } + GNUNET_break (GNUNET_OK == + adb->create_tables (adb->cls)); + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Starting audit\n"); setup_sessions_and_run (); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Audit complete\n"); TALER_AUDITORDB_plugin_unload (adb); TALER_EXCHANGEDB_plugin_unload (edb); } |