summaryrefslogtreecommitdiff
path: root/src/auditor/taler-auditor-reserves.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/auditor/taler-auditor-reserves.c')
-rw-r--r--src/auditor/taler-auditor-reserves.c1641
1 files changed, 1641 insertions, 0 deletions
diff --git a/src/auditor/taler-auditor-reserves.c b/src/auditor/taler-auditor-reserves.c
new file mode 100644
index 000000000..2fe103c81
--- /dev/null
+++ b/src/auditor/taler-auditor-reserves.c
@@ -0,0 +1,1641 @@
+/*
+ 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 <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file auditor/taler-auditor-reserves.c
+ * @brief audits the reserves of an exchange database
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#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"
+
+
+/**
+ * Use a 1 day grace period to deal with clocks not being perfectly synchronized.
+ */
+#define CLOSING_GRACE_PERIOD GNUNET_TIME_UNIT_DAYS
+
+/**
+ * Return value from main().
+ */
+static int global_ret;
+
+/**
+ * After how long should idle reserves be closed?
+ */
+static struct GNUNET_TIME_Relative idle_reserve_expiration_time;
+
+/**
+ * Checkpointing our progress for reserves.
+ */
+static struct TALER_AUDITORDB_ProgressPointReserve ppr;
+
+/**
+ * Checkpointing our progress for reserves.
+ */
+static struct TALER_AUDITORDB_ProgressPointReserve ppr_start;
+
+/**
+ * Array of reports about row inconsitencies.
+ */
+static json_t *report_row_inconsistencies;
+
+/**
+ * Array of reports about the denomination key not being
+ * valid at the time of withdrawal.
+ */
+static json_t *denomination_key_validity_withdraw_inconsistencies;
+
+/**
+ * Array of reports about reserve balance insufficient inconsitencies.
+ */
+static json_t *report_reserve_balance_insufficient_inconsistencies;
+
+/**
+ * Total amount reserves were charged beyond their balance.
+ */
+static struct TALER_Amount total_balance_insufficient_loss;
+
+/**
+ * Array of reports about reserve balance summary wrong in database.
+ */
+static json_t *report_reserve_balance_summary_wrong_inconsistencies;
+
+/**
+ * Total delta between expected and stored reserve balance summaries,
+ * for positive deltas.
+ */
+static struct TALER_Amount total_balance_summary_delta_plus;
+
+/**
+ * Total delta between expected and stored reserve balance summaries,
+ * for negative deltas.
+ */
+static struct TALER_Amount total_balance_summary_delta_minus;
+
+/**
+ * Array of reports about reserve's not being closed inconsitencies.
+ */
+static json_t *report_reserve_not_closed_inconsistencies;
+
+/**
+ * Total amount affected by reserves not having been closed on time.
+ */
+static struct TALER_Amount total_balance_reserve_not_closed;
+
+/**
+ * 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;
+
+/**
+ * Expected balance in the escrow account.
+ */
+static struct TALER_Amount total_escrow_balance;
+
+/**
+ * Recoups we made on denominations that were not revoked (!?).
+ */
+static struct TALER_Amount total_irregular_recoups;
+
+/**
+ * Total withdraw fees earned.
+ */
+static struct TALER_Amount total_withdraw_fee_income;
+
+/**
+ * Array of 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;
+
+
+/* ***************************** Report logic **************************** */
+
+
+/**
+ * Report a (serious) inconsistency in the exchange's database with
+ * respect to calculations involving amounts.
+ *
+ * @param operation what operation had the inconsistency
+ * @param rowid affected row, UINT64_MAX if row is missing
+ * @param exchange amount calculated by exchange
+ * @param auditor amount calculated by auditor
+ * @param profitable 1 if @a exchange being larger than @a auditor is
+ * profitable for the exchange for this operation,
+ * -1 if @a exchange being smaller than @a auditor is
+ * profitable for the exchange, and 0 if it is unclear
+ */
+static void
+report_amount_arithmetic_inconsistency (const char *operation,
+ uint64_t rowid,
+ const struct TALER_Amount *exchange,
+ const struct TALER_Amount *auditor,
+ int profitable)
+{
+ struct TALER_Amount delta;
+ struct TALER_Amount *target;
+
+ if (0 < TALER_amount_cmp (exchange,
+ auditor))
+ {
+ /* exchange > 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));
+ }
+ 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)
+{
+ report (report_row_inconsistencies,
+ json_pack ("{s:s, s:I, s:s}",
+ "table", table,
+ "row", (json_int_t) rowid,
+ "diagnostic", diagnostic));
+}
+
+
+/* ***************************** Analyze reserves ************************ */
+/* This logic checks the reserves_in, reserves_out and reserves-tables */
+
+/**
+ * Summary data we keep per reserve.
+ */
+struct ReserveSummary
+{
+ /**
+ * Public key of the reserve.
+ * Always set when the struct is first initialized.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Sum of all incoming transfers during this transaction.
+ * Updated only in #handle_reserve_in().
+ */
+ struct TALER_Amount total_in;
+
+ /**
+ * Sum of all outgoing transfers during this transaction (includes fees).
+ * Updated only in #handle_reserve_out().
+ */
+ struct TALER_Amount total_out;
+
+ /**
+ * Sum of withdraw fees encountered during this transaction.
+ */
+ struct TALER_Amount total_fee;
+
+ /**
+ * Previous balance of the reserve as remembered by the auditor.
+ * (updated based on @e total_in and @e total_out at the end).
+ */
+ struct TALER_Amount a_balance;
+
+ /**
+ * Previous withdraw fee balance of the reserve, as remembered by the auditor.
+ * (updated based on @e total_fee at the end).
+ */
+ struct TALER_Amount a_withdraw_fee_balance;
+
+ /**
+ * Previous reserve expiration data, as remembered by the auditor.
+ * (updated on-the-fly in #handle_reserve_in()).
+ */
+ struct GNUNET_TIME_Absolute a_expiration_date;
+
+ /**
+ * Which account did originally put money into the reserve?
+ */
+ char *sender_account;
+
+ /**
+ * Did we have a previous reserve info? Used to decide between
+ * UPDATE and INSERT later. Initialized in
+ * #load_auditor_reserve_summary() together with the a-* values
+ * (if available).
+ */
+ int had_ri;
+
+};
+
+
+/**
+ * Load the auditor's remembered state about the reserve into @a rs.
+ * The "total_in" and "total_out" amounts of @a rs must already be
+ * initialized (so we can determine the currency).
+ *
+ * @param[in,out] rs reserve summary to (fully) initialize
+ * @return transaction status code
+ */
+static enum GNUNET_DB_QueryStatus
+load_auditor_reserve_summary (struct ReserveSummary *rs)
+{
+ enum GNUNET_DB_QueryStatus qs;
+ uint64_t rowid;
+
+ qs = adb->get_reserve_info (adb->cls,
+ asession,
+ &rs->reserve_pub,
+ &master_pub,
+ &rowid,
+ &rs->a_balance,
+ &rs->a_withdraw_fee_balance,
+ &rs->a_expiration_date,
+ &rs->sender_account);
+ if (0 > qs)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ rs->had_ri = GNUNET_NO;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (rs->total_in.currency,
+ &rs->a_balance));
+ 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_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+ rs->had_ri = GNUNET_YES;
+ if ( (GNUNET_YES !=
+ TALER_amount_cmp_currency (&rs->a_balance,
+ &rs->a_withdraw_fee_balance)) ||
+ (GNUNET_YES !=
+ TALER_amount_cmp_currency (&rs->total_in,
+ &rs->a_balance)) )
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ 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_DB_STATUS_SUCCESS_ONE_RESULT;
+}
+
+
+/**
+ * Closure to the various callbacks we make while checking a reserve.
+ */
+struct ReserveContext
+{
+ /**
+ * Map from hash of reserve's public key to a `struct ReserveSummary`.
+ */
+ struct GNUNET_CONTAINER_MultiHashMap *reserves;
+
+ /**
+ * Map from hash of denomination's public key to a
+ * static string "revoked" for keys that have been revoked,
+ * or "master signature invalid" in case the revocation is
+ * there but bogus.
+ */
+ struct GNUNET_CONTAINER_MultiHashMap *revoked;
+
+ /**
+ * Transaction status code, set to error codes if applicable.
+ */
+ enum GNUNET_DB_QueryStatus qs;
+
+};
+
+
+/**
+ * Function called with details about incoming wire transfers.
+ *
+ * @param cls our `struct ReserveContext`
+ * @param rowid unique serial ID for the refresh session in our DB
+ * @param reserve_pub public key of the reserve (also the WTID)
+ * @param credit amount that was received
+ * @param sender_account_details information about the sender's bank account
+ * @param wire_reference unique reference identifying the wire transfer
+ * @param execution_date when did we receive the funds
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+static int
+handle_reserve_in (void *cls,
+ uint64_t rowid,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_Amount *credit,
+ const char *sender_account_details,
+ uint64_t wire_reference,
+ struct GNUNET_TIME_Absolute execution_date)
+{
+ struct ReserveContext *rc = cls;
+ struct GNUNET_HashCode key;
+ struct ReserveSummary *rs;
+ struct GNUNET_TIME_Absolute expiry;
+ enum GNUNET_DB_QueryStatus qs;
+
+ (void) wire_reference;
+ /* should be monotonically increasing */
+ GNUNET_assert (rowid >= ppr.last_reserve_in_serial_id);
+ ppr.last_reserve_in_serial_id = rowid + 1;
+
+ GNUNET_CRYPTO_hash (reserve_pub,
+ sizeof (*reserve_pub),
+ &key);
+ rs = GNUNET_CONTAINER_multihashmap_get (rc->reserves,
+ &key);
+ if (NULL == rs)
+ {
+ rs = GNUNET_new (struct ReserveSummary);
+ rs->sender_account = GNUNET_strdup (sender_account_details);
+ rs->reserve_pub = *reserve_pub;
+ rs->total_in = *credit;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (credit->currency,
+ &rs->total_out));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (credit->currency,
+ &rs->total_fee));
+ if (0 > (qs = load_auditor_reserve_summary (rs)))
+ {
+ GNUNET_break (0);
+ GNUNET_free (rs);
+ rc->qs = qs;
+ return GNUNET_SYSERR;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap_put (rc->reserves,
+ &key,
+ rs,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+ }
+ else
+ {
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_add (&rs->total_in,
+ &rs->total_in,
+ credit));
+ if (NULL == rs->sender_account)
+ rs->sender_account = GNUNET_strdup (sender_account_details);
+ }
+ 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,
+ idle_reserve_expiration_time);
+ rs->a_expiration_date = GNUNET_TIME_absolute_max (rs->a_expiration_date,
+ expiry);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called with details about withdraw operations. Verifies
+ * the signature and updates the reserve's balance.
+ *
+ * @param cls our `struct ReserveContext`
+ * @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
+ * @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
+handle_reserve_out (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 ReserveContext *rc = cls;
+ struct TALER_WithdrawRequestPS wsrd;
+ struct GNUNET_HashCode key;
+ struct ReserveSummary *rs;
+ const struct TALER_DenominationKeyValidityPS *issue;
+ struct TALER_Amount withdraw_fee;
+ struct GNUNET_TIME_Absolute valid_start;
+ struct GNUNET_TIME_Absolute expire_withdraw;
+ enum GNUNET_DB_QueryStatus qs;
+
+ /* should be monotonically increasing */
+ GNUNET_assert (rowid >= ppr.last_reserve_out_serial_id);
+ ppr.last_reserve_out_serial_id = rowid + 1;
+
+ /* lookup denomination pub data (make sure denom_pub is valid, establish fees) */
+ qs = get_denomination_info (denom_pub,
+ &issue,
+ &wsrd.h_denomination_pub);
+ if (0 > qs)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Hard database error trying to get denomination %s (%s) from database!\n",
+ TALER_B2S (denom_pub),
+ TALER_amount2s (amount_with_fee));
+ rc->qs = qs;
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ report_row_inconsistency ("withdraw",
+ rowid,
+ "denomination key not found");
+ return GNUNET_OK;
+ }
+
+ /* check that execution date is within withdraw range for denom_pub */
+ valid_start = GNUNET_TIME_absolute_ntoh (issue->start);
+ expire_withdraw = GNUNET_TIME_absolute_ntoh (issue->expire_withdraw);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Checking withdraw timing: %llu, expire: %llu, timing: %llu\n",
+ (unsigned long long) valid_start.abs_value_us,
+ (unsigned long long) expire_withdraw.abs_value_us,
+ (unsigned long long) execution_date.abs_value_us);
+ if ( (valid_start.abs_value_us > execution_date.abs_value_us) ||
+ (expire_withdraw.abs_value_us < execution_date.abs_value_us) )
+ {
+ report (denomination_key_validity_withdraw_inconsistencies,
+ json_pack ("{s:I, s:o, s:o, s:o}",
+ "row", (json_int_t) rowid,
+ "execution_date",
+ json_from_time_abs (execution_date),
+ "reserve_pub", GNUNET_JSON_from_data_auto (reserve_pub),
+ "denompub_h", GNUNET_JSON_from_data_auto (
+ &wsrd.h_denomination_pub)));
+ }
+
+ /* check reserve_sig */
+ wsrd.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW);
+ wsrd.purpose.size = htonl (sizeof (wsrd));
+ wsrd.reserve_pub = *reserve_pub;
+ TALER_amount_hton (&wsrd.amount_with_fee,
+ amount_with_fee);
+ wsrd.withdraw_fee = issue->fee_withdraw;
+ wsrd.h_coin_envelope = *h_blind_ev;
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW,
+ &wsrd.purpose,
+ &reserve_sig->eddsa_signature,
+ &reserve_pub->eddsa_pub))
+ {
+ report (report_bad_sig_losses,
+ json_pack ("{s:s, s:I, s:o, s:o}",
+ "operation", "withdraw",
+ "row", (json_int_t) rowid,
+ "loss", TALER_JSON_from_amount (amount_with_fee),
+ "key_pub", GNUNET_JSON_from_data_auto (reserve_pub)));
+ GNUNET_break (GNUNET_OK ==
+ TALER_amount_add (&total_bad_sig_loss,
+ &total_bad_sig_loss,
+ amount_with_fee));
+ return GNUNET_OK;
+ }
+
+ GNUNET_CRYPTO_hash (reserve_pub,
+ sizeof (*reserve_pub),
+ &key);
+ rs = GNUNET_CONTAINER_multihashmap_get (rc->reserves,
+ &key);
+ if (NULL == rs)
+ {
+ rs = GNUNET_new (struct ReserveSummary);
+ rs->reserve_pub = *reserve_pub;
+ rs->total_out = *amount_with_fee;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (amount_with_fee->currency,
+ &rs->total_in));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (amount_with_fee->currency,
+ &rs->total_fee));
+ qs = load_auditor_reserve_summary (rs);
+ if (0 > qs)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ GNUNET_free (rs);
+ rc->qs = qs;
+ return GNUNET_SYSERR;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap_put (rc->reserves,
+ &key,
+ rs,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+ }
+ else
+ {
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_add (&rs->total_out,
+ &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,
+ &issue->fee_withdraw);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Increasing withdraw profits by fee %s\n",
+ TALER_amount2s (&withdraw_fee));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_add (&rs->total_fee,
+ &rs->total_fee,
+ &withdraw_fee));
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called with details about withdraw operations. Verifies
+ * the signature and updates the reserve's balance.
+ *
+ * @param cls our `struct ReserveContext`
+ * @param rowid unique serial ID for the refresh session in our DB
+ * @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, denomination signature is
+ * already verified in #check_recoup()
+ * @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
+handle_recoup_by_reserve (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 ReserveContext *rc = cls;
+ struct GNUNET_HashCode key;
+ struct ReserveSummary *rs;
+ struct GNUNET_TIME_Absolute expiry;
+ struct TALER_RecoupRequestPS pr;
+ struct TALER_MasterSignatureP msig;
+ uint64_t rev_rowid;
+ enum GNUNET_DB_QueryStatus qs;
+ const char *rev;
+
+ (void) denom_pub;
+ /* should be monotonically increasing */
+ GNUNET_assert (rowid >= ppr.last_reserve_recoup_serial_id);
+ ppr.last_reserve_recoup_serial_id = rowid + 1;
+ /* We know that denom_pub matches denom_pub_hash because this
+ is how the SQL statement joined the tables. */
+ pr.h_denom_pub = coin->denom_pub_hash;
+ 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))
+ {
+ 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 (
+ &coin->coin_pub)));
+ GNUNET_break (GNUNET_OK ==
+ TALER_amount_add (&total_bad_sig_loss,
+ &total_bad_sig_loss,
+ amount));
+ }
+
+ /* check that the coin was eligible for recoup!*/
+ rev = GNUNET_CONTAINER_multihashmap_get (rc->revoked,
+ &pr.h_denom_pub);
+ if (NULL == rev)
+ {
+ qs = edb->get_denomination_revocation (edb->cls,
+ esession,
+ &pr.h_denom_pub,
+ &msig,
+ &rev_rowid);
+ if (0 > qs)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ rc->qs = qs;
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ report_row_inconsistency ("recoup",
+ rowid,
+ "denomination key not in revocation set");
+ GNUNET_break (GNUNET_OK ==
+ TALER_amount_add (&total_irregular_recoups,
+ &total_irregular_recoups,
+ amount));
+ }
+ else
+ {
+ /* verify msig */
+ struct TALER_MasterDenominationKeyRevocationPS kr;
+
+ kr.purpose.purpose = htonl (
+ TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED);
+ kr.purpose.size = htonl (sizeof (kr));
+ kr.h_denom_pub = pr.h_denom_pub;
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (
+ TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED,
+ &kr.purpose,
+ &msig.eddsa_signature,
+ &master_pub.eddsa_pub))
+ {
+ rev = "master signature invalid";
+ }
+ else
+ {
+ rev = "revoked";
+ }
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap_put (rc->revoked,
+ &pr.h_denom_pub,
+ (void *) rev,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+ }
+ }
+ else
+ {
+ rev_rowid = 0; /* reported elsewhere */
+ }
+ if ( (NULL != rev) &&
+ (0 == strcmp (rev, "master signature invalid")) )
+ {
+ report (report_bad_sig_losses,
+ json_pack ("{s:s, s:I, s:o, s:o}",
+ "operation", "recoup-master",
+ "row", (json_int_t) rev_rowid,
+ "loss", TALER_JSON_from_amount (amount),
+ "key_pub", GNUNET_JSON_from_data_auto (&master_pub)));
+ GNUNET_break (GNUNET_OK ==
+ TALER_amount_add (&total_bad_sig_loss,
+ &total_bad_sig_loss,
+ amount));
+ }
+
+ GNUNET_CRYPTO_hash (reserve_pub,
+ sizeof (*reserve_pub),
+ &key);
+ rs = GNUNET_CONTAINER_multihashmap_get (rc->reserves,
+ &key);
+ if (NULL == rs)
+ {
+ rs = GNUNET_new (struct ReserveSummary);
+ rs->reserve_pub = *reserve_pub;
+ rs->total_in = *amount;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (amount->currency,
+ &rs->total_out));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (amount->currency,
+ &rs->total_fee));
+ qs = load_auditor_reserve_summary (rs);
+ if (0 > qs)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ GNUNET_free (rs);
+ rc->qs = qs;
+ return GNUNET_SYSERR;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap_put (rc->reserves,
+ &key,
+ rs,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+ }
+ else
+ {
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_add (&rs->total_in,
+ &rs->total_in,
+ amount));
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Additional /recoup value to for reserve `%s' of %s\n",
+ TALER_B2S (reserve_pub),
+ TALER_amount2s (amount));
+ expiry = GNUNET_TIME_absolute_add (timestamp,
+ idle_reserve_expiration_time);
+ rs->a_expiration_date = GNUNET_TIME_absolute_max (rs->a_expiration_date,
+ expiry);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Obtain the closing fee for a transfer at @a time for target
+ * @a receiver_account.
+ *
+ * @param receiver_account payto:// URI of the target account
+ * @param atime when was the transfer made
+ * @param[out] fee set to the closing fee
+ * @return #GNUNET_OK on success
+ */
+static int
+get_closing_fee (const char *receiver_account,
+ struct GNUNET_TIME_Absolute atime,
+ struct TALER_Amount *fee)
+{
+ struct TALER_MasterSignatureP master_sig;
+ struct GNUNET_TIME_Absolute start_date;
+ struct GNUNET_TIME_Absolute end_date;
+ struct TALER_Amount wire_fee;
+ char *method;
+
+ method = TALER_payto_get_method (receiver_account);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Method is `%s'\n",
+ method);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ edb->get_wire_fee (edb->cls,
+ esession,
+ method,
+ atime,
+ &start_date,
+ &end_date,
+ &wire_fee,
+ fee,
+ &master_sig))
+ {
+ report_row_inconsistency ("closing-fee",
+ atime.abs_value_us,
+ "closing fee unavailable at given time");
+ GNUNET_free (method);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (method);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called about reserve closing operations
+ * the aggregator triggered.
+ *
+ * @param cls closure
+ * @param rowid row identifier used to uniquely identify the reserve closing operation
+ * @param execution_date when did we execute the close operation
+ * @param amount_with_fee how much did we debit the reserve
+ * @param closing_fee how much did we charge for closing the reserve
+ * @param reserve_pub public key of the reserve
+ * @param receiver_account where did we send the funds
+ * @param transfer_details details about the wire transfer
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+static int
+handle_reserve_closed (void *cls,
+ uint64_t rowid,
+ struct GNUNET_TIME_Absolute execution_date,
+ const struct TALER_Amount *amount_with_fee,
+ const struct TALER_Amount *closing_fee,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const char *receiver_account,
+ const struct
+ TALER_WireTransferIdentifierRawP *transfer_details)
+{
+ struct ReserveContext *rc = cls;
+ struct GNUNET_HashCode key;
+ struct ReserveSummary *rs;
+ enum GNUNET_DB_QueryStatus qs;
+
+ (void) transfer_details;
+ /* should be monotonically increasing */
+ GNUNET_assert (rowid >= ppr.last_reserve_close_serial_id);
+ ppr.last_reserve_close_serial_id = rowid + 1;
+
+ GNUNET_CRYPTO_hash (reserve_pub,
+ sizeof (*reserve_pub),
+ &key);
+ rs = GNUNET_CONTAINER_multihashmap_get (rc->reserves,
+ &key);
+ if (NULL == rs)
+ {
+ rs = GNUNET_new (struct ReserveSummary);
+ rs->reserve_pub = *reserve_pub;
+ rs->total_out = *amount_with_fee;
+ rs->total_fee = *closing_fee;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (amount_with_fee->currency,
+ &rs->total_in));
+ qs = load_auditor_reserve_summary (rs);
+ if (0 > qs)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ GNUNET_free (rs);
+ rc->qs = qs;
+ return GNUNET_SYSERR;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap_put (rc->reserves,
+ &key,
+ rs,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+ }
+ else
+ {
+ struct TALER_Amount expected_fee;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_add (&rs->total_out,
+ &rs->total_out,
+ amount_with_fee));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_add (&rs->total_fee,
+ &rs->total_fee,
+ closing_fee));
+ /* verify closing_fee is correct! */
+ if (GNUNET_OK !=
+ get_closing_fee (receiver_account,
+ execution_date,
+ &expected_fee))
+ {
+ GNUNET_break (0);
+ }
+ else if (0 != TALER_amount_cmp (&expected_fee,
+ closing_fee))
+ {
+ report_amount_arithmetic_inconsistency ("closing aggregation fee",
+ rowid,
+ closing_fee,
+ &expected_fee,
+ 1);
+ }
+ }
+ if (NULL == rs->sender_account)
+ {
+ GNUNET_break (GNUNET_NO == rs->had_ri);
+ report_row_inconsistency ("reserves_close",
+ rowid,
+ "target account not verified, auditor does not know reserve");
+ }
+ else if (0 != strcmp (rs->sender_account,
+ receiver_account))
+ {
+ report_row_inconsistency ("reserves_close",
+ rowid,
+ "target account does not match origin account");
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Additional closing operation for reserve `%s' of %s\n",
+ TALER_B2S (reserve_pub),
+ TALER_amount2s (amount_with_fee));
+ return GNUNET_OK;
+}
+
+
+/**
+ * Check that the reserve summary matches what the exchange database
+ * thinks about the reserve, and update our own state of the reserve.
+ *
+ * Remove all reserves that we are happy with from the DB.
+ *
+ * @param cls our `struct ReserveContext`
+ * @param key hash of the reserve public key
+ * @param value a `struct ReserveSummary`
+ * @return #GNUNET_OK to process more entries
+ */
+static int
+verify_reserve_balance (void *cls,
+ const struct GNUNET_HashCode *key,
+ void *value)
+{
+ struct ReserveContext *rc = cls;
+ struct ReserveSummary *rs = value;
+ struct TALER_EXCHANGEDB_Reserve reserve;
+ struct TALER_Amount balance;
+ struct TALER_Amount nbalance;
+ struct TALER_Amount cfee;
+ enum GNUNET_DB_QueryStatus qs;
+ int ret;
+
+ ret = GNUNET_OK;
+ reserve.pub = rs->reserve_pub;
+ qs = edb->reserves_get (edb->cls,
+ esession,
+ &reserve);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+ {
+ char *diag;
+
+ GNUNET_asprintf (&diag,
+ "Failed to find summary for reserve `%s'\n",
+ TALER_B2S (&rs->reserve_pub));
+ report_row_inconsistency ("reserve-summary",
+ UINT64_MAX,
+ diag);
+ GNUNET_free (diag);
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ GNUNET_break (0);
+ qs = GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ rc->qs = qs;
+ return GNUNET_OK;
+ }
+
+ if (GNUNET_OK !=
+ TALER_amount_add (&balance,
+ &rs->total_in,
+ &rs->a_balance))
+ {
+ GNUNET_break (0);
+ goto cleanup;
+ }
+
+ if (GNUNET_SYSERR ==
+ TALER_amount_subtract (&nbalance,
+ &balance,
+ &rs->total_out))
+ {
+ struct TALER_Amount loss;
+
+ GNUNET_break (GNUNET_SYSERR !=
+ TALER_amount_subtract (&loss,
+ &rs->total_out,
+ &balance));
+ GNUNET_break (GNUNET_OK ==
+ TALER_amount_add (&total_balance_insufficient_loss,
+ &total_balance_insufficient_loss,
+ &loss));
+ report (report_reserve_balance_insufficient_inconsistencies,
+ json_pack ("{s:o, s:o}",
+ "reserve_pub",
+ GNUNET_JSON_from_data_auto (&rs->reserve_pub),
+ "loss",
+ TALER_JSON_from_amount (&loss)));
+ goto cleanup;
+ }
+ if (0 != TALER_amount_cmp (&nbalance,
+ &reserve.balance))
+ {
+ struct TALER_Amount delta;
+
+ if (0 < TALER_amount_cmp (&nbalance,
+ &reserve.balance))
+ {
+ /* balance > reserve.balance */
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_subtract (&delta,
+ &nbalance,
+ &reserve.balance));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_add (&total_balance_summary_delta_plus,
+ &total_balance_summary_delta_plus,
+ &delta));
+ }
+ else
+ {
+ /* balance < reserve.balance */
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_subtract (&delta,
+ &reserve.balance,
+ &nbalance));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_add (&total_balance_summary_delta_minus,
+ &total_balance_summary_delta_minus,
+ &delta));
+ }
+ report (report_reserve_balance_summary_wrong_inconsistencies,
+ json_pack ("{s:o, s:o, s:o}",
+ "reserve_pub",
+ GNUNET_JSON_from_data_auto (&rs->reserve_pub),
+ "exchange",
+ TALER_JSON_from_amount (&reserve.balance),
+ "auditor",
+ TALER_JSON_from_amount (&nbalance)));
+ goto cleanup;
+ }
+
+ /* Check that reserve is being closed if it is past its expiration date */
+
+ if (CLOSING_GRACE_PERIOD.rel_value_us <
+ GNUNET_TIME_absolute_get_duration (rs->a_expiration_date).rel_value_us)
+ {
+ if ( (NULL != rs->sender_account) &&
+ (GNUNET_OK ==
+ get_closing_fee (rs->sender_account,
+ rs->a_expiration_date,
+ &cfee)) )
+ {
+ if (1 == TALER_amount_cmp (&nbalance,
+ &cfee))
+ {
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_add (&total_balance_reserve_not_closed,
+ &total_balance_reserve_not_closed,
+ &nbalance));
+ report (report_reserve_not_closed_inconsistencies,
+ json_pack ("{s:o, s:o, s:o}",
+ "reserve_pub",
+ GNUNET_JSON_from_data_auto (&rs->reserve_pub),
+ "balance",
+ TALER_JSON_from_amount (&nbalance),
+ "expiration_time",
+ json_from_time_abs (rs->a_expiration_date)));
+ }
+ }
+ else
+ {
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_add (&total_balance_reserve_not_closed,
+ &total_balance_reserve_not_closed,
+ &nbalance));
+ report (report_reserve_not_closed_inconsistencies,
+ json_pack ("{s:o, s:o, s:o, s:s}",
+ "reserve_pub",
+ GNUNET_JSON_from_data_auto (&rs->reserve_pub),
+ "balance",
+ TALER_JSON_from_amount (&nbalance),
+ "expiration_time",
+ json_from_time_abs (rs->a_expiration_date),
+ "diagnostic",
+ "could not determine closing fee"));
+ }
+ }
+
+ /* Add withdraw fees we encountered to totals */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Reserve reserve `%s' made %s in withdraw fees\n",
+ TALER_B2S (&rs->reserve_pub),
+ TALER_amount2s (&rs->total_fee));
+ if (GNUNET_YES !=
+ TALER_amount_add (&rs->a_withdraw_fee_balance,
+ &rs->a_withdraw_fee_balance,
+ &rs->total_fee))
+ {
+ GNUNET_break (0);
+ ret = GNUNET_SYSERR;
+ goto cleanup;
+ }
+ if ( (GNUNET_YES !=
+ TALER_amount_add (&total_escrow_balance,
+ &total_escrow_balance,
+ &rs->total_in)) ||
+ (GNUNET_SYSERR ==
+ TALER_amount_subtract (&total_escrow_balance,
+ &total_escrow_balance,
+ &rs->total_out)) ||
+ (GNUNET_YES !=
+ TALER_amount_add (&total_withdraw_fee_income,
+ &total_withdraw_fee_income,
+ &rs->total_fee)) )
+ {
+ GNUNET_break (0);
+ ret = GNUNET_SYSERR;
+ goto cleanup;
+ }
+
+ if ( (0ULL == balance.value) &&
+ (0U == balance.fraction) )
+ {
+ /* 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 (&nbalance));
+ qs = adb->del_reserve_info (adb->cls,
+ asession,
+ &rs->reserve_pub,
+ &master_pub);
+ if (0 >= qs)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ ret = GNUNET_SYSERR;
+ rc->qs = qs;
+ 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 (&nbalance));
+ }
+ ret = GNUNET_OK;
+ goto cleanup;
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Remembering final balance of reserve `%s' as %s\n",
+ TALER_B2S (&rs->reserve_pub),
+ TALER_amount2s (&nbalance));
+
+ if (rs->had_ri)
+ qs = adb->update_reserve_info (adb->cls,
+ asession,
+ &rs->reserve_pub,
+ &master_pub,
+ &nbalance,
+ &rs->a_withdraw_fee_balance,
+ rs->a_expiration_date);
+ else
+ qs = adb->insert_reserve_info (adb->cls,
+ asession,
+ &rs->reserve_pub,
+ &master_pub,
+ &nbalance,
+ &rs->a_withdraw_fee_balance,
+ rs->a_expiration_date,
+ rs->sender_account);
+ if (0 >= qs)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ ret = GNUNET_SYSERR;
+ rc->qs = qs;
+ }
+cleanup:
+ GNUNET_assert (GNUNET_YES ==
+ GNUNET_CONTAINER_multihashmap_remove (rc->reserves,
+ key,
+ rs));
+ GNUNET_free_non_null (rs->sender_account);
+ GNUNET_free (rs);
+ return ret;
+}
+
+
+/**
+ * Analyze reserves for being well-formed.
+ *
+ * @param cls NULL
+ * @return transaction status code
+ */
+static enum GNUNET_DB_QueryStatus
+analyze_reserves (void *cls)
+{
+ struct ReserveContext rc;
+ enum GNUNET_DB_QueryStatus qsx;
+ enum GNUNET_DB_QueryStatus qs;
+ enum GNUNET_DB_QueryStatus qsp;
+
+ (void) cls;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Analyzing reserves\n");
+ qsp = adb->get_auditor_progress_reserve (adb->cls,
+ asession,
+ &master_pub,
+ &ppr);
+ 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 audit from scratch\n"));
+ }
+ else
+ {
+ ppr_start = ppr;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ _ ("Resuming reserve audit at %llu/%llu/%llu/%llu\n"),
+ (unsigned long long) ppr.last_reserve_in_serial_id,
+ (unsigned long long) ppr.last_reserve_out_serial_id,
+ (unsigned long long) ppr.last_reserve_recoup_serial_id,
+ (unsigned long long) ppr.last_reserve_close_serial_id);
+ }
+ rc.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+ qsx = adb->get_reserve_summary (adb->cls,
+ asession,
+ &master_pub,
+ &total_escrow_balance,
+ &total_withdraw_fee_income);
+ if (qsx < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qsx);
+ return qsx;
+ }
+ rc.reserves = GNUNET_CONTAINER_multihashmap_create (512,
+ GNUNET_NO);
+ rc.revoked = GNUNET_CONTAINER_multihashmap_create (4,
+ GNUNET_NO);
+
+ qs = edb->select_reserves_in_above_serial_id (edb->cls,
+ esession,
+ ppr.last_reserve_in_serial_id,
+ &handle_reserve_in,
+ &rc);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+ qs = edb->select_withdrawals_above_serial_id (edb->cls,
+ esession,
+ ppr.last_reserve_out_serial_id,
+ &handle_reserve_out,
+ &rc);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+ qs = edb->select_recoup_above_serial_id (edb->cls,
+ esession,
+ ppr.last_reserve_recoup_serial_id,
+ &handle_recoup_by_reserve,
+ &rc);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+ qs = edb->select_reserve_closed_above_serial_id (edb->cls,
+ esession,
+ ppr.
+ last_reserve_close_serial_id,
+ &handle_reserve_closed,
+ &rc);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+
+ GNUNET_CONTAINER_multihashmap_iterate (rc.reserves,
+ &verify_reserve_balance,
+ &rc);
+ GNUNET_break (0 ==
+ GNUNET_CONTAINER_multihashmap_size (rc.reserves));
+ GNUNET_CONTAINER_multihashmap_destroy (rc.reserves);
+ GNUNET_CONTAINER_multihashmap_destroy (rc.revoked);
+
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != rc.qs)
+ return qs;
+
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qsx)
+ {
+ qs = adb->insert_reserve_summary (adb->cls,
+ asession,
+ &master_pub,
+ &total_escrow_balance,
+ &total_withdraw_fee_income);
+ }
+ else
+ {
+ qs = adb->update_reserve_summary (adb->cls,
+ asession,
+ &master_pub,
+ &total_escrow_balance,
+ &total_withdraw_fee_income);
+ }
+ if (0 >= qs)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qsp)
+ qs = adb->update_auditor_progress_reserve (adb->cls,
+ asession,
+ &master_pub,
+ &ppr);
+ else
+ qs = adb->insert_auditor_progress_reserve (adb->cls,
+ asession,
+ &master_pub,
+ &ppr);
+ 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 reserve audit step at %llu/%llu/%llu/%llu\n"),
+ (unsigned long long) ppr.last_reserve_in_serial_id,
+ (unsigned long long) ppr.last_reserve_out_serial_id,
+ (unsigned long long) ppr.last_reserve_recoup_serial_id,
+ (unsigned long long) ppr.last_reserve_close_serial_id);
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+}
+
+
+/**
+ * Main function that will be run.
+ *
+ * @param cls closure
+ * @param args remaining command-line arguments
+ * @param 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 *cfgfile,
+ const struct GNUNET_CONFIGURATION_Handle *c)
+{
+ json_t *report;
+
+ (void) cls;
+ (void) args;
+ (void) cfgfile;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Launching auditor\n");
+ if (GNUNET_OK !=
+ setup_globals (cfg))
+ {
+ global_ret = 1;
+ return;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (cfg,
+ "exchangedb",
+ "IDLE_RESERVE_EXPIRATION_TIME",
+ &idle_reserve_expiration_time))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchangedb",
+ "IDLE_RESERVE_EXPIRATION_TIME");
+ global_ret = 1;
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Starting audit\n");
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (currency,
+ &total_escrow_balance));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (currency,
+ &total_irregular_recoups));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (currency,
+ &total_withdraw_fee_income));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (currency,
+ &total_balance_insufficient_loss));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (currency,
+ &total_balance_summary_delta_plus));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (currency,
+ &total_balance_summary_delta_minus));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (currency,
+ &total_arithmetic_delta_plus));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (currency,
+ &total_arithmetic_delta_minus));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (currency,
+ &total_balance_reserve_not_closed));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (currency,
+ &total_bad_sig_loss));
+ GNUNET_assert (NULL !=
+ (report_row_inconsistencies = json_array ()));
+ GNUNET_assert (NULL !=
+ (denomination_key_validity_withdraw_inconsistencies =
+ json_array ()));
+ GNUNET_assert (NULL !=
+ (report_reserve_balance_summary_wrong_inconsistencies =
+ json_array ()));
+ GNUNET_assert (NULL !=
+ (report_reserve_balance_insufficient_inconsistencies =
+ json_array ()));
+ GNUNET_assert (NULL !=
+ (report_reserve_not_closed_inconsistencies = json_array ()));
+ GNUNET_assert (NULL !=
+ (report_amount_arithmetic_inconsistencies = json_array ()));
+ GNUNET_assert (NULL !=
+ (report_bad_sig_losses = json_array ()));
+ if (GNUNET_OK !=
+ setup_sessions_and_run (&analyze_reserves,
+ NULL))
+ {
+ global_ret = 1;
+ return;
+ }
+ 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:I,"
+ " s:I, s:I, s:I, s:I, s:I,"
+ " s:I, s:I }",
+ /* blocks #1 */
+ "reserve_balance_insufficient_inconsistencies",
+ report_reserve_balance_insufficient_inconsistencies,
+ /* Tested in test-auditor.sh #3 */
+ "total_loss_balance_insufficient",
+ TALER_JSON_from_amount (&total_balance_insufficient_loss),
+ /* Tested in test-auditor.sh #3 */
+ "reserve_balance_summary_wrong_inconsistencies",
+ report_reserve_balance_summary_wrong_inconsistencies,
+ "total_balance_summary_delta_plus",
+ TALER_JSON_from_amount (
+ &total_balance_summary_delta_plus),
+ "total_balance_summary_delta_minus",
+ TALER_JSON_from_amount (
+ &total_balance_summary_delta_minus),
+ /* blocks #2 */
+ "total_escrow_balance",
+ TALER_JSON_from_amount (&total_escrow_balance),
+ "total_withdraw_fee_income",
+ TALER_JSON_from_amount (&total_withdraw_fee_income),
+ /* Tested in test-auditor.sh #21 */
+ "reserve_not_closed_inconsistencies",
+ report_reserve_not_closed_inconsistencies,
+ /* Tested in test-auditor.sh #21 */
+ "total_balance_reserve_not_closed",
+ TALER_JSON_from_amount (
+ &total_balance_reserve_not_closed),
+ /* Tested in test-auditor.sh #4/#5/#6/#7/#13 */
+ "bad_sig_losses",
+ report_bad_sig_losses,
+ /* blocks #3 */
+ /* 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,
+ /* Tested in test-auditor.sh #23 */
+ "denomination_key_validity_withdraw_inconsistencies",
+ denomination_key_validity_withdraw_inconsistencies,
+ "amount_arithmetic_inconsistencies",
+ report_amount_arithmetic_inconsistencies,
+ "total_arithmetic_delta_plus",
+ TALER_JSON_from_amount (&total_arithmetic_delta_plus),
+ /* blocks #4 */
+ "total_arithmetic_delta_minus",
+ TALER_JSON_from_amount (&total_arithmetic_delta_minus),
+ "auditor_start_time",
+ json_from_time_abs (start_time),
+ "auditor_end_time",
+ json_from_time_abs (GNUNET_TIME_absolute_get ()),
+ "total_irregular_recoups",
+ TALER_JSON_from_amount (&total_irregular_recoups),
+ "start_ppr_reserve_in_serial_id",
+ (json_int_t) ppr_start.last_reserve_in_serial_id,
+ /* blocks #5 */
+ "start_ppr_reserve_out_serial_id",
+ (json_int_t) ppr_start.last_reserve_out_serial_id,
+ "start_ppr_reserve_recoup_serial_id",
+ (json_int_t) ppr_start.last_reserve_recoup_serial_id,
+ "start_ppr_reserve_close_serial_id",
+ (json_int_t) ppr_start.last_reserve_close_serial_id,
+ "end_ppr_reserve_in_serial_id",
+ (json_int_t) ppr.last_reserve_in_serial_id,
+ "end_ppr_reserve_out_serial_id",
+ (json_int_t) ppr.last_reserve_out_serial_id,
+ /* blocks #6 */
+ "end_ppr_reserve_recoup_serial_id",
+ (json_int_t) ppr.last_reserve_recoup_serial_id,
+ "end_ppr_reserve_close_serial_id",
+ (json_int_t) ppr.last_reserve_close_serial_id
+ );
+ GNUNET_break (NULL != report);
+ finish_report (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)",
+ &master_pub),
+ GNUNET_GETOPT_option_flag ('r',
+ "restart",
+ "restart audit from the beginning (required on first run)",
+ &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-reserves",
+ "MESSAGE",
+ NULL));
+ if (GNUNET_OK !=
+ GNUNET_PROGRAM_run (argc,
+ argv,
+ "taler-auditor-reserves",
+ "Audit Taler exchange reserve handling",
+ options,
+ &run,
+ NULL))
+ return 1;
+ return global_ret;
+}
+
+
+/* end of taler-auditor-reserves.c */