summaryrefslogtreecommitdiff
path: root/src/auditor/taler-helper-auditor-purses.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/auditor/taler-helper-auditor-purses.c')
-rw-r--r--src/auditor/taler-helper-auditor-purses.c1451
1 files changed, 1451 insertions, 0 deletions
diff --git a/src/auditor/taler-helper-auditor-purses.c b/src/auditor/taler-helper-auditor-purses.c
new file mode 100644
index 000000000..967ac13a7
--- /dev/null
+++ b/src/auditor/taler-helper-auditor-purses.c
@@ -0,0 +1,1451 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2016-2024 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-helper-auditor-purses.c
+ * @brief audits the purses 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 EXPIRATION_GRACE_PERIOD GNUNET_TIME_UNIT_DAYS
+
+/**
+ * Return value from main().
+ */
+static int global_ret;
+
+/**
+ * Run in test mode. Exit when idle instead of
+ * going to sleep and waiting for more work.
+ *
+ * FIXME: not yet implemented!
+ */
+static int test_mode;
+
+/**
+ * Checkpointing our progress for purses.
+ */
+static TALER_ARL_DEF_PP (purse_account_merge_serial_id);
+static TALER_ARL_DEF_PP (purse_decision_serial_id);
+static TALER_ARL_DEF_PP (purse_deposits_serial_id);
+static TALER_ARL_DEF_PP (purse_merges_serial_id);
+static TALER_ARL_DEF_PP (purse_request_serial_id);
+static TALER_ARL_DEF_PP (purse_open_counter);
+static TALER_ARL_DEF_AB (purse_global_balance);
+
+/**
+ * Array of reports about row inconsistencies.
+ */
+static json_t *report_row_inconsistencies;
+
+/**
+ * Array of reports about purse balance insufficient inconsistencies.
+ */
+static json_t *report_purse_balance_insufficient_inconsistencies;
+
+/**
+ * Total amount purses were merged with insufficient balance.
+ */
+static struct TALER_Amount total_balance_insufficient_loss;
+
+/**
+ * Total amount purse decisions are delayed past deadline.
+ */
+static struct TALER_Amount total_delayed_decisions;
+
+/**
+ * Array of reports about purses's not being closed inconsistencies.
+ */
+static json_t *report_purse_not_closed_inconsistencies;
+
+/**
+ * Total amount affected by purses not having been closed on time.
+ */
+static struct TALER_Amount total_balance_purse_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;
+
+/**
+ * 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;
+
+/**
+ * Should we run checks that only work for exchange-internal audits?
+ */
+static int internal_checks;
+
+/* ***************************** 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, 0 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 */
+ TALER_ARL_amount_subtract (&delta,
+ exchange,
+ auditor);
+ }
+ else
+ {
+ /* auditor < exchange */
+ profitable = -profitable;
+ TALER_ARL_amount_subtract (&delta,
+ auditor,
+ exchange);
+ }
+ TALER_ARL_report (report_amount_arithmetic_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("operation",
+ operation),
+ GNUNET_JSON_pack_uint64 ("rowid",
+ rowid),
+ TALER_JSON_pack_amount ("exchange",
+ exchange),
+ TALER_JSON_pack_amount ("auditor",
+ auditor),
+ GNUNET_JSON_pack_int64 ("profitable",
+ profitable)));
+ if (0 != profitable)
+ {
+ target = (1 == profitable)
+ ? &total_arithmetic_delta_plus
+ : &total_arithmetic_delta_minus;
+ TALER_ARL_amount_add (target,
+ target,
+ &delta);
+ }
+}
+
+
+/**
+ * Report a (serious) inconsistency in the exchange's database.
+ *
+ * @param table affected table
+ * @param rowid affected row, 0 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,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("table",
+ table),
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ GNUNET_JSON_pack_string ("diagnostic",
+ diagnostic)));
+}
+
+
+/**
+ * Obtain the purse fee for a purse created at @a time.
+ *
+ * @param atime when was the purse created
+ * @param[out] fee set to the purse fee
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+get_purse_fee (struct GNUNET_TIME_Timestamp atime,
+ struct TALER_Amount *fee)
+{
+ struct TALER_MasterSignatureP master_sig;
+ struct GNUNET_TIME_Timestamp start_date;
+ struct GNUNET_TIME_Timestamp end_date;
+ struct TALER_GlobalFeeSet fees;
+ struct GNUNET_TIME_Relative ptimeout;
+ struct GNUNET_TIME_Relative hexp;
+ uint32_t pacl;
+
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ TALER_ARL_edb->get_global_fee (TALER_ARL_edb->cls,
+ atime,
+ &start_date,
+ &end_date,
+ &fees,
+ &ptimeout,
+ &hexp,
+ &pacl,
+ &master_sig))
+ {
+ char *diag;
+
+ GNUNET_asprintf (&diag,
+ "purse fee unavailable at %s\n",
+ GNUNET_TIME_timestamp2s (atime));
+ report_row_inconsistency ("purse-fee",
+ atime.abs_time.abs_value_us,
+ diag);
+ GNUNET_free (diag);
+ return GNUNET_SYSERR;
+ }
+ *fee = fees.purse;
+ return GNUNET_OK;
+}
+
+
+/* ***************************** Analyze purses ************************ */
+/* This logic checks the purses_requests, purse_deposits,
+ purse_refunds, purse_merges and account_merges */
+
+/**
+ * Summary data we keep per purse.
+ */
+struct PurseSummary
+{
+ /**
+ * Public key of the purse.
+ * Always set when the struct is first initialized.
+ */
+ struct TALER_PurseContractPublicKeyP purse_pub;
+
+ /**
+ * Balance of the purse from deposits (includes purse fee, excludes deposit
+ * fees), as calculated by auditor.
+ */
+ struct TALER_Amount balance;
+
+ /**
+ * Expected value of the purse, excludes purse fee.
+ */
+ struct TALER_Amount total_value;
+
+ /**
+ * Purse balance according to exchange DB.
+ */
+ struct TALER_Amount exchange_balance;
+
+ /**
+ * Contract terms of the purse.
+ */
+ struct TALER_PrivateContractHashP h_contract_terms;
+
+ /**
+ * Merge timestamp (as per exchange DB).
+ */
+ struct GNUNET_TIME_Timestamp merge_timestamp;
+
+ /**
+ * Purse creation date. This is when the merge
+ * fee is applied.
+ */
+ struct GNUNET_TIME_Timestamp creation_date;
+
+ /**
+ * Purse expiration date.
+ */
+ struct GNUNET_TIME_Timestamp expiration_date;
+
+ /**
+ * Did we have a previous purse info? Used to decide between UPDATE and
+ * INSERT later. Initialized in #load_auditor_purse_summary().
+ */
+ bool had_pi;
+
+ /**
+ * Was the purse deleted? FIXME: Not yet handled (do we need to? purse
+ * might just appear as expired eventually; but in the meantime, exchange
+ * may seem to have refunded the coins for no good reason...), also we do
+ * not yet check the deletion signature.
+ */
+ bool purse_deleted;
+
+ /**
+ * Was the purse refunded? FIXME: Not yet handled (do we need to?)
+ */
+ bool purse_refunded;
+
+};
+
+
+/**
+ * Load the auditor's remembered state about the purse into @a ps.
+ *
+ * @param[in,out] ps purse summary to (fully) initialize
+ * @return transaction status code
+ */
+static enum GNUNET_DB_QueryStatus
+load_auditor_purse_summary (struct PurseSummary *ps)
+{
+ enum GNUNET_DB_QueryStatus qs;
+ uint64_t rowid;
+
+ qs = TALER_ARL_adb->get_purse_info (TALER_ARL_adb->cls,
+ &ps->purse_pub,
+ &rowid,
+ &ps->balance,
+ &ps->expiration_date);
+ if (0 > qs)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ ps->had_pi = false;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &ps->balance));
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Creating fresh purse `%s'\n",
+ TALER_B2S (&ps->purse_pub));
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+ ps->had_pi = true;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Auditor remembers purse `%s' has balance %s\n",
+ TALER_B2S (&ps->purse_pub),
+ TALER_amount2s (&ps->balance));
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+}
+
+
+/**
+ * Closure to the various callbacks we make while checking a purse.
+ */
+struct PurseContext
+{
+ /**
+ * Map from hash of purse's public key to a `struct PurseSummary`.
+ */
+ struct GNUNET_CONTAINER_MultiHashMap *purses;
+
+ /**
+ * Transaction status code, set to error codes if applicable.
+ */
+ enum GNUNET_DB_QueryStatus qs;
+
+};
+
+
+/**
+ * Create a new reserve for @a reserve_pub in @a rc.
+ *
+ * @param[in,out] pc context to update
+ * @param purse_pub key for which to create a purse
+ * @return NULL on error
+ */
+static struct PurseSummary *
+setup_purse (struct PurseContext *pc,
+ const struct TALER_PurseContractPublicKeyP *purse_pub)
+{
+ struct PurseSummary *ps;
+ struct GNUNET_HashCode key;
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_CRYPTO_hash (purse_pub,
+ sizeof (*purse_pub),
+ &key);
+ ps = GNUNET_CONTAINER_multihashmap_get (pc->purses,
+ &key);
+ if (NULL != ps)
+ return ps;
+ ps = GNUNET_new (struct PurseSummary);
+ ps->purse_pub = *purse_pub;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &ps->balance));
+ /* get purse meta-data from exchange DB */
+ qs = TALER_ARL_edb->select_purse (TALER_ARL_edb->cls,
+ purse_pub,
+ &ps->creation_date,
+ &ps->expiration_date,
+ &ps->total_value,
+ &ps->exchange_balance,
+ &ps->h_contract_terms,
+ &ps->merge_timestamp,
+ &ps->purse_deleted,
+ &ps->purse_refunded);
+ if (0 >= qs)
+ {
+ GNUNET_free (ps);
+ pc->qs = qs;
+ return NULL;
+ }
+ if (0 > (qs = load_auditor_purse_summary (ps)))
+ {
+ GNUNET_free (ps);
+ pc->qs = qs;
+ return NULL;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap_put (pc->purses,
+ &key,
+ ps,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+ return ps;
+}
+
+
+/**
+ * Function called on purse requests.
+ *
+ * @param cls closure
+ * @param rowid which row in the database was the request stored in
+ * @param purse_pub public key of the purse
+ * @param merge_pub public key representing the merge capability
+ * @param purse_creation when was the purse created
+ * @param purse_expiration when would an unmerged purse expire
+ * @param h_contract_terms contract associated with the purse
+ * @param age_limit the age limit for deposits into the purse
+ * @param target_amount amount to be put into the purse
+ * @param purse_sig signature of the purse over the initialization data
+ * @return #GNUNET_OK to continue to iterate
+ */
+static enum GNUNET_GenericReturnValue
+handle_purse_requested (
+ void *cls,
+ uint64_t rowid,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseMergePublicKeyP *merge_pub,
+ struct GNUNET_TIME_Timestamp purse_creation,
+ struct GNUNET_TIME_Timestamp purse_expiration,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ uint32_t age_limit,
+ const struct TALER_Amount *target_amount,
+ const struct TALER_PurseContractSignatureP *purse_sig)
+{
+ struct PurseContext *pc = cls;
+ struct PurseSummary *ps;
+ struct GNUNET_HashCode key;
+
+ TALER_ARL_USE_PP (purse_request_serial_id) = rowid;
+ if (GNUNET_OK !=
+ TALER_wallet_purse_create_verify (purse_expiration,
+ h_contract_terms,
+ merge_pub,
+ age_limit,
+ target_amount,
+ purse_pub,
+ purse_sig))
+ {
+ TALER_ARL_report (report_bad_sig_losses,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("operation",
+ "purse-reqeust"),
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ TALER_JSON_pack_amount ("loss",
+ target_amount),
+ GNUNET_JSON_pack_data_auto ("key_pub",
+ purse_pub)));
+ TALER_ARL_amount_add (&total_bad_sig_loss,
+ &total_bad_sig_loss,
+ target_amount);
+ }
+ GNUNET_CRYPTO_hash (purse_pub,
+ sizeof (*purse_pub),
+ &key);
+ ps = GNUNET_new (struct PurseSummary);
+ ps->purse_pub = *purse_pub;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &ps->balance));
+ ps->creation_date = purse_creation;
+ ps->expiration_date = purse_expiration;
+ ps->total_value = *target_amount;
+ ps->h_contract_terms = *h_contract_terms;
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap_put (pc->purses,
+ &key,
+ ps,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called with details about purse 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 deposit deposit details
+ * @param reserve_pub which reserve is the purse merged into, NULL if unknown
+ * @param flags purse flags
+ * @param auditor_balance purse balance (according to the
+ * auditor during auditing)
+ * @param purse_total target amount the purse should reach
+ * @param denom_pub denomination public key of @a coin_pub
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+static enum GNUNET_GenericReturnValue
+handle_purse_deposits (
+ void *cls,
+ uint64_t rowid,
+ const struct TALER_EXCHANGEDB_PurseDeposit *deposit,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ enum TALER_WalletAccountMergeFlags flags,
+ const struct TALER_Amount *auditor_balance,
+ const struct TALER_Amount *purse_total,
+ const struct TALER_DenominationPublicKey *denom_pub)
+{
+ struct PurseContext *pc = cls;
+ struct TALER_Amount amount_minus_fee;
+ struct PurseSummary *ps;
+ const char *base_url
+ = (NULL == deposit->exchange_base_url)
+ ? TALER_ARL_exchange_url
+ : deposit->exchange_base_url;
+ struct TALER_DenominationHashP h_denom_pub;
+
+ /* should be monotonically increasing */
+ GNUNET_assert (rowid >= TALER_ARL_USE_PP (purse_deposits_serial_id));
+ TALER_ARL_USE_PP (purse_deposits_serial_id) = rowid + 1;
+
+ {
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue;
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TALER_ARL_get_denomination_info (denom_pub,
+ &issue,
+ &h_denom_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 from database!\n",
+ TALER_B2S (denom_pub));
+ pc->qs = qs;
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ report_row_inconsistency ("purse-deposit",
+ rowid,
+ "denomination key not found");
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
+ return GNUNET_OK;
+ }
+ TALER_ARL_amount_subtract (&amount_minus_fee,
+ &deposit->amount,
+ &issue->fees.deposit);
+ }
+
+ if (GNUNET_OK !=
+ TALER_wallet_purse_deposit_verify (base_url,
+ &deposit->purse_pub,
+ &deposit->amount,
+ &h_denom_pub,
+ &deposit->h_age_commitment,
+ &deposit->coin_pub,
+ &deposit->coin_sig))
+ {
+ TALER_ARL_report (report_bad_sig_losses,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("operation",
+ "purse-deposit"),
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ TALER_JSON_pack_amount ("loss",
+ &deposit->amount),
+ GNUNET_JSON_pack_data_auto ("key_pub",
+ &deposit->coin_pub)));
+ TALER_ARL_amount_add (&total_bad_sig_loss,
+ &total_bad_sig_loss,
+ &deposit->amount);
+ return GNUNET_OK;
+ }
+
+ ps = setup_purse (pc,
+ &deposit->purse_pub);
+ if (NULL == ps)
+ {
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == pc->qs)
+ {
+ report_row_inconsistency ("purse-deposit",
+ rowid,
+ "purse not found");
+ }
+ else
+ {
+ /* Database trouble!? */
+ GNUNET_break (0);
+ }
+ return GNUNET_SYSERR;
+ }
+ TALER_ARL_amount_add (&ps->balance,
+ &ps->balance,
+ &amount_minus_fee);
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_global_balance),
+ &TALER_ARL_USE_AB (purse_global_balance),
+ &amount_minus_fee);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called with details about purse merges that have been made, with
+ * the goal of auditing the purse merge execution.
+ *
+ * @param cls closure
+ * @param rowid unique serial ID for the deposit in our DB
+ * @param partner_base_url where is the reserve, NULL for this exchange
+ * @param amount total amount expected in the purse
+ * @param balance current balance in the purse
+ * @param flags purse flags
+ * @param merge_pub merge capability key
+ * @param reserve_pub reserve the merge affects
+ * @param merge_sig signature affirming the merge
+ * @param purse_pub purse key
+ * @param merge_timestamp when did the merge happen
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+static enum GNUNET_GenericReturnValue
+handle_purse_merged (
+ void *cls,
+ uint64_t rowid,
+ const char *partner_base_url,
+ const struct TALER_Amount *amount,
+ const struct TALER_Amount *balance,
+ enum TALER_WalletAccountMergeFlags flags,
+ const struct TALER_PurseMergePublicKeyP *merge_pub,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_PurseMergeSignatureP *merge_sig,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct GNUNET_TIME_Timestamp merge_timestamp)
+{
+ struct PurseContext *pc = cls;
+ struct PurseSummary *ps;
+
+ /* should be monotonically increasing */
+ GNUNET_assert (rowid >= TALER_ARL_USE_PP (purse_merges_serial_id));
+ TALER_ARL_USE_PP (purse_merges_serial_id) = rowid + 1;
+
+ {
+ char *reserve_url;
+
+ reserve_url
+ = TALER_reserve_make_payto (NULL == partner_base_url
+ ? TALER_ARL_exchange_url
+ : partner_base_url,
+ reserve_pub);
+ if (GNUNET_OK !=
+ TALER_wallet_purse_merge_verify (reserve_url,
+ merge_timestamp,
+ purse_pub,
+ merge_pub,
+ merge_sig))
+ {
+ GNUNET_free (reserve_url);
+ TALER_ARL_report (report_bad_sig_losses,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("operation",
+ "merge-purse"),
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ TALER_JSON_pack_amount ("loss",
+ amount),
+ GNUNET_JSON_pack_data_auto ("key_pub",
+ merge_pub)));
+ TALER_ARL_amount_add (&total_bad_sig_loss,
+ &total_bad_sig_loss,
+ amount);
+ return GNUNET_OK;
+ }
+ GNUNET_free (reserve_url);
+ }
+
+ ps = setup_purse (pc,
+ purse_pub);
+ if (NULL == ps)
+ {
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == pc->qs)
+ {
+ report_row_inconsistency ("purse-merge",
+ rowid,
+ "purse not found");
+ }
+ else
+ {
+ /* Database trouble!? */
+ GNUNET_break (0);
+ }
+ return GNUNET_SYSERR;
+ }
+ GNUNET_break (0 ==
+ GNUNET_TIME_timestamp_cmp (merge_timestamp,
+ ==,
+ ps->merge_timestamp));
+ TALER_ARL_amount_add (&ps->balance,
+ &ps->balance,
+ amount);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called with details about account merge requests that have been
+ * made, with the goal of auditing the account merge execution.
+ *
+ * @param cls closure
+ * @param rowid unique serial ID for the deposit in our DB
+ * @param reserve_pub reserve affected by the merge
+ * @param purse_pub purse being merged
+ * @param h_contract_terms hash over contract of the purse
+ * @param purse_expiration when would the purse expire
+ * @param amount total amount in the purse
+ * @param min_age minimum age of all coins deposited into the purse
+ * @param flags how was the purse created
+ * @param purse_fee if a purse fee was paid, how high is it
+ * @param merge_timestamp when was the merge approved
+ * @param reserve_sig signature by reserve approving the merge
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+static enum GNUNET_GenericReturnValue
+handle_account_merged (
+ void *cls,
+ uint64_t rowid,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ struct GNUNET_TIME_Timestamp purse_expiration,
+ const struct TALER_Amount *amount,
+ uint32_t min_age,
+ enum TALER_WalletAccountMergeFlags flags,
+ const struct TALER_Amount *purse_fee,
+ struct GNUNET_TIME_Timestamp merge_timestamp,
+ const struct TALER_ReserveSignatureP *reserve_sig)
+{
+ struct PurseContext *pc = cls;
+ struct PurseSummary *ps;
+
+ /* should be monotonically increasing */
+ GNUNET_assert (rowid >= TALER_ARL_USE_PP (purse_account_merge_serial_id));
+ TALER_ARL_USE_PP (purse_account_merge_serial_id) = rowid + 1;
+ if (GNUNET_OK !=
+ TALER_wallet_account_merge_verify (merge_timestamp,
+ purse_pub,
+ purse_expiration,
+ h_contract_terms,
+ amount,
+ purse_fee,
+ min_age,
+ flags,
+ reserve_pub,
+ reserve_sig))
+ {
+ TALER_ARL_report (report_bad_sig_losses,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("operation",
+ "account-merge"),
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ TALER_JSON_pack_amount ("loss",
+ purse_fee),
+ GNUNET_JSON_pack_data_auto ("key_pub",
+ reserve_pub)));
+ TALER_ARL_amount_add (&total_bad_sig_loss,
+ &total_bad_sig_loss,
+ purse_fee);
+ return GNUNET_OK;
+ }
+ ps = setup_purse (pc,
+ purse_pub);
+ if (NULL == ps)
+ {
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == pc->qs)
+ {
+ report_row_inconsistency ("account-merge",
+ rowid,
+ "purse not found");
+ }
+ else
+ {
+ /* Database trouble!? */
+ GNUNET_break (0);
+ }
+ return GNUNET_SYSERR;
+ }
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_global_balance),
+ &TALER_ARL_USE_AB (purse_global_balance),
+ purse_fee);
+ TALER_ARL_amount_add (&ps->balance,
+ &ps->balance,
+ purse_fee);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called with details about purse decisions that have been made.
+ *
+ * @param cls closure
+ * @param rowid unique serial ID for the deposit in our DB
+ * @param purse_pub which purse was the decision made on
+ * @param refunded true if decision was to refund
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+static enum GNUNET_GenericReturnValue
+handle_purse_decision (
+ void *cls,
+ uint64_t rowid,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ bool refunded)
+{
+ struct PurseContext *pc = cls;
+ struct PurseSummary *ps;
+ struct GNUNET_HashCode key;
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_Amount purse_fee;
+ struct TALER_Amount balance_without_purse_fee;
+
+ TALER_ARL_USE_PP (purse_decision_serial_id) = rowid;
+ ps = setup_purse (pc,
+ purse_pub);
+ if (NULL == ps)
+ {
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == pc->qs)
+ {
+ report_row_inconsistency ("purse-decision",
+ rowid,
+ "purse not found");
+ }
+ else
+ {
+ /* Database trouble!? */
+ GNUNET_break (0);
+ }
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ get_purse_fee (ps->creation_date,
+ &purse_fee))
+ {
+ report_row_inconsistency ("purse-request",
+ rowid,
+ "purse fee unavailable");
+ }
+ if (0 >
+ TALER_amount_subtract (&balance_without_purse_fee,
+ &ps->balance,
+ &purse_fee))
+ {
+ report_row_inconsistency ("purse-request",
+ rowid,
+ "purse fee higher than balance");
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &balance_without_purse_fee));
+ }
+
+ if (refunded)
+ {
+ if (-1 != TALER_amount_cmp (&balance_without_purse_fee,
+ &ps->total_value))
+ {
+ report_amount_arithmetic_inconsistency ("purse-decision: refund",
+ rowid,
+ &balance_without_purse_fee,
+ &ps->total_value,
+ 0);
+ }
+ }
+ else
+ {
+ if (-1 == TALER_amount_cmp (&balance_without_purse_fee,
+ &ps->total_value))
+ {
+ report_amount_arithmetic_inconsistency ("purse-decision: merge",
+ rowid,
+ &ps->total_value,
+ &balance_without_purse_fee,
+ 0);
+ TALER_ARL_amount_add (&total_balance_insufficient_loss,
+ &total_balance_insufficient_loss,
+ &ps->total_value);
+ }
+ }
+
+ qs = TALER_ARL_adb->delete_purse_info (TALER_ARL_adb->cls,
+ purse_pub);
+ GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+ pc->qs = qs;
+ return GNUNET_SYSERR;
+ }
+ GNUNET_CRYPTO_hash (purse_pub,
+ sizeof (*purse_pub),
+ &key);
+ GNUNET_assert (GNUNET_YES ==
+ GNUNET_CONTAINER_multihashmap_remove (pc->purses,
+ &key,
+ ps));
+ GNUNET_free (ps);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called on expired purses.
+ *
+ * @param cls closure
+ * @param purse_pub public key of the purse
+ * @param balance amount of money in the purse
+ * @param expiration_date when did the purse expire?
+ * @return #GNUNET_OK to continue to iterate
+ */
+static enum GNUNET_GenericReturnValue
+handle_purse_expired (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_Amount *balance,
+ struct GNUNET_TIME_Timestamp expiration_date)
+{
+ struct PurseContext *pc = cls;
+
+ (void) pc;
+ TALER_ARL_report (report_purse_not_closed_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("purse_pub",
+ purse_pub),
+ TALER_JSON_pack_amount ("balance",
+ balance),
+ TALER_JSON_pack_time_abs_human ("expired",
+ expiration_date.abs_time)));
+ TALER_ARL_amount_add (&total_delayed_decisions,
+ &total_delayed_decisions,
+ balance);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Check that the purse summary matches what the exchange database
+ * thinks about the purse, and update our own state of the purse.
+ *
+ * Remove all purses that we are happy with from the DB.
+ *
+ * @param cls our `struct PurseContext`
+ * @param key hash of the purse public key
+ * @param value a `struct PurseSummary`
+ * @return #GNUNET_OK to process more entries
+ */
+static enum GNUNET_GenericReturnValue
+verify_purse_balance (void *cls,
+ const struct GNUNET_HashCode *key,
+ void *value)
+{
+ struct PurseContext *pc = cls;
+ struct PurseSummary *ps = value;
+ enum GNUNET_GenericReturnValue ret;
+ enum GNUNET_DB_QueryStatus qs;
+
+ ret = GNUNET_OK;
+ if (internal_checks)
+ {
+ struct TALER_Amount pf;
+ struct TALER_Amount balance_without_purse_fee;
+
+ /* subtract purse fee from ps->balance to get actual balance we expect, as
+ we track the balance including purse fee, while the exchange subtracts
+ the purse fee early on. */
+ if (GNUNET_OK !=
+ get_purse_fee (ps->creation_date,
+ &pf))
+ {
+ GNUNET_break (0);
+ report_row_inconsistency ("purse",
+ 0,
+ "purse fee unavailable");
+ }
+ if (0 >
+ TALER_amount_subtract (&balance_without_purse_fee,
+ &ps->balance,
+ &pf))
+ {
+ report_row_inconsistency ("purse",
+ 0,
+ "purse fee higher than balance");
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &balance_without_purse_fee));
+ }
+
+ if (0 != TALER_amount_cmp (&ps->exchange_balance,
+ &balance_without_purse_fee))
+ {
+ report_amount_arithmetic_inconsistency ("purse-balance",
+ 0,
+ &ps->exchange_balance,
+ &balance_without_purse_fee,
+ 0);
+ }
+ }
+
+ if (ps->had_pi)
+ qs = TALER_ARL_adb->update_purse_info (TALER_ARL_adb->cls,
+ &ps->purse_pub,
+ &ps->balance);
+ else
+ qs = TALER_ARL_adb->insert_purse_info (TALER_ARL_adb->cls,
+ &ps->purse_pub,
+ &ps->balance,
+ ps->expiration_date);
+ GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+ pc->qs = qs;
+ return GNUNET_SYSERR;
+ }
+
+ GNUNET_assert (GNUNET_YES ==
+ GNUNET_CONTAINER_multihashmap_remove (pc->purses,
+ key,
+ ps));
+ GNUNET_free (ps);
+ return ret;
+}
+
+
+/**
+ * Analyze purses for being well-formed.
+ *
+ * @param cls NULL
+ * @return transaction status code
+ */
+static enum GNUNET_DB_QueryStatus
+analyze_purses (void *cls)
+{
+ struct PurseContext pc;
+ enum GNUNET_DB_QueryStatus qsx;
+ enum GNUNET_DB_QueryStatus qs;
+ enum GNUNET_DB_QueryStatus qsp;
+
+ (void) cls;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Analyzing purses\n");
+ qsp = TALER_ARL_adb->get_auditor_progress (
+ TALER_ARL_adb->cls,
+ TALER_ARL_GET_PP (purse_account_merge_serial_id),
+ TALER_ARL_GET_PP (purse_decision_serial_id),
+ TALER_ARL_GET_PP (purse_deposits_serial_id),
+ TALER_ARL_GET_PP (purse_merges_serial_id),
+ TALER_ARL_GET_PP (purse_request_serial_id),
+ TALER_ARL_GET_PP (purse_open_counter),
+ NULL);
+ 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
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Resuming purse audit at %llu/%llu/%llu/%llu/%llu\n",
+ (unsigned long long) TALER_ARL_USE_PP (
+ purse_request_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ purse_decision_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ purse_merges_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ purse_deposits_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ purse_account_merge_serial_id));
+ }
+ pc.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+ qsx = TALER_ARL_adb->get_balance (
+ TALER_ARL_adb->cls,
+ TALER_ARL_GET_AB (purse_global_balance),
+ NULL);
+ if (qsx < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qsx);
+ return qsx;
+ }
+ pc.purses = GNUNET_CONTAINER_multihashmap_create (512,
+ GNUNET_NO);
+
+ qs = TALER_ARL_edb->select_purse_requests_above_serial_id (
+ TALER_ARL_edb->cls,
+ TALER_ARL_USE_PP (purse_request_serial_id),
+ &handle_purse_requested,
+ &pc);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+
+ qs = TALER_ARL_edb->select_purse_merges_above_serial_id (
+ TALER_ARL_edb->cls,
+ TALER_ARL_USE_PP (purse_merges_serial_id),
+ &handle_purse_merged,
+ &pc);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+ qs = TALER_ARL_edb->select_purse_deposits_above_serial_id (
+ TALER_ARL_edb->cls,
+ TALER_ARL_USE_PP (purse_deposits_serial_id),
+ &handle_purse_deposits,
+ &pc);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+ /* Charge purse fee! */
+ qs = TALER_ARL_edb->select_account_merges_above_serial_id (
+ TALER_ARL_edb->cls,
+ TALER_ARL_USE_PP (purse_account_merge_serial_id),
+ &handle_account_merged,
+ &pc);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+
+ qs = TALER_ARL_edb->select_all_purse_decisions_above_serial_id (
+ TALER_ARL_edb->cls,
+ TALER_ARL_USE_PP (purse_decision_serial_id),
+ &handle_purse_decision,
+ &pc);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+
+ qs = TALER_ARL_adb->select_purse_expired (
+ TALER_ARL_adb->cls,
+ &handle_purse_expired,
+ &pc);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+
+ GNUNET_CONTAINER_multihashmap_iterate (pc.purses,
+ &verify_purse_balance,
+ &pc);
+ GNUNET_break (0 ==
+ GNUNET_CONTAINER_multihashmap_size (pc.purses));
+ GNUNET_CONTAINER_multihashmap_destroy (pc.purses);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != pc.qs)
+ return qs;
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qsx)
+ {
+ qs = TALER_ARL_adb->insert_balance (
+ TALER_ARL_adb->cls,
+ TALER_ARL_SET_AB (purse_global_balance),
+ NULL);
+ }
+ else
+ {
+ qs = TALER_ARL_adb->update_balance (
+ TALER_ARL_adb->cls,
+ TALER_ARL_SET_AB (purse_global_balance),
+ NULL);
+ }
+ 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 (
+ TALER_ARL_adb->cls,
+ TALER_ARL_SET_PP (purse_account_merge_serial_id),
+ TALER_ARL_SET_PP (purse_decision_serial_id),
+ TALER_ARL_SET_PP (purse_deposits_serial_id),
+ TALER_ARL_SET_PP (purse_merges_serial_id),
+ TALER_ARL_SET_PP (purse_request_serial_id),
+ TALER_ARL_SET_PP (purse_open_counter),
+ NULL);
+ else
+ qs = TALER_ARL_adb->insert_auditor_progress (
+ TALER_ARL_adb->cls,
+ TALER_ARL_SET_PP (purse_account_merge_serial_id),
+ TALER_ARL_SET_PP (purse_decision_serial_id),
+ TALER_ARL_SET_PP (purse_deposits_serial_id),
+ TALER_ARL_SET_PP (purse_merges_serial_id),
+ TALER_ARL_SET_PP (purse_request_serial_id),
+ TALER_ARL_SET_PP (purse_open_counter),
+ NULL);
+ 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 purse audit step at %llu/%llu/%llu/%llu/%llu\n",
+ (unsigned long long) TALER_ARL_USE_PP (
+ purse_request_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ purse_decision_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ purse_merges_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ purse_deposits_serial_id),
+ (unsigned long long) TALER_ARL_USE_PP (
+ purse_account_merge_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)
+{
+ (void) cls;
+ (void) args;
+ (void) cfgfile;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Launching auditor\n");
+ if (GNUNET_OK !=
+ TALER_ARL_init (c))
+ {
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &TALER_ARL_USE_AB (
+ purse_global_balance)));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &total_balance_insufficient_loss));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &total_delayed_decisions));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &total_arithmetic_delta_plus));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &total_arithmetic_delta_minus));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &total_balance_purse_not_closed));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &total_bad_sig_loss));
+
+ GNUNET_assert (NULL !=
+ (report_row_inconsistencies = json_array ()));
+ GNUNET_assert (NULL !=
+ (report_purse_balance_insufficient_inconsistencies
+ = json_array ()));
+ GNUNET_assert (NULL !=
+ (report_purse_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 !=
+ TALER_ARL_setup_sessions_and_run (&analyze_purses,
+ NULL))
+ {
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+ TALER_ARL_done (
+ GNUNET_JSON_PACK (
+ /* Globals (REVIEW!) */
+ TALER_JSON_pack_amount ("total_balance_insufficient",
+ &total_balance_insufficient_loss),
+ TALER_JSON_pack_amount ("total_delayed_purse_decisions",
+ &total_delayed_decisions),
+ GNUNET_JSON_pack_array_steal (
+ "purse_balance_insufficient_inconsistencies",
+ report_purse_balance_insufficient_inconsistencies),
+ TALER_JSON_pack_amount ("total_balance_purse_not_closed",
+ &total_balance_purse_not_closed),
+ TALER_JSON_pack_amount ("total_bad_sig_loss",
+ &total_bad_sig_loss),
+ TALER_JSON_pack_amount ("total_arithmetic_delta_plus",
+ &total_arithmetic_delta_plus),
+ TALER_JSON_pack_amount ("total_arithmetic_delta_minus",
+ &total_arithmetic_delta_minus),
+
+ /* Global 'balances' */
+ TALER_JSON_pack_amount ("total_purse_balance",
+ &TALER_ARL_USE_AB (purse_global_balance)),
+ GNUNET_JSON_pack_uint64 ("total_purse_count",
+ TALER_ARL_USE_PP (purse_open_counter)),
+
+ GNUNET_JSON_pack_array_steal ("purse_not_closed_inconsistencies",
+ report_purse_not_closed_inconsistencies),
+ GNUNET_JSON_pack_array_steal ("bad_sig_losses",
+ report_bad_sig_losses),
+ GNUNET_JSON_pack_array_steal ("row_inconsistencies",
+ report_row_inconsistencies),
+ GNUNET_JSON_pack_array_steal ("amount_arithmetic_inconsistencies",
+ report_amount_arithmetic_inconsistencies),
+ /* Information about audited range ... */
+ TALER_JSON_pack_time_abs_human ("auditor_start_time",
+ start_time),
+ TALER_JSON_pack_time_abs_human ("auditor_end_time",
+ GNUNET_TIME_absolute_get ()),
+ GNUNET_JSON_pack_uint64 ("start_ppp_purse_merges_serial_id",
+ 0 /* not supported anymore */),
+ GNUNET_JSON_pack_uint64 ("start_ppp_purse_deposits_serial_id",
+ 0 /* not supported anymore */),
+ GNUNET_JSON_pack_uint64 ("start_ppp_account_merge_serial_id",
+ 0 /* not supported anymore */),
+ GNUNET_JSON_pack_uint64 ("end_ppp_purse_merges_serial_id",
+ TALER_ARL_USE_PP (
+ purse_merges_serial_id)),
+ GNUNET_JSON_pack_uint64 ("end_ppp_purse_deposits_serial_id",
+ TALER_ARL_USE_PP (
+ purse_deposits_serial_id)),
+ GNUNET_JSON_pack_uint64 ("end_ppp_account_merge_serial_id",
+ TALER_ARL_USE_PP (
+ purse_account_merge_serial_id))));
+}
+
+
+/**
+ * The main function to check the database's handling of purses.
+ *
+ * @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_flag ('i',
+ "internal",
+ "perform checks only applicable for exchange-internal audits",
+ &internal_checks),
+ GNUNET_GETOPT_option_flag ('t',
+ "test",
+ "run in test mode and exit when idle",
+ &test_mode),
+ GNUNET_GETOPT_option_timetravel ('T',
+ "timetravel"),
+ GNUNET_GETOPT_OPTION_END
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ /* 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 ();
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_get_utf8_args (argc, argv,
+ &argc, &argv))
+ return EXIT_INVALIDARGUMENT;
+ ret = GNUNET_PROGRAM_run (
+ argc,
+ argv,
+ "taler-helper-auditor-purses",
+ gettext_noop ("Audit Taler exchange purse handling"),
+ options,
+ &run,
+ NULL);
+ GNUNET_free_nz ((void *) argv);
+ if (GNUNET_SYSERR == ret)
+ return EXIT_INVALIDARGUMENT;
+ if (GNUNET_NO == ret)
+ return EXIT_SUCCESS;
+ return global_ret;
+}
+
+
+/* end of taler-helper-auditor-purses.c */