From 2ace9969b7e1ede610ff99546c5a84f59adf0931 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sat, 21 Mar 2020 11:05:51 +0100 Subject: rename fest on refactored auditor logic --- src/auditor/taler-helper-auditor-coins.c | 2374 ++++++++++++++++++++++++++++++ 1 file changed, 2374 insertions(+) create mode 100644 src/auditor/taler-helper-auditor-coins.c (limited to 'src/auditor/taler-helper-auditor-coins.c') diff --git a/src/auditor/taler-helper-auditor-coins.c b/src/auditor/taler-helper-auditor-coins.c new file mode 100644 index 000000000..c9250961b --- /dev/null +++ b/src/auditor/taler-helper-auditor-coins.c @@ -0,0 +1,2374 @@ +/* + This file is part of TALER + Copyright (C) 2016-2020 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero Public License for more details. + + You should have received a copy of the GNU Affero Public License along with + TALER; see the file COPYING. If not, see +*/ +/** + * @file auditor/taler-helper-auditor0coins.c + * @brief audits coins in an exchange database. + * @author Christian Grothoff + * + * UNDECIDED: + * - do we care about checking the 'done' flag in deposit_cb? + */ +#include "platform.h" +#include +#include "taler_auditordb_plugin.h" +#include "taler_exchangedb_lib.h" +#include "taler_json_lib.h" +#include "taler_bank_service.h" +#include "taler_signatures.h" +#include "report-lib.h" + +/** + * How many coin histories do we keep in RAM at any given point in + * time? Used bound memory consumption of the auditor. Larger values + * reduce database accesses. + * + * Set to a VERY low value here for testing. Practical values may be + * in the millions. + */ +#define MAX_COIN_SUMMARIES 4 + +/** + * Use a 1 day grace period to deal with clocks not being perfectly synchronized. + */ +#define DEPOSIT_GRACE_PERIOD GNUNET_TIME_UNIT_DAYS + +/** + * Return value from main(). + */ +static int global_ret; + +/** + * Checkpointing our progress for coins. + */ +static struct TALER_AUDITORDB_ProgressPointCoin ppc; + +/** + * Checkpointing our progress for coins. + */ +static struct TALER_AUDITORDB_ProgressPointCoin ppc_start; + +/** + * Array of TALER_ARL_reports about denomination keys with an + * emergency (more value deposited than withdrawn) + */ +static json_t *report_emergencies; + +/** + * Array of TALER_ARL_reports about denomination keys with an + * emergency (more coins deposited than withdrawn) + */ +static json_t *report_emergencies_by_count; + +/** + * Array of TALER_ARL_reports about row inconsitencies. + */ +static json_t *report_row_inconsistencies; + +/** + * Report about amount calculation differences (causing profit + * or loss at the exchange). + */ +static json_t *report_amount_arithmetic_inconsistencies; + +/** + * Profits the exchange made by bad amount calculations. + */ +static struct TALER_Amount total_arithmetic_delta_plus; + +/** + * Losses the exchange made by bad amount calculations. + */ +static struct TALER_Amount total_arithmetic_delta_minus; + +/** + * Total amount reported in all calls to #report_emergency_by_count(). + */ +static struct TALER_Amount reported_emergency_risk_by_count; + +/** + * Total amount reported in all calls to #report_emergency_by_amount(). + */ +static struct TALER_Amount reported_emergency_risk_by_amount; + +/** + * Total amount in losses reported in all calls to #report_emergency_by_amount(). + */ +static struct TALER_Amount reported_emergency_loss; + +/** + * Total amount in losses reported in all calls to #report_emergency_by_count(). + */ +static struct TALER_Amount reported_emergency_loss_by_count; + +/** + * Expected balance in the escrow account. + */ +static struct TALER_Amount total_escrow_balance; + +/** + * Active risk exposure. + */ +static struct TALER_Amount total_risk; + +/** + * Actualized risk (= loss) from recoups. + */ +static struct TALER_Amount total_recoup_loss; + +/** + * Recoups we made on denominations that were not revoked (!?). + */ +static struct TALER_Amount total_irregular_recoups; + +/** + * Total deposit fees earned. + */ +static struct TALER_Amount total_deposit_fee_income; + +/** + * Total melt fees earned. + */ +static struct TALER_Amount total_melt_fee_income; + +/** + * Total refund fees earned. + */ +static struct TALER_Amount total_refund_fee_income; + +/** + * Array of TALER_ARL_reports about coin operations with bad signatures. + */ +static json_t *report_bad_sig_losses; + +/** + * Total amount lost by operations for which signatures were invalid. + */ +static struct TALER_Amount total_bad_sig_loss; + +/** + * Array of refresh transactions where the /refresh/reveal has not yet + * happened (and may of course never happen). + */ +static json_t *report_refreshs_hanging; + +/** + * Total amount lost by operations for which signatures were invalid. + */ +static struct TALER_Amount total_refresh_hanging; + + +/* ***************************** Report logic **************************** */ + +/** + * Called in case we detect an emergency situation where the exchange + * is paying out a larger amount on a denomination than we issued in + * that denomination. This means that the exchange's private keys + * might have gotten compromised, and that we need to trigger an + * emergency request to all wallets to deposit pending coins for the + * denomination (and as an exchange suffer a huge financial loss). + * + * @param issue denomination key where the loss was detected + * @param risk maximum risk that might have just become real (coins created by this @a issue) + * @param loss actual losses already (actualized before denomination was revoked) + */ +static void +report_emergency_by_amount (const struct + TALER_DenominationKeyValidityPS *issue, + const struct TALER_Amount *risk, + const struct TALER_Amount *loss) +{ + TALER_ARL_report (report_emergencies, + json_pack ("{s:o, s:o, s:o, s:o, s:o, s:o}", + "denompub_hash", + GNUNET_JSON_from_data_auto (&issue->denom_hash), + "denom_risk", + TALER_JSON_from_amount (risk), + "denom_loss", + TALER_JSON_from_amount (loss), + "start", + TALER_ARL_TALER_ARL_json_from_time_abs_nbo ( + issue->start), + "deposit_end", + TALER_ARL_TALER_ARL_json_from_time_abs_nbo ( + issue->expire_deposit), + "value", + TALER_JSON_from_amount_nbo (&issue->value))); + GNUNET_assert (GNUNET_OK == + TALER_amount_add (&reported_emergency_risk_by_amount, + &reported_emergency_risk_by_amount, + risk)); + GNUNET_assert (GNUNET_OK == + TALER_amount_add (&reported_emergency_loss, + &reported_emergency_loss, + loss)); +} + + +/** + * Called in case we detect an emergency situation where the exchange + * is paying out a larger NUMBER of coins of a denomination than we + * issued in that denomination. This means that the exchange's + * private keys might have gotten compromised, and that we need to + * trigger an emergency request to all wallets to deposit pending + * coins for the denomination (and as an exchange suffer a huge + * financial loss). + * + * @param issue denomination key where the loss was detected + * @param num_issued number of coins that were issued + * @param num_known number of coins that have been deposited + * @param risk amount that is at risk + */ +static void +report_emergency_by_count (const struct + TALER_DenominationKeyValidityPS *issue, + uint64_t num_issued, + uint64_t num_known, + const struct TALER_Amount *risk) +{ + struct TALER_Amount denom_value; + + TALER_ARL_report (report_emergencies_by_count, + json_pack ("{s:o, s:I, s:I, s:o, s:o, s:o, s:o}", + "denompub_hash", + GNUNET_JSON_from_data_auto (&issue->denom_hash), + "num_issued", + (json_int_t) num_issued, + "num_known", + (json_int_t) num_known, + "denom_risk", + TALER_JSON_from_amount (risk), + "start", + TALER_ARL_TALER_ARL_json_from_time_abs_nbo ( + issue->start), + "deposit_end", + TALER_ARL_TALER_ARL_json_from_time_abs_nbo ( + issue->expire_deposit), + "value", + TALER_JSON_from_amount_nbo (&issue->value))); + GNUNET_assert (GNUNET_OK == + TALER_amount_add (&reported_emergency_risk_by_count, + &reported_emergency_risk_by_count, + risk)); + TALER_amount_ntoh (&denom_value, + &issue->value); + for (uint64_t i = num_issued; i auditor */ + GNUNET_break (GNUNET_OK == + TALER_amount_subtract (&delta, + exchange, + auditor)); + } + else + { + /* auditor < exchange */ + profitable = -profitable; + GNUNET_break (GNUNET_OK == + TALER_amount_subtract (&delta, + auditor, + exchange)); + } + TALER_ARL_report (report_amount_arithmetic_inconsistencies, + json_pack ("{s:s, s:I, s:o, s:o, s:I}", + "operation", operation, + "rowid", (json_int_t) rowid, + "exchange", TALER_JSON_from_amount (exchange), + "auditor", TALER_JSON_from_amount (auditor), + "profitable", (json_int_t) profitable)); + if (0 != profitable) + { + target = (1 == profitable) + ? &total_arithmetic_delta_plus + : &total_arithmetic_delta_minus; + GNUNET_break (GNUNET_OK == + TALER_amount_add (target, + target, + &delta)); + } +} + + +/** + * Report a (serious) inconsistency in the exchange's database. + * + * @param table affected table + * @param rowid affected row, UINT64_MAX if row is missing + * @param diagnostic message explaining the problem + */ +static void +report_row_inconsistency (const char *table, + uint64_t rowid, + const char *diagnostic) +{ + TALER_ARL_report (report_row_inconsistencies, + json_pack ("{s:s, s:I, s:s}", + "table", table, + "row", (json_int_t) rowid, + "diagnostic", diagnostic)); +} + + +/* ************************* Analyze coins ******************** */ +/* This logic checks that the exchange did the right thing for each + coin, checking deposits, refunds, refresh* and known_coins + tables */ + + +/** + * Summary data we keep per denomination. + */ +struct DenominationSummary +{ + /** + * Total value of outstanding (not deposited) coins issued with this + * denomination key. + */ + struct TALER_Amount denom_balance; + + /** + * Total losses made (once coins deposited exceed + * coins withdrawn and thus the @e denom_balance is + * effectively negative). + */ + struct TALER_Amount denom_loss; + + /** + * Total value of coins issued with this denomination key. + */ + struct TALER_Amount denom_risk; + + /** + * Total value of coins subjected to recoup with this denomination key. + */ + struct TALER_Amount denom_recoup; + + /** + * How many coins (not their amount!) of this denomination + * did the exchange issue overall? + */ + uint64_t num_issued; + + /** + * Denomination key information for this denomination. + */ + const struct TALER_DenominationKeyValidityPS *issue; + + /** + * #GNUNET_YES if this record already existed in the DB. + * Used to decide between insert/update in + * #sync_denomination(). + */ + int in_db; + + /** + * Should we TALER_ARL_report an emergency for this denomination? + */ + int report_emergency; + + /** + * #GNUNET_YES if this denomination was revoked. + */ + int was_revoked; +}; + + +/** + * Closure for callbacks during #analyze_coins(). + */ +struct CoinContext +{ + + /** + * Map for tracking information about denominations. + */ + struct GNUNET_CONTAINER_MultiHashMap *denom_summaries; + + /** + * Current write/replace offset in the circular @e summaries buffer. + */ + unsigned int summaries_off; + + /** + * Transaction status code. + */ + enum GNUNET_DB_QueryStatus qs; + +}; + + +/** + * Initialize information about denomination from the database. + * + * @param denom_hash hash of the public key of the denomination + * @param[out] ds summary to initialize + * @return transaction status code + */ +static enum GNUNET_DB_QueryStatus +init_denomination (const struct GNUNET_HashCode *denom_hash, + struct DenominationSummary *ds) +{ + enum GNUNET_DB_QueryStatus qs; + struct TALER_MasterSignatureP msig; + uint64_t rowid; + + qs = TALER_ARL_adb->get_denomination_balance (TALER_ARL_adb->cls, + TALER_ARL_asession, + denom_hash, + &ds->denom_balance, + &ds->denom_loss, + &ds->denom_risk, + &ds->denom_recoup, + &ds->num_issued); + if (0 > qs) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + return qs; + } + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) + { + 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_DB_STATUS_SUCCESS_ONE_RESULT; + } + qs = TALER_ARL_edb->get_denomination_revocation (TALER_ARL_edb->cls, + TALER_ARL_esession, + denom_hash, + &msig, + &rowid); + if (0 > qs) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + return qs; + } + if (0 < qs) + { + /* check revocation signature */ + struct TALER_MasterDenominationKeyRevocationPS rm; + + rm.purpose.purpose = htonl ( + TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED); + rm.purpose.size = htonl (sizeof (rm)); + rm.h_denom_pub = *denom_hash; + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify ( + TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED, + &rm.purpose, + &msig.eddsa_signature, + &TALER_ARL_master_pub.eddsa_pub)) + { + report_row_inconsistency ("denomination revocation table", + rowid, + "revocation signature invalid"); + } + else + { + ds->was_revoked = GNUNET_YES; + } + } + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (TALER_ARL_currency, + &ds->denom_balance)); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (TALER_ARL_currency, + &ds->denom_loss)); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (TALER_ARL_currency, + &ds->denom_risk)); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (TALER_ARL_currency, + &ds->denom_recoup)); + 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_DB_STATUS_SUCCESS_ONE_RESULT; +} + + +/** + * Obtain the denomination summary for the given @a dh + * + * @param cc our execution context + * @param issue 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_DenominationKeyValidityPS *issue, + 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->issue = issue; + if (0 > (cc->qs = 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 TALER_ARL_reporting data about the + * denomination. Also remove and free the memory of @a value. + * + * @param cls the `struct CoinContext` + * @param denom_hash 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_DenominationKeyValidityPS *issue = ds->issue; + struct GNUNET_TIME_Absolute now; + struct GNUNET_TIME_Absolute expire_deposit; + struct GNUNET_TIME_Absolute expire_deposit_grace; + enum GNUNET_DB_QueryStatus qs; + + now = GNUNET_TIME_absolute_get (); + expire_deposit = GNUNET_TIME_absolute_ntoh (issue->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) + qs = TALER_ARL_adb->del_denomination_balance (TALER_ARL_adb->cls, + TALER_ARL_asession, + denom_hash); + else + qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; + if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) && + ( (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 (&total_risk, + &total_risk, + &ds->denom_risk)) + { + /* Holy smokes, our risk assessment was inconsistent! + This is really, really bad. */ + GNUNET_break (0); + cc->qs = GNUNET_DB_STATUS_HARD_ERROR; + } + } + if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) && + ( (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_DB_STATUS_SUCCESS_ONE_RESULT != + (qs = TALER_ARL_adb->insert_historic_denom_revenue ( + TALER_ARL_adb->cls, + TALER_ARL_asession, + & + TALER_ARL_master_pub, + denom_hash, + expire_deposit, + &ds->denom_balance, + &ds->denom_recoup))) + { + /* Failed to store profits? Bad database */ + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + cc->qs = qs; + } + } + } + else + { + long long cnt; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Final balance for denomination `%s' is %s (%llu)\n", + GNUNET_h2s (denom_hash), + TALER_amount2s (&ds->denom_balance), + (unsigned long long) ds->num_issued); + cnt = TALER_ARL_edb->count_known_coins (TALER_ARL_edb->cls, + TALER_ARL_esession, + denom_hash); + if (0 > cnt) + { + /* Failed to obtain count? Bad database */ + qs = (enum GNUNET_DB_QueryStatus) cnt; + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + cc->qs = qs; + } + else + { + if (ds->num_issued < (uint64_t) cnt) + { + report_emergency_by_count (issue, + ds->num_issued, + cnt, + &ds->denom_risk); + } + if (GNUNET_YES == ds->report_emergency) + { + report_emergency_by_amount (issue, + &ds->denom_risk, + &ds->denom_loss); + + } + if (ds->in_db) + qs = TALER_ARL_adb->update_denomination_balance (TALER_ARL_adb->cls, + TALER_ARL_asession, + denom_hash, + &ds->denom_balance, + &ds->denom_loss, + &ds->denom_risk, + &ds->denom_recoup, + ds->num_issued); + else + qs = TALER_ARL_adb->insert_denomination_balance (TALER_ARL_adb->cls, + TALER_ARL_asession, + denom_hash, + &ds->denom_balance, + &ds->denom_loss, + &ds->denom_risk, + &ds->denom_recoup, + ds->num_issued); + } + } + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + cc->qs = qs; + } + GNUNET_assert (GNUNET_YES == + GNUNET_CONTAINER_multihashmap_remove (cc->denom_summaries, + denom_hash, + ds)); + GNUNET_free (ds); + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != cc->qs) + return GNUNET_SYSERR; + 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 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_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_DenominationKeyValidityPS *issue; + struct TALER_Amount value; + enum GNUNET_DB_QueryStatus qs; + + (void) h_blind_ev; + (void) reserve_pub; + (void) reserve_sig; + (void) execution_date; + (void) amount_with_fee; + GNUNET_assert (rowid >= ppc.last_withdraw_serial_id); /* should be monotonically increasing */ + ppc.last_withdraw_serial_id = rowid + 1; + + qs = TALER_ARL_get_denomination_info (denom_pub, + &issue, + &dh); + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + { + report_row_inconsistency ("withdraw", + rowid, + "denomination key not found"); + return GNUNET_OK; + } + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) + { + /* This really ought to be a transient DB error. */ + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + cc->qs = qs; + return GNUNET_SYSERR; + } + ds = get_denomination_summary (cc, + issue, + &dh); + if (NULL == ds) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + TALER_amount_ntoh (&value, + &issue->value); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Issued coin in denomination `%s' of total value %s\n", + GNUNET_h2s (&dh), + TALER_amount2s (&value)); + ds->num_issued++; + if (GNUNET_OK != + TALER_amount_add (&ds->denom_balance, + &ds->denom_balance, + &value)) + { + GNUNET_break (0); + cc->qs = GNUNET_DB_STATUS_HARD_ERROR; + 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 (&total_escrow_balance, + &total_escrow_balance, + &value)) + { + GNUNET_break (0); + cc->qs = GNUNET_DB_STATUS_HARD_ERROR; + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_amount_add (&total_risk, + &total_risk, + &value)) + { + GNUNET_break (0); + cc->qs = GNUNET_DB_STATUS_HARD_ERROR; + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_amount_add (&ds->denom_risk, + &ds->denom_risk, + &value)) + { + GNUNET_break (0); + cc->qs = GNUNET_DB_STATUS_HARD_ERROR; + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Closure for #reveal_data_cb(). + */ +struct RevealContext +{ + + /** + * Denomination public keys of the new coins. + */ + struct TALER_DenominationPublicKey *new_dps; + + /** + * Size of the @a new_dp and @a new_dps arrays. + */ + unsigned int num_freshcoins; +}; + + +/** + * Function called with information about a refresh order. + * + * @param cls closure + * @param num_freshcoins size of the @a rrcs array + * @param rrcs array of @a num_freshcoins information about coins to be created + * @param num_tprivs number of entries in @a tprivs, should be #TALER_CNC_KAPPA - 1 + * @param tprivs array of @e num_tprivs transfer private keys + * @param tp transfer public key information + */ +static void +reveal_data_cb (void *cls, + uint32_t num_freshcoins, + const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs, + unsigned int num_tprivs, + const struct TALER_TransferPrivateKeyP *tprivs, + const struct TALER_TransferPublicKeyP *tp) +{ + struct RevealContext *rctx = cls; + + (void) num_tprivs; + (void) tprivs; + (void) tp; + rctx->num_freshcoins = num_freshcoins; + rctx->new_dps = GNUNET_new_array (num_freshcoins, + struct TALER_DenominationPublicKey); + for (unsigned int i = 0; inew_dps[i].rsa_public_key + = GNUNET_CRYPTO_rsa_public_key_dup (rrcs[i].denom_pub.rsa_public_key); +} + + +/** + * Check that the @a coin_pub is a known coin with a proper + * signature for denominatinon @a denom_pub. If not, TALER_ARL_report + * a loss of @a loss_potential. + * + * @param coin_pub public key of a coin + * @param denom_pub expected denomination of the coin + * @param loss_potential how big could the loss be if the coin is + * not properly signed + * @return database transaction status, on success + * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT + */ +static enum GNUNET_DB_QueryStatus +check_known_coin (const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_DenominationPublicKey *denom_pub, + const struct TALER_Amount *loss_potential) +{ + struct TALER_CoinPublicInfo ci; + enum GNUNET_DB_QueryStatus qs; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Checking denomination signature on %s\n", + TALER_B2S (coin_pub)); + qs = TALER_ARL_edb->get_known_coin (TALER_ARL_edb->cls, + TALER_ARL_esession, + coin_pub, + &ci); + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + return qs; + } + if (GNUNET_YES != + TALER_test_coin_valid (&ci, + denom_pub)) + { + TALER_ARL_report (report_bad_sig_losses, + json_pack ("{s:s, s:I, s:o, s:o}", + "operation", "known-coin", + "row", (json_int_t) -1, + "loss", TALER_JSON_from_amount ( + loss_potential), + "key_pub", GNUNET_JSON_from_data_auto ( + coin_pub))); + GNUNET_break (GNUNET_OK == + TALER_amount_add (&total_bad_sig_loss, + &total_bad_sig_loss, + loss_potential)); + + } + GNUNET_CRYPTO_rsa_signature_free (ci.denom_sig.rsa_signature); + return qs; +} + + +/** + * 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 noreveal_index which index was picked by the exchange in cut-and-choose + * @param rc what is the refresh commitment + * @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, + uint32_t noreveal_index, + const struct TALER_RefreshCommitmentP *rc) +{ + struct CoinContext *cc = cls; + struct TALER_RefreshMeltCoinAffirmationPS rmc; + const struct TALER_DenominationKeyValidityPS *issue; + struct DenominationSummary *dso; + struct TALER_Amount amount_without_fee; + struct TALER_Amount tmp; + enum GNUNET_DB_QueryStatus qs; + + (void) noreveal_index; + GNUNET_assert (rowid >= ppc.last_melt_serial_id); /* should be monotonically increasing */ + ppc.last_melt_serial_id = rowid + 1; + + qs = TALER_ARL_get_denomination_info (denom_pub, + &issue, + NULL); + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + { + report_row_inconsistency ("melt", + rowid, + "denomination key not found"); + return GNUNET_OK; + } + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + cc->qs = qs; + return GNUNET_SYSERR; + } + qs = check_known_coin (coin_pub, + denom_pub, + amount_with_fee); + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + cc->qs = qs; + return GNUNET_SYSERR; + } + + /* verify melt signature */ + rmc.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT); + rmc.purpose.size = htonl (sizeof (rmc)); + rmc.rc = *rc; + TALER_amount_hton (&rmc.amount_with_fee, + amount_with_fee); + rmc.melt_fee = issue->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)) + { + TALER_ARL_report (report_bad_sig_losses, + json_pack ("{s:s, s:I, s:o, s:o}", + "operation", "melt", + "row", (json_int_t) rowid, + "loss", TALER_JSON_from_amount ( + amount_with_fee), + "key_pub", GNUNET_JSON_from_data_auto ( + coin_pub))); + GNUNET_break (GNUNET_OK == + TALER_amount_add (&total_bad_sig_loss, + &total_bad_sig_loss, + amount_with_fee)); + 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 (&issue->denom_hash), + TALER_amount2s (amount_with_fee)); + + { + struct RevealContext reveal_ctx; + struct TALER_Amount refresh_cost; + int err; + + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (amount_with_fee->currency, + &refresh_cost)); + memset (&reveal_ctx, + 0, + sizeof (reveal_ctx)); + qs = TALER_ARL_edb->get_refresh_reveal (TALER_ARL_edb->cls, + TALER_ARL_esession, + rc, + &reveal_data_cb, + &reveal_ctx); + if (0 > qs) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + cc->qs = GNUNET_DB_STATUS_HARD_ERROR; + return GNUNET_SYSERR; + } + if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) || + (0 == reveal_ctx.num_freshcoins) ) + { + /* This can happen if /refresh/reveal was not yet called or only + with invalid data, even if the exchange is correctly + operating. We still TALER_ARL_report it. */ + TALER_ARL_report (report_refreshs_hanging, + json_pack ("{s:I, s:o, s:o}", + "row", (json_int_t) rowid, + "amount", TALER_JSON_from_amount ( + amount_with_fee), + "coin_pub", GNUNET_JSON_from_data_auto ( + coin_pub))); + GNUNET_break (GNUNET_OK == + TALER_amount_add (&total_refresh_hanging, + &total_refresh_hanging, + amount_with_fee)); + return GNUNET_OK; + } + + { + const struct TALER_DenominationKeyValidityPS *new_issues[reveal_ctx. + num_freshcoins]; + + /* Update outstanding amounts for all new coin's denominations, and check + that the resulting amounts are consistent with the value being refreshed. */ + err = GNUNET_OK; + for (unsigned int i = 0; iqs = qs; + err = GNUNET_SYSERR; /* terminate, return GNUNET_SYSERR */ + } + GNUNET_CRYPTO_rsa_public_key_free ( + reveal_ctx.new_dps[i].rsa_public_key); + reveal_ctx.new_dps[i].rsa_public_key = NULL; + } + GNUNET_free (reveal_ctx.new_dps); + reveal_ctx.new_dps = NULL; + + if (GNUNET_OK != err) + return (GNUNET_SYSERR == err) ? GNUNET_SYSERR : GNUNET_OK; + + /* calculate total refresh cost */ + for (unsigned int i = 0; ifee_withdraw); + TALER_amount_ntoh (&value, + &new_issues[i]->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); + cc->qs = GNUNET_DB_STATUS_HARD_ERROR; + return GNUNET_SYSERR; + } + } + + /* compute contribution of old coin */ + { + struct TALER_Amount melt_fee; + + TALER_amount_ntoh (&melt_fee, + &issue->fee_refresh); + if (GNUNET_OK != + TALER_amount_subtract (&amount_without_fee, + amount_with_fee, + &melt_fee)) + { + GNUNET_break (0); + cc->qs = GNUNET_DB_STATUS_HARD_ERROR; + 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_amount_arithmetic_inconsistency ("melt (fee)", + rowid, + &amount_without_fee, + &refresh_cost, + -1); + return GNUNET_OK; + } + + /* update outstanding denomination amounts */ + for (unsigned int i = 0; idenom_hash); + if (NULL == dsi) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + TALER_amount_ntoh (&value, + &new_issues[i]->value); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Created fresh coin in denomination `%s' of value %s\n", + GNUNET_h2s (&new_issues[i]->denom_hash), + TALER_amount2s (&value)); + dsi->num_issued++; + if (GNUNET_OK != + TALER_amount_add (&dsi->denom_balance, + &dsi->denom_balance, + &value)) + { + GNUNET_break (0); + cc->qs = GNUNET_DB_STATUS_HARD_ERROR; + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_amount_add (&dsi->denom_risk, + &dsi->denom_risk, + &value)) + { + GNUNET_break (0); + cc->qs = GNUNET_DB_STATUS_HARD_ERROR; + return GNUNET_SYSERR; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "New balance of denomination `%s' is %s\n", + GNUNET_h2s (&new_issues[i]->denom_hash), + TALER_amount2s (&dsi->denom_balance)); + if (GNUNET_OK != + TALER_amount_add (&total_escrow_balance, + &total_escrow_balance, + &value)) + { + GNUNET_break (0); + cc->qs = GNUNET_DB_STATUS_HARD_ERROR; + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_amount_add (&total_risk, + &total_risk, + &value)) + { + GNUNET_break (0); + cc->qs = GNUNET_DB_STATUS_HARD_ERROR; + return GNUNET_SYSERR; + } + } + } + } + + /* update old coin's denomination balance */ + dso = get_denomination_summary (cc, + issue, + &issue->denom_hash); + if (NULL == dso) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (GNUNET_SYSERR == + TALER_amount_subtract (&tmp, + &dso->denom_balance, + amount_with_fee)) + { + GNUNET_assert (GNUNET_OK == + TALER_amount_add (&dso->denom_loss, + &dso->denom_loss, + amount_with_fee)); + dso->report_emergency = GNUNET_YES; + } + else + { + dso->denom_balance = tmp; + } + if (-1 == TALER_amount_cmp (&total_escrow_balance, + amount_with_fee)) + { + /* This can theoretically happen if for example the exchange + never issued any coins (i.e. escrow balance is zero), but + accepted a forged coin (i.e. emergency situation after + private key compromise). In that case, we cannot even + subtract the profit we make from the fee from the escrow + balance. Tested as part of test-auditor.sh, case #18 */report_amount_arithmetic_inconsistency ( + "subtracting refresh fee from escrow balance", + rowid, + &total_escrow_balance, + amount_with_fee, + 0); + } + else + { + GNUNET_assert (GNUNET_SYSERR != + TALER_amount_subtract (&total_escrow_balance, + &total_escrow_balance, + amount_with_fee)); + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "New balance of denomination `%s' after melt is %s\n", + GNUNET_h2s (&issue->denom_hash), + TALER_amount2s (&dso->denom_balance)); + + /* update global melt fees */ + { + struct TALER_Amount rfee; + + TALER_amount_ntoh (&rfee, + &issue->fee_refresh); + if (GNUNET_OK != + TALER_amount_add (&total_melt_fee_income, + &total_melt_fee_income, + &rfee)) + { + GNUNET_break (0); + cc->qs = GNUNET_DB_STATUS_HARD_ERROR; + return GNUNET_SYSERR; + } + } + + /* 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. + * + * @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_contract_terms 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_contract_terms, + 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_DenominationKeyValidityPS *issue; + struct DenominationSummary *ds; + struct TALER_DepositRequestPS dr; + struct TALER_Amount tmp; + enum GNUNET_DB_QueryStatus qs; + + (void) wire_deadline; + (void) done; + GNUNET_assert (rowid >= ppc.last_deposit_serial_id); /* should be monotonically increasing */ + ppc.last_deposit_serial_id = rowid + 1; + + qs = TALER_ARL_get_denomination_info (denom_pub, + &issue, + NULL); + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + { + report_row_inconsistency ("deposits", + rowid, + "denomination key not found"); + return GNUNET_OK; + } + + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + cc->qs = qs; + return GNUNET_SYSERR; + } + qs = check_known_coin (coin_pub, + denom_pub, + amount_with_fee); + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + cc->qs = qs; + return GNUNET_SYSERR; + } + + /* Verify deposit signature */ + dr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT); + dr.purpose.size = htonl (sizeof (dr)); + dr.h_contract_terms = *h_contract_terms; + if (GNUNET_OK != + TALER_JSON_merchant_wire_signature_hash (receiver_wire_account, + &dr.h_wire)) + { + TALER_ARL_report (report_bad_sig_losses, + json_pack ("{s:s, s:I, s:o, s:o}", + "operation", "deposit", + "row", (json_int_t) rowid, + "loss", TALER_JSON_from_amount ( + amount_with_fee), + "key_pub", GNUNET_JSON_from_data_auto ( + coin_pub))); + GNUNET_break (GNUNET_OK == + TALER_amount_add (&total_bad_sig_loss, + &total_bad_sig_loss, + amount_with_fee)); + return GNUNET_OK; + } + 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 = issue->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)) + { + TALER_ARL_report (report_bad_sig_losses, + json_pack ("{s:s, s:I, s:o, s:o}", + "operation", "deposit", + "row", (json_int_t) rowid, + "loss", TALER_JSON_from_amount ( + amount_with_fee), + "key_pub", GNUNET_JSON_from_data_auto ( + coin_pub))); + GNUNET_break (GNUNET_OK == + TALER_amount_add (&total_bad_sig_loss, + &total_bad_sig_loss, + amount_with_fee)); + 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 (&issue->denom_hash), + TALER_amount2s (amount_with_fee)); + + /* update old coin's denomination balance */ + ds = get_denomination_summary (cc, + issue, + &issue->denom_hash); + if (NULL == ds) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (GNUNET_SYSERR == + TALER_amount_subtract (&tmp, + &ds->denom_balance, + amount_with_fee)) + { + GNUNET_assert (GNUNET_OK == + TALER_amount_add (&ds->denom_loss, + &ds->denom_loss, + amount_with_fee)); + ds->report_emergency = GNUNET_YES; + } + else + { + ds->denom_balance = tmp; + } + + if (-1 == TALER_amount_cmp (&total_escrow_balance, + amount_with_fee)) + { + /* This can theoretically happen if for example the exchange + never issued any coins (i.e. escrow balance is zero), but + accepted a forged coin (i.e. emergency situation after + private key compromise). In that case, we cannot even + subtract the profit we make from the fee from the escrow + balance. Tested as part of test-auditor.sh, case #18 */report_amount_arithmetic_inconsistency ( + "subtracting deposit fee from escrow balance", + rowid, + &total_escrow_balance, + amount_with_fee, + 0); + } + else + { + GNUNET_assert (GNUNET_SYSERR != + TALER_amount_subtract (&total_escrow_balance, + &total_escrow_balance, + amount_with_fee)); + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "New balance of denomination `%s' after deposit is %s\n", + GNUNET_h2s (&issue->denom_hash), + TALER_amount2s (&ds->denom_balance)); + + /* update global up melt fees */ + { + struct TALER_Amount dfee; + + TALER_amount_ntoh (&dfee, + &issue->fee_deposit); + if (GNUNET_OK != + TALER_amount_add (&total_deposit_fee_income, + &total_deposit_fee_income, + &dfee)) + { + GNUNET_break (0); + cc->qs = GNUNET_DB_STATUS_HARD_ERROR; + return GNUNET_SYSERR; + } + } + + 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. + * + * @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_contract_terms 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 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_contract_terms, + uint64_t rtransaction_id, + const struct TALER_Amount *amount_with_fee) +{ + struct CoinContext *cc = cls; + const struct TALER_DenominationKeyValidityPS *issue; + struct DenominationSummary *ds; + struct TALER_RefundRequestPS rr; + struct TALER_Amount amount_without_fee; + struct TALER_Amount refund_fee; + enum GNUNET_DB_QueryStatus qs; + + GNUNET_assert (rowid >= ppc.last_refund_serial_id); /* should be monotonically increasing */ + ppc.last_refund_serial_id = rowid + 1; + + qs = TALER_ARL_get_denomination_info (denom_pub, + &issue, + NULL); + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + { + report_row_inconsistency ("refunds", + rowid, + "denomination key not found"); + return GNUNET_SYSERR; + } + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + return GNUNET_SYSERR; + } + + /* verify refund signature */ + rr.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND); + rr.purpose.size = htonl (sizeof (rr)); + rr.h_contract_terms = *h_contract_terms; + 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 = issue->fee_refund; + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_REFUND, + &rr.purpose, + &merchant_sig->eddsa_sig, + &merchant_pub->eddsa_pub)) + { + TALER_ARL_report (report_bad_sig_losses, + json_pack ("{s:s, s:I, s:o, s:o}", + "operation", "refund", + "row", (json_int_t) rowid, + "loss", TALER_JSON_from_amount ( + amount_with_fee), + "key_pub", GNUNET_JSON_from_data_auto ( + merchant_pub))); + GNUNET_break (GNUNET_OK == + TALER_amount_add (&total_bad_sig_loss, + &total_bad_sig_loss, + amount_with_fee)); + return GNUNET_OK; + } + + TALER_amount_ntoh (&refund_fee, + &issue->fee_refund); + if (GNUNET_OK != + TALER_amount_subtract (&amount_without_fee, + amount_with_fee, + &refund_fee)) + { + report_amount_arithmetic_inconsistency ("refund (fee)", + rowid, + &amount_without_fee, + &refund_fee, + -1); + return GNUNET_OK; + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Refunding coin %s in denomination `%s' value %s\n", + TALER_B2S (coin_pub), + GNUNET_h2s (&issue->denom_hash), + TALER_amount2s (amount_with_fee)); + + /* update coin's denomination balance */ + ds = get_denomination_summary (cc, + issue, + &issue->denom_hash); + if (NULL == ds) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_amount_add (&ds->denom_balance, + &ds->denom_balance, + &amount_without_fee)) + { + GNUNET_break (0); + cc->qs = GNUNET_DB_STATUS_HARD_ERROR; + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_amount_add (&ds->denom_risk, + &ds->denom_risk, + &amount_without_fee)) + { + GNUNET_break (0); + cc->qs = GNUNET_DB_STATUS_HARD_ERROR; + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_amount_add (&total_escrow_balance, + &total_escrow_balance, + &amount_without_fee)) + { + GNUNET_break (0); + cc->qs = GNUNET_DB_STATUS_HARD_ERROR; + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_amount_add (&total_risk, + &total_risk, + &amount_without_fee)) + { + GNUNET_break (0); + cc->qs = GNUNET_DB_STATUS_HARD_ERROR; + return GNUNET_SYSERR; + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "New balance of denomination `%s' after refund is %s\n", + GNUNET_h2s (&issue->denom_hash), + TALER_amount2s (&ds->denom_balance)); + + /* update total refund fee balance */ + if (GNUNET_OK != + TALER_amount_add (&total_refund_fee_income, + &total_refund_fee_income, + &refund_fee)) + { + GNUNET_break (0); + cc->qs = GNUNET_DB_STATUS_HARD_ERROR; + return GNUNET_SYSERR; + } + + return GNUNET_OK; +} + + +/** + * Check that the recoup operation was properly initiated by a coin + * and update the denomination's losses accordingly. + * + * @param cc the context with details about the coin + * @param rowid row identifier used to uniquely identify the recoup operation + * @param amount how much should be added back to the reserve + * @param coin public information about the coin + * @param denom_pub public key of the denomionation of @a coin + * @param coin_sig signature with @e coin_pub of type #TALER_SIGNATURE_WALLET_COIN_RECOUP + * @param coin_blind blinding factor used to blind the coin + * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop + */ +static int +check_recoup (struct CoinContext *cc, + uint64_t rowid, + const struct TALER_Amount *amount, + const struct TALER_CoinPublicInfo *coin, + const struct TALER_DenominationPublicKey *denom_pub, + const struct TALER_CoinSpendSignatureP *coin_sig, + const struct TALER_DenominationBlindingKeyP *coin_blind) +{ + struct TALER_RecoupRequestPS pr; + struct DenominationSummary *ds; + enum GNUNET_DB_QueryStatus qs; + const struct TALER_DenominationKeyValidityPS *issue; + + if (GNUNET_OK != + TALER_test_coin_valid (coin, + denom_pub)) + { + TALER_ARL_report (report_bad_sig_losses, + json_pack ("{s:s, s:I, s:o, s:o}", + "operation", "recoup", + "row", (json_int_t) rowid, + "loss", TALER_JSON_from_amount (amount), + "key_pub", GNUNET_JSON_from_data_auto ( + &pr.h_denom_pub))); + GNUNET_break (GNUNET_OK == + TALER_amount_add (&total_bad_sig_loss, + &total_bad_sig_loss, + amount)); + } + qs = TALER_ARL_get_denomination_info (denom_pub, + &issue, + &pr.h_denom_pub); + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + { + report_row_inconsistency ("recoup", + rowid, + "denomination key not found"); + return GNUNET_OK; + } + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) + { + /* The key not existing should be prevented by foreign key constraints, + so must be a transient DB error. */ + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + cc->qs = qs; + return GNUNET_SYSERR; + } + pr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_RECOUP); + pr.purpose.size = htonl (sizeof (pr)); + pr.coin_pub = coin->coin_pub; + pr.coin_blind = *coin_blind; + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_RECOUP, + &pr.purpose, + &coin_sig->eddsa_signature, + &coin->coin_pub.eddsa_pub)) + { + TALER_ARL_report (report_bad_sig_losses, + json_pack ("{s:s, s:I, s:o, s:o}", + "operation", "recoup", + "row", (json_int_t) rowid, + "loss", TALER_JSON_from_amount (amount), + "coin_pub", GNUNET_JSON_from_data_auto ( + &coin->coin_pub))); + GNUNET_break (GNUNET_OK == + TALER_amount_add (&total_bad_sig_loss, + &total_bad_sig_loss, + amount)); + return GNUNET_OK; + } + ds = get_denomination_summary (cc, + issue, + &issue->denom_hash); + if (GNUNET_NO == ds->was_revoked) + { + /* Woopsie, we allowed recoup on non-revoked denomination!? */ + TALER_ARL_report (report_bad_sig_losses, + json_pack ("{s:s, s:I, s:o, s:o}", + "operation", + "recoup (denomination not revoked)", + "row", (json_int_t) rowid, + "loss", TALER_JSON_from_amount (amount), + "coin_pub", GNUNET_JSON_from_data_auto ( + &coin->coin_pub))); + } + GNUNET_break (GNUNET_OK == + TALER_amount_add (&ds->denom_recoup, + &ds->denom_recoup, + amount)); + GNUNET_break (GNUNET_OK == + TALER_amount_add (&total_recoup_loss, + &total_recoup_loss, + amount)); + return GNUNET_OK; +} + + +/** + * Function called about recoups the exchange has to perform. + * + * @param cls a `struct CoinContext *` + * @param rowid row identifier used to uniquely identify the recoup operation + * @param timestamp when did we receive the recoup request + * @param amount how much should be added back to the reserve + * @param reserve_pub public key of the reserve + * @param coin public information about the coin + * @param denom_pub denomination public key of @a coin + * @param coin_sig signature with @e coin_pub of type #TALER_SIGNATURE_WALLET_COIN_RECOUP + * @param coin_blind blinding factor used to blind the coin + * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop + */ +static int +recoup_cb (void *cls, + uint64_t rowid, + struct GNUNET_TIME_Absolute timestamp, + const struct TALER_Amount *amount, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_CoinPublicInfo *coin, + const struct TALER_DenominationPublicKey *denom_pub, + const struct TALER_CoinSpendSignatureP *coin_sig, + const struct TALER_DenominationBlindingKeyP *coin_blind) +{ + struct CoinContext *cc = cls; + + (void) timestamp; + (void) reserve_pub; + return check_recoup (cc, + rowid, + amount, + coin, + denom_pub, + coin_sig, + coin_blind); +} + + +/** + * Function called about recoups on refreshed coins the exchange has to + * perform. + * + * @param cls a `struct CoinContext *` + * @param rowid row identifier used to uniquely identify the recoup operation + * @param timestamp when did we receive the recoup request + * @param amount how much should be added back to the reserve + * @param old_coin_pub original coin that was refreshed to create @a coin + * @param coin public information about the coin + * @param denom_pub denomination public key of @a coin + * @param coin_sig signature with @e coin_pub of type #TALER_SIGNATURE_WALLET_COIN_RECOUP + * @param coin_blind blinding factor used to blind the coin + * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop + */ +static int +recoup_refresh_cb (void *cls, + uint64_t rowid, + struct GNUNET_TIME_Absolute timestamp, + const struct TALER_Amount *amount, + const struct TALER_CoinSpendPublicKeyP *old_coin_pub, + const struct TALER_CoinPublicInfo *coin, + const struct TALER_DenominationPublicKey *denom_pub, + const struct TALER_CoinSpendSignatureP *coin_sig, + const struct TALER_DenominationBlindingKeyP *coin_blind) +{ + struct CoinContext *cc = cls; + + (void) timestamp; + (void) old_coin_pub; + return check_recoup (cc, + rowid, + amount, + coin, + denom_pub, + coin_sig, + coin_blind); +} + + +/** + * Analyze the exchange's processing of coins. + * + * @param cls closure + * @return transaction status code + */ +static enum GNUNET_DB_QueryStatus +analyze_coins (void *cls) +{ + struct CoinContext cc; + enum GNUNET_DB_QueryStatus qs; + enum GNUNET_DB_QueryStatus qsx; + enum GNUNET_DB_QueryStatus qsp; + + (void) cls; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Analyzing coins\n"); + qsp = TALER_ARL_adb->get_auditor_progress_coin (TALER_ARL_adb->cls, + TALER_ARL_asession, + &TALER_ARL_master_pub, + &ppc); + if (0 > qsp) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qsp); + return qsp; + } + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qsp) + { + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "First analysis using this auditor, starting from scratch\n"); + } + else + { + ppc_start = ppc; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Resuming coin audit at %llu/%llu/%llu/%llu/%llu\n", + (unsigned long long) ppc.last_deposit_serial_id, + (unsigned long long) ppc.last_melt_serial_id, + (unsigned long long) ppc.last_refund_serial_id, + (unsigned long long) ppc.last_withdraw_serial_id, + (unsigned long long) ppc.last_recoup_refresh_serial_id); + } + + /* setup 'cc' */ + cc.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; + cc.denom_summaries = GNUNET_CONTAINER_multihashmap_create (256, + GNUNET_NO); + qsx = TALER_ARL_adb->get_balance_summary (TALER_ARL_adb->cls, + TALER_ARL_asession, + &TALER_ARL_master_pub, + &total_escrow_balance, + &total_deposit_fee_income, + &total_melt_fee_income, + &total_refund_fee_income, + &total_risk, + &total_recoup_loss, + &total_irregular_recoups); + if (0 > qsx) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qsx); + return qsx; + } + + /* process withdrawals */ + if (0 > + (qs = TALER_ARL_edb->select_withdrawals_above_serial_id ( + TALER_ARL_edb->cls, + TALER_ARL_esession, + ppc. + last_withdraw_serial_id, + &withdraw_cb, + &cc)) ) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + return qs; + } + if (0 > cc.qs) + return cc.qs; + + /* process refunds */ + if (0 > + (qs = TALER_ARL_edb->select_refunds_above_serial_id (TALER_ARL_edb->cls, + TALER_ARL_esession, + ppc. + last_refund_serial_id, + &refund_cb, + &cc))) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + return qs; + } + if (0 > cc.qs) + return cc.qs; + + /* process refreshs */ + if (0 > + (qs = TALER_ARL_edb->select_refreshes_above_serial_id (TALER_ARL_edb->cls, + TALER_ARL_esession, + ppc. + last_melt_serial_id, + &refresh_session_cb, + &cc))) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + return qs; + } + if (0 > cc.qs) + return cc.qs; + + /* process deposits */ + if (0 > + (qs = TALER_ARL_edb->select_deposits_above_serial_id (TALER_ARL_edb->cls, + TALER_ARL_esession, + ppc. + last_deposit_serial_id, + &deposit_cb, + &cc))) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + return qs; + } + if (0 > cc.qs) + return cc.qs; + + /* process recoups */ + if (0 > + (qs = TALER_ARL_edb->select_recoup_above_serial_id (TALER_ARL_edb->cls, + TALER_ARL_esession, + ppc. + last_recoup_serial_id, + &recoup_cb, + &cc))) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + return qs; + } + if (0 > cc.qs) + return cc.qs; + if (0 > + (qs = TALER_ARL_edb->select_recoup_refresh_above_serial_id ( + TALER_ARL_edb->cls, + TALER_ARL_esession, + ppc. + last_recoup_refresh_serial_id, + & + recoup_refresh_cb, + &cc))) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + return qs; + } + if (0 > cc.qs) + return cc.qs; + + /* sync 'cc' back to disk */ + cc.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; + GNUNET_CONTAINER_multihashmap_iterate (cc.denom_summaries, + &sync_denomination, + &cc); + GNUNET_CONTAINER_multihashmap_destroy (cc.denom_summaries); + if (0 > cc.qs) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == cc.qs); + return cc.qs; + } + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qsx) + qs = TALER_ARL_adb->update_balance_summary (TALER_ARL_adb->cls, + TALER_ARL_asession, + &TALER_ARL_master_pub, + &total_escrow_balance, + &total_deposit_fee_income, + &total_melt_fee_income, + &total_refund_fee_income, + &total_risk, + &total_recoup_loss, + &total_irregular_recoups); + else + qs = TALER_ARL_adb->insert_balance_summary (TALER_ARL_adb->cls, + TALER_ARL_asession, + &TALER_ARL_master_pub, + &total_escrow_balance, + &total_deposit_fee_income, + &total_melt_fee_income, + &total_refund_fee_income, + &total_risk, + &total_recoup_loss, + &total_irregular_recoups); + if (0 >= qs) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + return qs; + } + + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qsp) + qs = TALER_ARL_adb->update_auditor_progress_coin (TALER_ARL_adb->cls, + TALER_ARL_asession, + &TALER_ARL_master_pub, + &ppc); + else + qs = TALER_ARL_adb->insert_auditor_progress_coin (TALER_ARL_adb->cls, + TALER_ARL_asession, + &TALER_ARL_master_pub, + &ppc); + if (0 >= qs) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Failed to update auditor DB, not recording progress\n"); + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + return qs; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + _ ("Concluded coin audit step at %llu/%llu/%llu/%llu/%llu\n"), + (unsigned long long) ppc.last_deposit_serial_id, + (unsigned long long) ppc.last_melt_serial_id, + (unsigned long long) ppc.last_refund_serial_id, + (unsigned long long) ppc.last_withdraw_serial_id, + (unsigned long long) ppc.last_recoup_refresh_serial_id); + return qs; +} + + +/** + * Main function that will be run. + * + * @param cls closure + * @param args remaining command-line arguments + * @param TALER_ARL_cfgfile name of the configuration file used (for saving, can be NULL!) + * @param c configuration + */ +static void +run (void *cls, + char *const *args, + const char *TALER_ARL_cfgfile, + const struct GNUNET_CONFIGURATION_Handle *c) +{ + json_t *report; + + (void) cls; + (void) args; + (void) TALER_ARL_cfgfile; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Launching auditor\n"); + if (GNUNET_OK != + TALER_ARL_init (c)) + { + global_ret = 1; + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Starting audit\n"); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (TALER_ARL_currency, + &reported_emergency_loss)); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (TALER_ARL_currency, + & + reported_emergency_risk_by_amount)); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (TALER_ARL_currency, + & + reported_emergency_risk_by_count)); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (TALER_ARL_currency, + & + reported_emergency_loss_by_count)); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (TALER_ARL_currency, + &total_escrow_balance)); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (TALER_ARL_currency, + &total_risk)); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (TALER_ARL_currency, + &total_recoup_loss)); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (TALER_ARL_currency, + &total_irregular_recoups)); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (TALER_ARL_currency, + &total_deposit_fee_income)); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (TALER_ARL_currency, + &total_melt_fee_income)); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (TALER_ARL_currency, + &total_refund_fee_income)); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (TALER_ARL_currency, + &total_arithmetic_delta_plus)); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (TALER_ARL_currency, + &total_arithmetic_delta_minus)); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (TALER_ARL_currency, + &total_bad_sig_loss)); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (TALER_ARL_currency, + &total_refresh_hanging)); + GNUNET_assert (NULL != + (report_emergencies = json_array ())); + GNUNET_assert (NULL != + (report_emergencies_by_count = json_array ())); + GNUNET_assert (NULL != + (report_row_inconsistencies = json_array ())); + GNUNET_assert (NULL != + (report_amount_arithmetic_inconsistencies = + json_array ())); + GNUNET_assert (NULL != + (report_bad_sig_losses = json_array ())); + GNUNET_assert (NULL != + (report_refreshs_hanging = json_array ())); + if (GNUNET_OK != + TALER_ARL_setup_sessions_and_run (&analyze_coins, + NULL)) + { + global_ret = 1; + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Audit complete\n"); + report = json_pack ("{s:o, s:o, s:o, s:o, s:o," + " s:o, s:o, s:o, s:o, s:o," + " s:o, s:o, s:o, s:o, s:o," + " s:o, s:o, s:o, s:o, s:o," + " s:I, s:I, s:I, s:I, s:I," + " s:I, s:I, s:I, s:I, s:I," + " s:I, s:I, s:o, s:o, s:o}", + /* Block #1 */ + "total_escrow_balance", + TALER_JSON_from_amount (&total_escrow_balance), + "total_active_risk", + TALER_JSON_from_amount (&total_risk), + "total_deposit_fee_income", + TALER_JSON_from_amount ( + &total_deposit_fee_income), + "total_melt_fee_income", + TALER_JSON_from_amount (&total_melt_fee_income), + "total_refund_fee_income", + TALER_JSON_from_amount ( + &total_refund_fee_income), + /* Block #2 */ + /* Tested in test-auditor.sh #18 */ + "emergencies", + report_emergencies, + /* Tested in test-auditor.sh #18 */ + "emergencies_risk_by_amount", + TALER_JSON_from_amount ( + &reported_emergency_risk_by_amount), + /* Tested in test-auditor.sh #4/#5/#6/#7/#13 */ + "bad_sig_losses", + report_bad_sig_losses, + /* Tested in test-auditor.sh #4/#5/#6/#7/#13 */ + "total_bad_sig_loss", + TALER_JSON_from_amount (&total_bad_sig_loss), + /* Tested in test-auditor.sh #14/#15 */ + "row_inconsistencies", + report_row_inconsistencies, + /* Block #3 */ + "amount_arithmetic_inconsistencies", + report_amount_arithmetic_inconsistencies, + "total_arithmetic_delta_plus", + TALER_JSON_from_amount ( + &total_arithmetic_delta_plus), + "total_arithmetic_delta_minus", + TALER_JSON_from_amount ( + &total_arithmetic_delta_minus), + /* Tested in test-auditor.sh #12 */ + "total_refresh_hanging", + TALER_JSON_from_amount (&total_refresh_hanging), + /* Tested in test-auditor.sh #12 */ + "refresh_hanging", + report_refreshs_hanging, + /* Block #4 */ + "total_recoup_loss", + TALER_JSON_from_amount (&total_recoup_loss), + /* Tested in test-auditor.sh #18 */ + "emergencies_by_count", + report_emergencies_by_count, + /* Tested in test-auditor.sh #18 */ + "emergencies_risk_by_count", + TALER_JSON_from_amount ( + &reported_emergency_risk_by_count), + /* Tested in test-auditor.sh #18 */ + "emergencies_loss", + TALER_JSON_from_amount ( + &reported_emergency_loss), + /* Tested in test-auditor.sh #18 */ + "emergencies_loss_by_count", + TALER_JSON_from_amount ( + &reported_emergency_loss_by_count), + /* Block #5 */ + "start_ppc_withdraw_serial_id", + (json_int_t) ppc_start.last_withdraw_serial_id, + "start_ppc_deposit_serial_id", + (json_int_t) ppc_start.last_deposit_serial_id, + "start_ppc_melt_serial_id", + (json_int_t) ppc_start.last_melt_serial_id, + "start_ppc_refund_serial_id", + (json_int_t) ppc_start.last_refund_serial_id, + "start_ppc_recoup_serial_id", + (json_int_t) ppc_start.last_recoup_serial_id, + /* Block #6 */ + "start_ppc_recoup_refresh_serial_id", + (json_int_t) ppc_start. + last_recoup_refresh_serial_id, + "end_ppc_withdraw_serial_id", + (json_int_t) ppc.last_withdraw_serial_id, + "end_ppc_deposit_serial_id", + (json_int_t) ppc.last_deposit_serial_id, + "end_ppc_melt_serial_id", + (json_int_t) ppc.last_melt_serial_id, + "end_ppc_refund_serial_id", + (json_int_t) ppc.last_refund_serial_id, + /* Block #7 */ + "end_ppc_recoup_serial_id", + (json_int_t) ppc.last_recoup_serial_id, + "end_ppc_recoup_refresh_serial_id", + (json_int_t) ppc.last_recoup_refresh_serial_id, + "auditor_start_time", json_string ( + GNUNET_STRINGS_absolute_time_to_string ( + start_time)), + "auditor_end_time", json_string ( + GNUNET_STRINGS_absolute_time_to_string ( + GNUNET_TIME_absolute_get ())), + "total_irregular_recoups", + TALER_JSON_from_amount ( + &total_irregular_recoups) + ); + GNUNET_break (NULL != report); + TALER_ARL_done (report); +} + + +/** + * The main function of the database initialization tool. + * Used to initialize the Taler Exchange's database. + * + * @param argc number of arguments from the command line + * @param argv command line arguments + * @return 0 ok, 1 on error + */ +int +main (int argc, + char *const *argv) +{ + const struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_option_base32_auto ('m', + "exchange-key", + "KEY", + "public key of the exchange (Crockford base32 encoded)", + &TALER_ARL_master_pub), + GNUNET_GETOPT_option_flag ('r', + "TALER_ARL_restart", + "TALER_ARL_restart audit from the beginning (required on first run)", + &TALER_ARL_restart), + GNUNET_GETOPT_option_timetravel ('T', + "timetravel"), + GNUNET_GETOPT_OPTION_END + }; + + /* force linker to link against libtalerutil; if we do + not do this, the linker may "optimize" libtalerutil + away and skip #TALER_OS_init(), which we do need */ + (void) TALER_project_data_default (); + GNUNET_assert (GNUNET_OK == + GNUNET_log_setup ("taler-auditor", + "MESSAGE", + NULL)); + if (GNUNET_OK != + GNUNET_PROGRAM_run (argc, + argv, + "taler-auditor", + "Audit Taler exchange database", + options, + &run, + NULL)) + return 1; + return global_ret; +} + + +/* end of taler-auditor.c */ -- cgit v1.2.3