aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorChristian Grothoff <grothoff@gnunet.org>2024-08-14 12:33:00 +0200
committerChristian Grothoff <grothoff@gnunet.org>2024-08-14 12:33:00 +0200
commitbeac439e78b0abc18b20ff6fee5a2912c3885608 (patch)
treed6b89f969ef782948363e36e760212899aa450cd /src
parentaa32787f20ee7a08775c5412608067a4ced17d98 (diff)
split wire auditor
Diffstat (limited to 'src')
-rw-r--r--src/auditor/.gitignore3
-rw-r--r--src/auditor/Makefile.am25
-rw-r--r--src/auditor/taler-helper-auditor-wire-credit.c3236
-rw-r--r--src/auditor/taler-helper-auditor-wire-debit.c (renamed from src/auditor/taler-helper-auditor-wire.c)0
4 files changed, 3259 insertions, 5 deletions
diff --git a/src/auditor/.gitignore b/src/auditor/.gitignore
index 3dfd2bc82..e7812f058 100644
--- a/src/auditor/.gitignore
+++ b/src/auditor/.gitignore
@@ -13,7 +13,8 @@ taler-helper-auditor-aggregation
taler-helper-auditor-coins
taler-helper-auditor-deposits
taler-helper-auditor-reserves
-taler-helper-auditor-wire
+taler-helper-auditor-wire-credit
+taler-helper-auditor-wire-debit
generate-auditor-basedb-prod.conf
generate-auditor-basedb-revocation.conf
revocation-tmp-*
diff --git a/src/auditor/Makefile.am b/src/auditor/Makefile.am
index 67bed9c7c..ab25b30f3 100644
--- a/src/auditor/Makefile.am
+++ b/src/auditor/Makefile.am
@@ -23,7 +23,8 @@ bin_PROGRAMS = \
taler-helper-auditor-deposits \
taler-helper-auditor-purses \
taler-helper-auditor-reserves \
- taler-helper-auditor-wire
+ taler-helper-auditor-wire-credit \
+ taler-helper-auditor-wire-debit
bin_SCRIPTS = \
taler-auditor \
@@ -142,9 +143,25 @@ taler_helper_auditor_reserves_LDADD = \
-taler_helper_auditor_wire_SOURCES = \
- taler-helper-auditor-wire.c
-taler_helper_auditor_wire_LDADD = \
+taler_helper_auditor_wire_credit_SOURCES = \
+ taler-helper-auditor-wire-credit.c
+taler_helper_auditor_wire_credit_LDADD = \
+ $(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/bank-lib/libtalerbank.la \
+ $(top_builddir)/src/exchangedb/libtalerexchangedb.la \
+ $(top_builddir)/src/auditordb/libtalerauditordb.la \
+ libauditorreport.la \
+ -ljansson \
+ -lgnunetjson \
+ -lgnunetcurl \
+ -lgnunetutil \
+ $(XLIB)
+
+taler_helper_auditor_wire_debit_SOURCES = \
+ taler-helper-auditor-wire-debit.c
+taler_helper_auditor_wire_debit_LDADD = \
$(LIBGCRYPT_LIBS) \
$(top_builddir)/src/json/libtalerjson.la \
$(top_builddir)/src/util/libtalerutil.la \
diff --git a/src/auditor/taler-helper-auditor-wire-credit.c b/src/auditor/taler-helper-auditor-wire-credit.c
new file mode 100644
index 000000000..c50d51fea
--- /dev/null
+++ b/src/auditor/taler-helper-auditor-wire-credit.c
@@ -0,0 +1,3236 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2017-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file auditor/taler-helper-auditor-wire-credit.c
+ * @brief audits that wire transfers match those from an exchange database.
+ * @author Christian Grothoff
+ *
+ * - First, this auditor verifies that 'reserves_in' actually matches
+ * the incoming wire transfers from the bank.
+ * - Second, we check that the outgoing wire transfers match those
+ * given in the 'wire_out' and 'reserve_closures' tables
+ * - Finally, we check that all wire transfers that should have been made,
+ * were actually made
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_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"
+#include "taler_dbevents.h"
+
+
+/**
+ * How much time do we allow the aggregator to lag behind? If
+ * wire transfers should have been made more than #GRACE_PERIOD
+ * before, we issue warnings.
+ */
+#define GRACE_PERIOD GNUNET_TIME_UNIT_HOURS
+
+/**
+ * How much do we allow the bank and the exchange to disagree about
+ * timestamps? Should be sufficiently large to avoid bogus reports from deltas
+ * created by imperfect clock synchronization and network delay.
+ */
+#define TIME_TOLERANCE GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, \
+ 15)
+
+
+/**
+ * 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;
+
+struct TALER_AUDITORDB_WireAccountProgressPoint
+{
+ uint64_t last_reserve_in_serial_id;
+ uint64_t last_wire_out_serial_id;
+};
+
+/**
+ * Information we keep for each supported account.
+ */
+struct WireAccount
+{
+ /**
+ * Accounts are kept in a DLL.
+ */
+ struct WireAccount *next;
+
+ /**
+ * Plugins are kept in a DLL.
+ */
+ struct WireAccount *prev;
+
+ /**
+ * Account details.
+ */
+ const struct TALER_EXCHANGEDB_AccountInfo *ai;
+
+ /**
+ * Active wire request for the transaction history.
+ */
+ struct TALER_BANK_CreditHistoryHandle *chh;
+
+ /**
+ * Active wire request for the transaction history.
+ */
+ struct TALER_BANK_DebitHistoryHandle *dhh;
+
+ /**
+ * Progress point for this account.
+ */
+ struct TALER_AUDITORDB_WireAccountProgressPoint pp;
+
+ /**
+ * Initial progress point for this account.
+ */
+ struct TALER_AUDITORDB_WireAccountProgressPoint start_pp;
+
+ /**
+ * Where we are in the inbound transaction history.
+ */
+ uint64_t wire_off_in;
+
+ /**
+ * Where we are in the outbound transaction history.
+ */
+ uint64_t wire_off_out;
+
+ /**
+ * Label under which we store our pp's reserve_in_serial_id.
+ */
+ char *label_reserve_in_serial_id;
+
+ /**
+ * Label under which we store our pp's reserve_in_serial_id.
+ */
+ char *label_wire_out_serial_id;
+
+ /**
+ * Label under which we store our wire_off_in.
+ */
+ char *label_wire_off_in;
+
+ /**
+ * Label under which we store our wire_off_out.
+ */
+ char *label_wire_off_out;
+
+ /**
+ * Return value when we got this account's progress point.
+ */
+ enum GNUNET_DB_QueryStatus qsx;
+};
+
+
+/**
+ * Information we track for a reserve being closed.
+ */
+struct ReserveClosure
+{
+ /**
+ * Row in the reserves_closed table for this action.
+ */
+ uint64_t rowid;
+
+ /**
+ * When was the reserve closed?
+ */
+ struct GNUNET_TIME_Timestamp execution_date;
+
+ /**
+ * Amount transferred (amount remaining minus fee).
+ */
+ struct TALER_Amount amount;
+
+ /**
+ * Target account where the money was sent.
+ */
+ char *receiver_account;
+
+ /**
+ * Wire transfer subject used.
+ */
+ struct TALER_WireTransferIdentifierRawP wtid;
+};
+
+
+/**
+ * Map from H(wtid,receiver_account) to `struct ReserveClosure` entries.
+ */
+static struct GNUNET_CONTAINER_MultiHashMap *reserve_closures;
+
+/**
+ * Return value from main().
+ */
+static int global_ret;
+
+/**
+ * Map with information about incoming wire transfers.
+ * Maps hashes of the wire offsets to `struct ReserveInInfo`s.
+ */
+static struct GNUNET_CONTAINER_MultiHashMap *in_map;
+
+/**
+ * Map with information about outgoing wire transfers.
+ * Maps hashes of the wire subjects (in binary encoding)
+ * to `struct ReserveOutInfo`s.
+ */
+static struct GNUNET_CONTAINER_MultiHashMap *out_map;
+
+/**
+ * Head of list of wire accounts we still need to look at.
+ */
+static struct WireAccount *wa_head;
+
+/**
+ * Tail of list of wire accounts we still need to look at.
+ */
+static struct WireAccount *wa_tail;
+
+/**
+ * Query status for the incremental processing status in the auditordb.
+ * Return value from our call to the "get_wire_auditor_progress" function.
+ */
+static enum GNUNET_DB_QueryStatus qsx_gwap;
+
+/**
+ * Last reserve_in / wire_out serial IDs seen.
+ */
+static TALER_ARL_DEF_PP (wire_reserve_close_id);
+static TALER_ARL_DEF_PP (wire_batch_deposit_id);
+static TALER_ARL_DEF_PP (wire_aggregation_id);
+
+/**
+ * Amount that is considered "tiny"
+ */
+static struct TALER_Amount tiny_amount;
+
+/**
+ * Total amount that was transferred too much from the exchange.
+ */
+static struct TALER_Amount total_bad_amount_out_plus;
+
+/**
+ * Total amount that was transferred too little from the exchange.
+ */
+static struct TALER_Amount total_bad_amount_out_minus;
+
+/**
+ * Total amount that was transferred too much to the exchange.
+ */
+static struct TALER_Amount total_bad_amount_in_plus;
+
+/**
+ * Total amount that was transferred too little to the exchange.
+ */
+static struct TALER_Amount total_bad_amount_in_minus;
+
+/**
+ * Total amount where the exchange has the wrong sender account
+ * for incoming funds and may thus wire funds to the wrong
+ * destination when closing the reserve.
+ */
+static struct TALER_Amount total_misattribution_in;
+
+/**
+ * Total amount which the exchange did not transfer in time.
+ */
+static struct TALER_Amount total_amount_lag;
+
+/**
+ * Total amount of reserve closures which the exchange did not transfer in time.
+ */
+static struct TALER_Amount total_closure_amount_lag;
+
+/**
+ * Total amount affected by wire format trouble.s
+ */
+static struct TALER_Amount total_wire_format_amount;
+
+/**
+ * Total amount credited to exchange accounts.
+ */
+static struct TALER_Amount total_wire_in;
+
+/**
+ * Total amount debited to exchange accounts.
+ */
+static struct TALER_Amount total_wire_out;
+
+/**
+ * Total amount of profits drained.
+ */
+static TALER_ARL_DEF_AB (total_drained);
+
+/**
+ * Final balance at the end of this iteration.
+ */
+static TALER_ARL_DEF_AB (final_balance);
+
+/**
+ * Starting balance at the beginning of this iteration.
+ */
+static struct TALER_Amount start_balance;
+
+/**
+ * True if #start_balance was initialized.
+ */
+static bool had_start_balance;
+
+/**
+ * True if #start_balance was initialized.
+ */
+static bool had_start_progress;
+
+/**
+ * Amount of zero in our currency.
+ */
+static struct TALER_Amount zero;
+
+/**
+ * Handle to the context for interacting with the bank.
+ */
+static struct GNUNET_CURL_Context *ctx;
+
+/**
+ * Scheduler context for running the @e ctx.
+ */
+static struct GNUNET_CURL_RescheduleContext *rc;
+
+/**
+ * Should we run checks that only work for exchange-internal audits?
+ */
+static int internal_checks;
+
+/**
+ * Should we ignore if the bank does not know our bank
+ * account?
+ */
+static int ignore_account_404;
+
+static struct GNUNET_DB_EventHandler *eh;
+
+/**
+ * Our database plugin.
+ */
+static struct TALER_AUDITORDB_Plugin *db_plugin;
+
+/**
+ * The auditors's configuration.
+ */
+static const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+/* ***************************** Shutdown **************************** */
+
+/**
+ * Entry in map with wire information we expect to obtain from the
+ * bank later.
+ */
+struct ReserveInInfo
+{
+
+ /**
+ * Hash of expected row offset.
+ */
+ struct GNUNET_HashCode row_off_hash;
+
+ /**
+ * Expected details about the wire transfer.
+ * The member "account_url" is to be allocated
+ * at the end of this struct!
+ */
+ struct TALER_BANK_CreditDetails credit_details;
+
+ /**
+ * RowID in reserves_in table.
+ */
+ uint64_t rowid;
+
+};
+
+
+/**
+ * Entry in map with wire information we expect to obtain from the
+ * #TALER_ARL_edb later.
+ */
+struct ReserveOutInfo
+{
+
+ /**
+ * Hash of the wire transfer subject.
+ */
+ struct GNUNET_HashCode subject_hash;
+
+ /**
+ * Expected details about the wire transfer.
+ */
+ struct TALER_BANK_DebitDetails details;
+
+};
+
+
+/**
+ * Free entry in #in_map.
+ *
+ * @param cls NULL
+ * @param key unused key
+ * @param value the `struct ReserveInInfo` to free
+ * @return #GNUNET_OK
+ */
+static enum GNUNET_GenericReturnValue
+free_rii (void *cls,
+ const struct GNUNET_HashCode *key,
+ void *value)
+{
+ struct ReserveInInfo *rii = value;
+
+ (void) cls;
+ GNUNET_assert (GNUNET_YES ==
+ GNUNET_CONTAINER_multihashmap_remove (in_map,
+ key,
+ rii));
+ GNUNET_free (rii);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Free entry in #out_map.
+ *
+ * @param cls NULL
+ * @param key unused key
+ * @param value the `struct ReserveOutInfo` to free
+ * @return #GNUNET_OK
+ */
+static enum GNUNET_GenericReturnValue
+free_roi (void *cls,
+ const struct GNUNET_HashCode *key,
+ void *value)
+{
+ struct ReserveOutInfo *roi = value;
+
+ (void) cls;
+ GNUNET_assert (GNUNET_YES ==
+ GNUNET_CONTAINER_multihashmap_remove (out_map,
+ key,
+ roi));
+ GNUNET_free (roi);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Free entry in #reserve_closures.
+ *
+ * @param cls NULL
+ * @param key unused key
+ * @param value the `struct ReserveClosure` to free
+ * @return #GNUNET_OK
+ */
+static enum GNUNET_GenericReturnValue
+free_rc (void *cls,
+ const struct GNUNET_HashCode *key,
+ void *value)
+{
+ struct ReserveClosure *rc = value;
+
+ (void) cls;
+ GNUNET_assert (GNUNET_YES ==
+ GNUNET_CONTAINER_multihashmap_remove (reserve_closures,
+ key,
+ rc));
+ GNUNET_free (rc->receiver_account);
+ GNUNET_free (rc);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Task run on shutdown.
+ *
+ * @param cls NULL
+ */
+static void
+do_shutdown (void *cls)
+{
+ struct WireAccount *wa;
+
+ (void) cls;
+ if (NULL != eh)
+ {
+ db_plugin->event_listen_cancel (eh);
+ eh = NULL;
+ }
+ if (NULL != db_plugin)
+ {
+ TALER_AUDITORDB_plugin_unload (db_plugin);
+ db_plugin = NULL;
+ }
+ TALER_ARL_done (NULL);
+ if (NULL != reserve_closures)
+ {
+ GNUNET_CONTAINER_multihashmap_iterate (reserve_closures,
+ &free_rc,
+ NULL);
+ GNUNET_CONTAINER_multihashmap_destroy (reserve_closures);
+ reserve_closures = NULL;
+ }
+ if (NULL != in_map)
+ {
+ GNUNET_CONTAINER_multihashmap_iterate (in_map,
+ &free_rii,
+ NULL);
+ GNUNET_CONTAINER_multihashmap_destroy (in_map);
+ in_map = NULL;
+ }
+ if (NULL != out_map)
+ {
+ GNUNET_CONTAINER_multihashmap_iterate (out_map,
+ &free_roi,
+ NULL);
+ GNUNET_CONTAINER_multihashmap_destroy (out_map);
+ out_map = NULL;
+ }
+ while (NULL != (wa = wa_head))
+ {
+ if (NULL != wa->dhh)
+ {
+ TALER_BANK_debit_history_cancel (wa->dhh);
+ wa->dhh = NULL;
+ }
+ if (NULL != wa->chh)
+ {
+ TALER_BANK_credit_history_cancel (wa->chh);
+ wa->chh = NULL;
+ }
+ GNUNET_CONTAINER_DLL_remove (wa_head,
+ wa_tail,
+ wa);
+ GNUNET_free (wa->label_reserve_in_serial_id);
+ GNUNET_free (wa->label_wire_out_serial_id);
+ GNUNET_free (wa->label_wire_off_in);
+ GNUNET_free (wa->label_wire_off_out);
+ GNUNET_free (wa);
+ }
+ if (NULL != ctx)
+ {
+ GNUNET_CURL_fini (ctx);
+ ctx = NULL;
+ }
+ if (NULL != rc)
+ {
+ GNUNET_CURL_gnunet_rc_destroy (rc);
+ rc = NULL;
+ }
+ TALER_EXCHANGEDB_unload_accounts ();
+ TALER_ARL_cfg = NULL;
+}
+
+
+/**
+ * Detect any entries in #reserve_closures that were not yet
+ * observed on the wire transfer side and update the progress
+ * point accordingly.
+ *
+ * @param cls NULL
+ * @param key unused key
+ * @param value the `struct ReserveClosure` to free
+ * @return #GNUNET_OK
+ */
+static enum GNUNET_GenericReturnValue
+check_pending_rc (void *cls,
+ const struct GNUNET_HashCode *key,
+ void *value)
+{
+ struct ReserveClosure *rc = value;
+ /*enum GNUNET_DB_QueryStatus qs;
+ struct TALER_AUDITORDB_ClosureLags cl;*/
+
+ (void) cls;
+ (void) key;
+ TALER_ARL_amount_add (&total_closure_amount_lag,
+ &total_closure_amount_lag,
+ &rc->amount);
+ if (! TALER_amount_is_zero (&rc->amount))
+ {
+#if FIXME
+
+ cl.account = rc->receiver_account;
+ cl.amount = &rc->amount;
+ cl.deadline = rc->execution_date.abs_time;
+ cl.wtid = &rc->wtid;
+
+ qs = TALER_ARL_adb->insert_auditor_closure_lags (
+ TALER_ARL_adb->cls,
+ &cl);
+
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ }
+
+ TALER_ARL_report (
+ report_closure_lags,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("row",
+ rc->rowid),
+ TALER_JSON_pack_amount ("amount",
+ &rc->amount),
+ TALER_JSON_pack_time_abs_human ("deadline",
+ rc->execution_date.abs_time),
+ GNUNET_JSON_pack_data_auto ("wtid",
+ &rc->wtid),
+ GNUNET_JSON_pack_string ("account",
+ rc->receiver_account)));
+#endif
+ }
+ TALER_ARL_USE_PP (wire_reserve_close_id)
+ = GNUNET_MIN (TALER_ARL_USE_PP (wire_reserve_close_id),
+ rc->rowid);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Compute the key under which a reserve closure for a given
+ * @a receiver_account and @a wtid would be stored.
+ *
+ * @param receiver_account payto://-URI of the account
+ * @param wtid wire transfer identifier used
+ * @param[out] key set to the key
+ */
+static void
+hash_rc (const char *receiver_account,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ struct GNUNET_HashCode *key)
+{
+ size_t slen = strlen (receiver_account);
+ char buf[sizeof (struct TALER_WireTransferIdentifierRawP) + slen];
+
+ GNUNET_memcpy (buf,
+ wtid,
+ sizeof (*wtid));
+ GNUNET_memcpy (&buf[sizeof (*wtid)],
+ receiver_account,
+ slen);
+ GNUNET_CRYPTO_hash (buf,
+ sizeof (buf),
+ key);
+}
+
+
+/**
+ * Commit the transaction, checkpointing our progress in the auditor DB.
+ *
+ * @param qs transaction status so far
+ * @return transaction status code
+ */
+static enum GNUNET_DB_QueryStatus
+commit (enum GNUNET_DB_QueryStatus qs)
+{
+ if (qs >= 0)
+ {
+ if (had_start_balance)
+ {
+ struct TALER_Amount sum;
+
+ TALER_ARL_amount_add (&sum,
+ &total_wire_in,
+ &start_balance);
+ TALER_ARL_amount_subtract (&TALER_ARL_USE_AB (final_balance),
+ &sum,
+ &total_wire_out);
+ qs = TALER_ARL_adb->update_balance (
+ TALER_ARL_adb->cls,
+ TALER_ARL_SET_AB (total_drained),
+ TALER_ARL_SET_AB (final_balance),
+ NULL);
+ }
+ else
+ {
+ TALER_ARL_amount_subtract (&TALER_ARL_USE_AB (final_balance),
+ &total_wire_in,
+ &total_wire_out);
+ qs = TALER_ARL_adb->insert_balance (
+ TALER_ARL_adb->cls,
+ TALER_ARL_SET_AB (total_drained),
+ TALER_ARL_SET_AB (final_balance),
+ NULL);
+ }
+ }
+ if (0 > qs)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Serialization issue, not recording progress\n");
+ else
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Hard error, not recording progress\n");
+ TALER_ARL_adb->rollback (TALER_ARL_adb->cls);
+ TALER_ARL_edb->rollback (TALER_ARL_edb->cls);
+ return qs;
+ }
+ for (struct WireAccount *wa = wa_head;
+ NULL != wa;
+ wa = wa->next)
+ {
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == wa->qsx &&
+ had_start_progress)
+ qs = TALER_ARL_adb->update_auditor_progress (
+ TALER_ARL_adb->cls,
+ wa->label_reserve_in_serial_id,
+ wa->pp.last_reserve_in_serial_id,
+ wa->label_wire_out_serial_id,
+ wa->pp.last_wire_out_serial_id,
+ wa->label_wire_off_in,
+ wa->wire_off_in,
+ wa->label_wire_off_out,
+ wa->wire_off_out,
+ NULL);
+ else
+ qs = TALER_ARL_adb->insert_auditor_progress (
+ TALER_ARL_adb->cls,
+ wa->label_reserve_in_serial_id,
+ wa->pp.last_reserve_in_serial_id,
+ wa->label_wire_out_serial_id,
+ wa->pp.last_wire_out_serial_id,
+ wa->label_wire_off_in,
+ wa->wire_off_in,
+ wa->label_wire_off_out,
+ wa->wire_off_out,
+ 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_CONTAINER_multihashmap_iterate (reserve_closures,
+ &check_pending_rc,
+ NULL);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qsx_gwap && had_start_progress ==
+ true)
+ qs = TALER_ARL_adb->update_auditor_progress (
+ TALER_ARL_adb->cls,
+ TALER_ARL_SET_PP (wire_reserve_close_id),
+ TALER_ARL_SET_PP (wire_batch_deposit_id),
+ TALER_ARL_SET_PP (wire_aggregation_id),
+ NULL);
+ else
+ qs = TALER_ARL_adb->insert_auditor_progress (
+ TALER_ARL_adb->cls,
+ TALER_ARL_SET_PP (wire_reserve_close_id),
+ TALER_ARL_SET_PP (wire_batch_deposit_id),
+ TALER_ARL_SET_PP (wire_aggregation_id),
+ 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 audit step at %llu/%llu\n",
+ (unsigned long long) TALER_ARL_USE_PP (wire_aggregation_id),
+ (unsigned long long) TALER_ARL_USE_PP (wire_batch_deposit_id));
+
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ qs = TALER_ARL_edb->commit (TALER_ARL_edb->cls);
+ if (0 > qs)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Exchange DB commit failed, rolling back transaction\n");
+ TALER_ARL_adb->rollback (TALER_ARL_adb->cls);
+ }
+ else
+ {
+ qs = TALER_ARL_adb->commit (TALER_ARL_adb->cls);
+ if (0 > qs)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Auditor DB commit failed!\n");
+ }
+ }
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Processing failed, rolling back transaction\n");
+ TALER_ARL_adb->rollback (TALER_ARL_adb->cls);
+ TALER_ARL_edb->rollback (TALER_ARL_edb->cls);
+ }
+ return qs;
+}
+
+
+/* ***************************** Analyze required transfers ************************ */
+
+/**
+ * Closure for import_wire_missing_cb().
+ */
+struct ImportMissingWireContext
+{
+ /**
+ * Set to maximum row ID encountered.
+ */
+ uint64_t max_batch_deposit_uuid;
+
+ /**
+ * Set to database errors in callback.
+ */
+ enum GNUNET_DB_QueryStatus err;
+};
+
+
+/**
+ * Function called on deposits that need to be checked for their
+ * wire transfer.
+ *
+ * @param cls closure, points to a `struct ImportMissingWireContext`
+ * @param batch_deposit_serial_id serial of the entry in the batch deposits table
+ * @param total_amount value of the missing deposits, including fee
+ * @param wire_target_h_payto where should the funds be wired
+ * @param deadline what was the earliest requested wire transfer deadline
+ */
+static void
+import_wire_missing_cb (void *cls,
+ uint64_t batch_deposit_serial_id,
+ const struct TALER_Amount *total_amount,
+ const struct TALER_PaytoHashP *wire_target_h_payto,
+ struct GNUNET_TIME_Timestamp deadline)
+{
+ struct ImportMissingWireContext *wc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+
+ if (wc->err < 0)
+ return; /* already failed */
+ GNUNET_assert (batch_deposit_serial_id > wc->max_batch_deposit_uuid);
+ wc->max_batch_deposit_uuid = batch_deposit_serial_id;
+ qs = TALER_ARL_adb->insert_pending_deposit (
+ TALER_ARL_adb->cls,
+ batch_deposit_serial_id,
+ wire_target_h_payto,
+ total_amount,
+ deadline);
+ if (qs < 0)
+ wc->err = qs;
+}
+
+
+/**
+ * Information about a delayed wire transfer and the possible
+ * reasons for the delay.
+ */
+struct ReasonDetail
+{
+ /**
+ * Total amount that should have been transferred.
+ */
+ struct TALER_Amount total_amount;
+
+ /**
+ * Earliest deadline for an expected transfer to the account.
+ */
+ struct GNUNET_TIME_Timestamp deadline;
+
+ /**
+ * Target account, NULL if even that is not known (due to
+ * exchange lacking required entry in wire_targets table).
+ */
+ char *payto_uri;
+
+ /**
+ * Account properties, possibly NULL.
+ */
+ json_t *properties;
+
+ /**
+ * Account KYC rules.
+ */
+ json_t *jrules;
+
+};
+
+/**
+ * Closure for report_wire_missing_cb().
+ */
+struct ReportMissingWireContext
+{
+ /**
+ * Map from wire_target_h_payto to `struct ReasonDetail`.
+ */
+ struct GNUNET_CONTAINER_MultiShortmap *map;
+
+ /**
+ * Set to database errors in callback.
+ */
+ enum GNUNET_DB_QueryStatus err;
+};
+
+
+/**
+ * Closure for #clear_finished_transfer_cb().
+ */
+struct AggregationContext
+{
+ /**
+ * Set to maximum row ID encountered.
+ */
+ uint64_t max_aggregation_serial;
+
+ /**
+ * Set to database errors in callback.
+ */
+ enum GNUNET_DB_QueryStatus err;
+};
+
+
+/**
+ * Free memory allocated in @a value.
+ *
+ * @param cls unused
+ * @param key unused
+ * @param value must be a `struct ReasonDetail`
+ * @return #GNUNET_YES if we should continue to
+ * iterate,
+ * #GNUNET_NO if not.
+ */
+static enum GNUNET_GenericReturnValue
+free_report_entry (void *cls,
+ const struct GNUNET_ShortHashCode *key,
+ void *value)
+{
+ struct ReasonDetail *rd = value;
+
+ json_decref (rd->properties);
+ json_decref (rd->jrules);
+ GNUNET_free (rd->payto_uri);
+ GNUNET_free (rd);
+ return GNUNET_YES;
+}
+
+
+/**
+ * We had an entry in our map of wire transfers that
+ * should have been performed. Generate report.
+ *
+ * @param cls unused
+ * @param key unused
+ * @param value must be a `struct ReasonDetail`
+ * @return #GNUNET_YES if we should continue to
+ * iterate,
+ * #GNUNET_NO if not.
+ */
+static enum GNUNET_GenericReturnValue
+generate_report (void *cls,
+ const struct GNUNET_ShortHashCode *key,
+ void *value)
+{
+ struct ReasonDetail *rd = value;
+ // enum GNUNET_DB_QueryStatus qs;
+ // struct TALER_AUDITORDB_KycLag kycl;
+ // struct TALER_AUDITORDB_AmlLag amllag;
+ // struct TALER_AUDITORDB_Lag lag;
+
+ /* For now, we simplify and only check that the
+ amount was tiny */
+ if (0 > TALER_amount_cmp (&rd->total_amount,
+ &tiny_amount))
+ return free_report_entry (cls,
+ key,
+ value); /* acceptable, amount was tiny */
+
+#if FIXME
+ // TODO: maybe split total_amount_lag up by category below?
+ TALER_ARL_amount_add (&total_amount_lag,
+ &total_amount_lag,
+ &rd->total_amount);
+ if (NULL != rd->kyc_pending)
+ {
+ json_t *rep;
+
+ rep = GNUNET_JSON_PACK (
+ TALER_JSON_pack_amount ("total_amount",
+ &rd->total_amount),
+ TALER_JSON_pack_time_abs_human ("deadline",
+ rd->deadline.abs_time),
+ GNUNET_JSON_pack_object_incref ("kyc_rules",
+ rd->rules),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("properties",
+ rd->properties)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("account",
+ rd->payto_uri)));
+ // TODO add kyc lag db entry
+ /*rbiil.reserve_pub = rs->reserve_pub.eddsa_pub;
+ rbiil.inconsistency_amount = loss;
+ rbiil.inconsistency_gain = false;
+
+ qs = TALER_ARL_adb->insert_reserve_balance_insufficient_inconsistency (
+ TALER_ARL_adb->cls,
+ &rbiil);
+
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ }*/
+
+ TALER_ARL_report (report_kyc_lags,
+ rep);
+ }
+ else if (TALER_AML_NORMAL != rd->status)
+ {
+ const char *sstatus = "<undefined>";
+ json_t *rep;
+
+ switch (rd->status)
+ {
+ case TALER_AML_NORMAL:
+ GNUNET_assert (0);
+ break;
+ case TALER_AML_PENDING:
+ sstatus = "pending";
+ break;
+ case TALER_AML_FROZEN:
+ sstatus = "frozen";
+ break;
+ }
+ rep = GNUNET_JSON_PACK (
+ TALER_JSON_pack_amount ("total_amount",
+ &rd->total_amount),
+ GNUNET_JSON_pack_allow_null (
+ TALER_JSON_pack_amount ("aml_limit",
+ TALER_amount_is_valid (&rd->aml_limit)
+ ? &rd->aml_limit
+ : NULL)),
+ TALER_JSON_pack_time_abs_human ("deadline",
+ rd->deadline.abs_time),
+ GNUNET_JSON_pack_string ("aml_status",
+ sstatus),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("account",
+ rd->payto_uri)));
+ // TODO add aml lag db entry
+ /*rbiil.reserve_pub = rs->reserve_pub.eddsa_pub;
+ rbiil.inconsistency_amount = loss;
+ rbiil.inconsistency_gain = false;
+
+ qs = TALER_ARL_adb->insert_reserve_balance_insufficient_inconsistency (
+ TALER_ARL_adb->cls,
+ &rbiil);
+
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ }*/
+ TALER_ARL_report (report_aml_lags,
+ rep);
+ }
+ else
+ {
+ json_t *rep;
+
+ rep = GNUNET_JSON_PACK (
+ TALER_JSON_pack_amount ("total_amount",
+ &rd->total_amount),
+ TALER_JSON_pack_time_abs_human ("deadline",
+ rd->deadline.abs_time),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("account",
+ rd->payto_uri)));
+ // TODO add lag
+ /*rbiil.reserve_pub = rs->reserve_pub.eddsa_pub;
+ rbiil.inconsistency_amount = loss;
+ rbiil.inconsistency_gain = false;
+
+ qs = TALER_ARL_adb->insert_reserve_balance_insufficient_inconsistency (
+ TALER_ARL_adb->cls,
+ &rbiil);
+
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ }*/
+ TALER_ARL_report (report_lags,
+ rep);
+ }
+#endif
+
+ return free_report_entry (cls,
+ key,
+ value);
+}
+
+
+/**
+ * Function called on deposits that are past their due date
+ * and have not yet seen a wire transfer.
+ *
+ * @param cls closure, points to a `struct ReportMissingWireContext`
+ * @param batch_deposit_serial_id row in the database for which the wire transfer is missing
+ * @param total_amount value of the missing deposits, including fee
+ * @param wire_target_h_payto hash of payto-URI where the funds should have been wired
+ * @param deadline what was the earliest requested wire transfer deadline
+ */
+static void
+report_wire_missing_cb (void *cls,
+ uint64_t batch_deposit_serial_id,
+ const struct TALER_Amount *total_amount,
+ const struct TALER_PaytoHashP *wire_target_h_payto,
+ struct GNUNET_TIME_Timestamp deadline)
+{
+ struct ReportMissingWireContext *rc = cls;
+ struct ReasonDetail *rd;
+
+ rd = GNUNET_CONTAINER_multishortmap_get (rc->map,
+ &wire_target_h_payto->hash);
+ if (NULL == rd)
+ {
+ rd = GNUNET_new (struct ReasonDetail);
+ GNUNET_assert (GNUNET_YES ==
+ GNUNET_CONTAINER_multishortmap_put (
+ rc->map,
+ &wire_target_h_payto->hash,
+ rd,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+ rc->err = TALER_ARL_edb->select_justification_for_missing_wire (
+ TALER_ARL_edb->cls,
+ wire_target_h_payto,
+ &rd->payto_uri,
+ &rd->properties,
+ &rd->jrules);
+ rd->total_amount = *total_amount;
+ rd->deadline = deadline;
+ }
+ else
+ {
+ TALER_ARL_amount_add (&rd->total_amount,
+ &rd->total_amount,
+ total_amount);
+ rd->deadline = GNUNET_TIME_timestamp_min (rd->deadline,
+ deadline);
+ }
+}
+
+
+/**
+ * Function called on aggregations that were done for
+ * a (batch) deposit.
+ *
+ * @param cls closure
+ * @param tracking_serial_id where in the table are we
+ * @param batch_deposit_serial_id which batch deposit was aggregated
+ */
+static void
+clear_finished_transfer_cb (
+ void *cls,
+ uint64_t tracking_serial_id,
+ uint64_t batch_deposit_serial_id)
+{
+ struct AggregationContext *ac = cls;
+ enum GNUNET_DB_QueryStatus qs;
+
+ if (0 > ac->err)
+ return; /* already failed */
+ GNUNET_assert (ac->max_aggregation_serial < tracking_serial_id);
+ ac->max_aggregation_serial = tracking_serial_id;
+ qs = TALER_ARL_adb->delete_pending_deposit (
+ TALER_ARL_adb->cls,
+ batch_deposit_serial_id);
+ if (0 == qs)
+ {
+ /* Aggregated something twice or other error, report! */
+ GNUNET_break (0);
+ // FIXME: report more nicely!
+ }
+ if (0 > qs)
+ ac->err = qs;
+}
+
+
+/**
+ * Checks that all wire transfers that should have happened
+ * (based on deposits) have indeed happened.
+ */
+static void
+check_for_required_transfers (void)
+{
+ struct ImportMissingWireContext wc = {
+ .max_batch_deposit_uuid = TALER_ARL_USE_PP (wire_batch_deposit_id),
+ .err = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT
+ };
+ struct GNUNET_TIME_Absolute deadline;
+ enum GNUNET_DB_QueryStatus qs;
+ struct ReportMissingWireContext rc = {
+ .err = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT
+ };
+ struct AggregationContext ac = {
+ .max_aggregation_serial = TALER_ARL_USE_PP (wire_aggregation_id),
+ .err = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT
+ };
+
+ qs = TALER_ARL_edb->select_batch_deposits_missing_wire (
+ TALER_ARL_edb->cls,
+ TALER_ARL_USE_PP (wire_batch_deposit_id),
+ &import_wire_missing_cb,
+ &wc);
+ if ((0 > qs) || (0 > wc.err))
+ {
+ GNUNET_break (0);
+ GNUNET_break ((GNUNET_DB_STATUS_SOFT_ERROR == qs) ||
+ (GNUNET_DB_STATUS_SOFT_ERROR == wc.err));
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ TALER_ARL_USE_PP (wire_batch_deposit_id) = wc.max_batch_deposit_uuid;
+ qs = TALER_ARL_edb->select_aggregations_above_serial (
+ TALER_ARL_edb->cls,
+ TALER_ARL_USE_PP (wire_aggregation_id),
+ &clear_finished_transfer_cb,
+ &ac);
+ if ((0 > qs) || (0 > ac.err))
+ {
+ GNUNET_break (0);
+ GNUNET_break ((GNUNET_DB_STATUS_SOFT_ERROR == qs) ||
+ (GNUNET_DB_STATUS_SOFT_ERROR == ac.err));
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ TALER_ARL_USE_PP (wire_aggregation_id) = ac.max_aggregation_serial;
+ /* Subtract #GRACE_PERIOD, so we can be a bit behind in processing
+ without immediately raising undue concern */
+ deadline = GNUNET_TIME_absolute_subtract (GNUNET_TIME_absolute_get (),
+ GRACE_PERIOD);
+ rc.map = GNUNET_CONTAINER_multishortmap_create (1024,
+ GNUNET_NO);
+ qs = TALER_ARL_adb->select_pending_deposits (
+ TALER_ARL_adb->cls,
+ deadline,
+ &report_wire_missing_cb,
+ &rc);
+ if ((0 > qs) || (0 > rc.err))
+ {
+ GNUNET_break (0);
+ GNUNET_break ((GNUNET_DB_STATUS_SOFT_ERROR == qs) ||
+ (GNUNET_DB_STATUS_SOFT_ERROR == rc.err));
+ GNUNET_CONTAINER_multishortmap_iterate (rc.map,
+ &free_report_entry,
+ NULL);
+ GNUNET_CONTAINER_multishortmap_destroy (rc.map);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ GNUNET_CONTAINER_multishortmap_iterate (rc.map,
+ &generate_report,
+ NULL);
+ GNUNET_CONTAINER_multishortmap_destroy (rc.map);
+ /* conclude with success */
+ commit (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
+ GNUNET_SCHEDULER_shutdown ();
+}
+
+
+/* ***************************** Analyze reserves_out ************************ */
+
+/**
+ * Clean up after processing wire out data.
+ */
+static void
+conclude_wire_out (void)
+{
+ GNUNET_CONTAINER_multihashmap_destroy (out_map);
+ out_map = NULL;
+ check_for_required_transfers ();
+}
+
+
+/**
+ * Check that @a want is within #TIME_TOLERANCE of @a have.
+ * Otherwise report an inconsistency in row @a rowid of @a table.
+ *
+ * @param table where is the inconsistency (if any)
+ * @param rowid what is the row
+ * @param want what is the expected time
+ * @param have what is the time we got
+ */
+static void
+check_time_difference (const char *table,
+ uint64_t rowid,
+ struct GNUNET_TIME_Timestamp want,
+ struct GNUNET_TIME_Timestamp have)
+{
+ struct GNUNET_TIME_Relative delta;
+ char *details;
+ // enum GNUNET_DB_QueryStatus qs;
+ // struct TALER_AUDITORDB_RowMinorInconsistencies rmi;
+
+ if (GNUNET_TIME_timestamp_cmp (have, >, want))
+ delta = GNUNET_TIME_absolute_get_difference (want.abs_time,
+ have.abs_time);
+ else
+ delta = GNUNET_TIME_absolute_get_difference (have.abs_time,
+ want.abs_time);
+ if (GNUNET_TIME_relative_cmp (delta,
+ <=,
+ TIME_TOLERANCE))
+ return;
+
+ GNUNET_asprintf (&details,
+ "execution date mismatch (%s)",
+ GNUNET_TIME_relative2s (delta,
+ true));
+#if FIXME
+
+ rmi.diagnostic = details;
+ rmi.row_table = (char *) table;
+
+ qs = TALER_ARL_adb->insert_row_minor_inconsistencies (
+ TALER_ARL_adb->cls,
+ &rmi);
+
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ }
+ TALER_ARL_report (report_row_minor_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("table",
+ table),
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ GNUNET_JSON_pack_string ("diagnostic",
+ details)));
+#endif
+ GNUNET_free (details);
+}
+
+
+/**
+ * Function called with details about outgoing wire transfers
+ * as claimed by the exchange DB.
+ *
+ * @param cls a `struct WireAccount`
+ * @param rowid unique serial ID for the refresh session in our DB
+ * @param date timestamp of the transfer (roughly)
+ * @param wtid wire transfer subject
+ * @param payto_uri wire transfer details of the receiver
+ * @param amount amount that was wired
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+static enum GNUNET_GenericReturnValue
+wire_out_cb (void *cls,
+ uint64_t rowid,
+ struct GNUNET_TIME_Timestamp date,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const char *payto_uri,
+ const struct TALER_Amount *amount)
+{
+ struct WireAccount *wa = cls;
+ struct GNUNET_HashCode key;
+ struct ReserveOutInfo *roi;
+ /*struct TALER_AUDITORDB_WireOutInconsistency woi;
+ struct TALER_AUDITORDB_WireOutInconsistency woi2;
+ struct TALER_AUDITORDB_WireOutInconsistency woi3;
+ struct TALER_AUDITORDB_WireOutInconsistency woi4;
+ enum GNUNET_DB_QueryStatus qs; */
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Exchange wire OUT at %s of %s with WTID %s\n",
+ GNUNET_TIME_timestamp2s (date),
+ TALER_amount2s (amount),
+ TALER_B2S (wtid));
+ TALER_ARL_amount_add (&total_wire_out,
+ &total_wire_out,
+ amount);
+ GNUNET_CRYPTO_hash (wtid,
+ sizeof (struct TALER_WireTransferIdentifierRawP),
+ &key);
+ roi = GNUNET_CONTAINER_multihashmap_get (out_map,
+ &key);
+ if (NULL == roi)
+ {
+ /* Wire transfer was not made (yet) at all (but would have been
+ justified), so the entire amount is missing / still to be done.
+ This is moderately harmless, it might just be that the aggregator
+ has not yet fully caught up with the transfers it should do. */
+#if FIXME
+// TODO fix woi implementation
+ /* woi.
+
+ qs = TALER_ARL_adb->insert_reserve_balance_insufficient_inconsistency (
+ TALER_ARL_adb->cls,
+ &rbiil);
+
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ }*/
+ TALER_ARL_report (
+ report_wire_out_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ TALER_JSON_pack_amount ("amount_wired",
+ &zero),
+ TALER_JSON_pack_amount ("amount_justified",
+ amount),
+ GNUNET_JSON_pack_data_auto ("wtid",
+ wtid),
+ TALER_JSON_pack_time_abs_human ("timestamp",
+ date.abs_time),
+ GNUNET_JSON_pack_string ("diagnostic",
+ "wire transfer not made (yet?)"),
+ GNUNET_JSON_pack_string ("account_section",
+ wa->ai->section_name)));
+#endif
+ TALER_ARL_amount_add (&total_bad_amount_out_minus,
+ &total_bad_amount_out_minus,
+ amount);
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
+ return GNUNET_OK;
+ }
+ if (0 != strcasecmp (payto_uri,
+ roi->details.credit_account_uri))
+ {
+ /* Destination bank account is wrong in actual wire transfer, so
+ we should count the wire transfer as entirely spurious, and
+ additionally consider the justified wire transfer as missing. */
+#if FIXME
+ /* woi2.
+
+ qs = TALER_ARL_adb->insert_reserve_balance_insufficient_inconsistency (
+ TALER_ARL_adb->cls,
+ &rbiil);
+
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ }*/
+ TALER_ARL_report (
+ report_wire_out_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ TALER_JSON_pack_amount ("amount_wired",
+ &roi->details.amount),
+ TALER_JSON_pack_amount ("amount_justified",
+ &zero),
+ GNUNET_JSON_pack_data_auto ("wtid",
+ wtid),
+ TALER_JSON_pack_time_abs_human ("timestamp",
+ date.abs_time),
+ GNUNET_JSON_pack_string ("diagnostic",
+ "receiver account mismatch"),
+ GNUNET_JSON_pack_string ("target",
+ payto_uri),
+ GNUNET_JSON_pack_string ("account_section",
+ wa->ai->section_name)));
+#endif
+ TALER_ARL_amount_add (&total_bad_amount_out_plus,
+ &total_bad_amount_out_plus,
+ &roi->details.amount);
+#if FIXME
+ TALER_ARL_report (
+ /* woi3.
+
+qs = TALER_ARL_adb->insert_reserve_balance_insufficient_inconsistency (
+ TALER_ARL_adb->cls,
+ &rbiil);
+
+if (qs < 0)
+{
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+}*/
+ report_wire_out_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ TALER_JSON_pack_amount ("amount_wired",
+ &zero),
+ TALER_JSON_pack_amount ("amount_justified",
+ amount),
+ GNUNET_JSON_pack_data_auto ("wtid",
+ wtid),
+ TALER_JSON_pack_time_abs_human ("timestamp",
+ date.abs_time),
+ GNUNET_JSON_pack_string ("diagnostic",
+ "receiver account mismatch"),
+ GNUNET_JSON_pack_string ("target",
+ roi->details.
+ credit_account_uri),
+ GNUNET_JSON_pack_string ("account_section",
+ wa->ai->section_name)));
+#endif
+ TALER_ARL_amount_add (&total_bad_amount_out_minus,
+ &total_bad_amount_out_minus,
+ amount);
+ goto cleanup;
+ }
+ if (0 != TALER_amount_cmp (&roi->details.amount,
+ amount))
+ {
+#if FIXME
+ TALER_ARL_report (
+ /* woi4.
+
+qs = TALER_ARL_adb->insert_reserve_balance_insufficient_inconsistency (
+ TALER_ARL_adb->cls,
+ &rbiil);
+
+if (qs < 0)
+{
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+}*/
+ report_wire_out_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ TALER_JSON_pack_amount ("amount_justified",
+ amount),
+ TALER_JSON_pack_amount ("amount_wired",
+ &roi->details.amount),
+ GNUNET_JSON_pack_data_auto ("wtid",
+ wtid),
+ TALER_JSON_pack_time_abs_human ("timestamp",
+ date.abs_time),
+ GNUNET_JSON_pack_string ("diagnostic",
+ "wire amount does not match"),
+ GNUNET_JSON_pack_string ("account_section",
+ wa->ai->section_name)));
+#endif
+ if (0 < TALER_amount_cmp (amount,
+ &roi->details.amount))
+ {
+ /* amount > roi->details.amount: wire transfer was smaller than it should have been */
+ struct TALER_Amount delta;
+
+ TALER_ARL_amount_subtract (&delta,
+ amount,
+ &roi->details.amount);
+ TALER_ARL_amount_add (&total_bad_amount_out_minus,
+ &total_bad_amount_out_minus,
+ &delta);
+ }
+ else
+ {
+ /* roi->details.amount < amount: wire transfer was larger than it should have been */
+ struct TALER_Amount delta;
+
+ TALER_ARL_amount_subtract (&delta,
+ &roi->details.amount,
+ amount);
+ TALER_ARL_amount_add (&total_bad_amount_out_plus,
+ &total_bad_amount_out_plus,
+ &delta);
+ }
+ goto cleanup;
+ }
+
+ check_time_difference ("wire_out",
+ rowid,
+ date,
+ roi->details.execution_date);
+cleanup:
+ GNUNET_assert (GNUNET_OK ==
+ free_roi (NULL,
+ &key,
+ roi));
+ wa->pp.last_wire_out_serial_id = rowid + 1;
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
+ return GNUNET_OK;
+}
+
+
+/**
+ * Closure for #check_rc_matches
+ */
+struct CheckMatchContext
+{
+
+ /**
+ * Reserve operation looking for a match
+ */
+ const struct ReserveOutInfo *roi;
+
+ /**
+ * Set to true if we found a match.
+ */
+ bool found;
+};
+
+
+/**
+ * Check if any of the reserve closures match the given wire transfer.
+ *
+ * @param[in,out] cls a `struct CheckMatchContext`
+ * @param key key of @a value in #reserve_closures
+ * @param value a `struct ReserveClosure`
+ */
+static enum GNUNET_GenericReturnValue
+check_rc_matches (void *cls,
+ const struct GNUNET_HashCode *key,
+ void *value)
+{
+ struct CheckMatchContext *ctx = cls;
+ struct ReserveClosure *rc = value;
+
+ if ((0 == GNUNET_memcmp (&ctx->roi->details.wtid,
+ &rc->wtid)) &&
+ (0 == strcasecmp (rc->receiver_account,
+ ctx->roi->details.credit_account_uri)) &&
+ (0 == TALER_amount_cmp (&rc->amount,
+ &ctx->roi->details.amount)))
+ {
+ check_time_difference ("reserves_closures",
+ rc->rowid,
+ rc->execution_date,
+ ctx->roi->details.execution_date);
+ ctx->found = true;
+ free_rc (NULL,
+ key,
+ rc);
+ return GNUNET_NO;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Check whether the given transfer was justified by a reserve closure or
+ * profit drain. If not, complain that we failed to match an entry from
+ * #out_map. This means a wire transfer was made without proper
+ * justification.
+ *
+ * @param cls a `struct WireAccount`
+ * @param key unused key
+ * @param value the `struct ReserveOutInfo` to report
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+complain_out_not_found (void *cls,
+ const struct GNUNET_HashCode *key,
+ void *value)
+{
+ // struct WireAccount *wa = cls;
+ struct ReserveOutInfo *roi = value;
+ struct GNUNET_HashCode rkey;
+ struct CheckMatchContext ctx = {
+ .roi = roi,
+ .found = false
+ };
+
+ (void) key;
+ hash_rc (roi->details.credit_account_uri,
+ &roi->details.wtid,
+ &rkey);
+ GNUNET_CONTAINER_multihashmap_get_multiple (reserve_closures,
+ &rkey,
+ &check_rc_matches,
+ &ctx);
+ if (ctx.found)
+ return GNUNET_OK;
+ /* check for profit drain */
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ uint64_t serial;
+ char *account_section;
+ char *payto_uri;
+ struct GNUNET_TIME_Timestamp request_timestamp;
+ struct TALER_Amount amount;
+ struct TALER_MasterSignatureP master_sig;
+ // struct TALER_AUDITORDB_RowInconsistency ri;
+ // struct TALER_AUDITORDB_WireOutInconsistency woi;
+ // struct TALER_AUDITORDB_WireOutInconsistency woi2;
+ // struct TALER_AUDITORDB_WireOutInconsistency woi3;
+
+ qs = TALER_ARL_edb->get_drain_profit (TALER_ARL_edb->cls,
+ &roi->details.wtid,
+ &serial,
+ &account_section,
+ &payto_uri,
+ &request_timestamp,
+ &amount,
+ &master_sig);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return GNUNET_SYSERR;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ /* should fail on commit later ... */
+ GNUNET_break (0);
+ return GNUNET_NO;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* not a profit drain */
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Profit drain of %s to %s found!\n",
+ TALER_amount2s (&amount),
+ payto_uri);
+ if (GNUNET_OK !=
+ TALER_exchange_offline_profit_drain_verify (
+ &roi->details.wtid,
+ request_timestamp,
+ &amount,
+ account_section,
+ payto_uri,
+ &TALER_ARL_master_pub,
+ &master_sig))
+ {
+ GNUNET_break (0);
+#if FIXME
+ ri.row_table = "profit_drains";
+ ri.diagnostic = "invalid signature";
+
+ qs = TALER_ARL_adb->insert_row_inconsistency (
+ TALER_ARL_adb->cls,
+ &ri);
+
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ }
+ TALER_ARL_report (report_row_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("table",
+ "profit_drains"),
+ GNUNET_JSON_pack_uint64 ("row",
+ serial),
+ GNUNET_JSON_pack_data_auto ("id",
+ &roi->details.wtid),
+ GNUNET_JSON_pack_string ("diagnostic",
+ "invalid signature")));
+#endif
+ TALER_ARL_amount_add (&total_bad_amount_out_plus,
+ &total_bad_amount_out_plus,
+ &amount);
+ }
+ else if (0 !=
+ strcasecmp (payto_uri,
+ roi->details.credit_account_uri))
+ {
+#if FIXME
+ // TODO fix woi
+ /* woi.
+
+ qs = TALER_ARL_adb->insert_reserve_balance_insufficient_inconsistency (
+ TALER_ARL_adb->cls,
+ &rbiil);
+
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ }*/
+ TALER_ARL_report (
+ report_wire_out_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("row",
+ serial),
+ TALER_JSON_pack_amount ("amount_wired",
+ &roi->details.amount),
+ TALER_JSON_pack_amount ("amount_wired",
+ &amount),
+ GNUNET_JSON_pack_data_auto ("wtid",
+ &roi->details.wtid),
+ TALER_JSON_pack_time_abs_human ("timestamp",
+ roi->details.execution_date.abs_time
+ ),
+ GNUNET_JSON_pack_string ("account",
+ wa->ai->section_name),
+ GNUNET_JSON_pack_string ("diagnostic",
+ "wrong target account")));
+#endif
+ TALER_ARL_amount_add (&total_bad_amount_out_plus,
+ &total_bad_amount_out_plus,
+ &amount);
+ }
+ else if (0 !=
+ TALER_amount_cmp (&amount,
+ &roi->details.amount))
+ {
+#if FIXME
+ // TODO fix woi
+ /* woi.
+
+ qs = TALER_ARL_adb->insert_reserve_balance_insufficient_inconsistency (
+ TALER_ARL_adb->cls,
+ &rbiil);
+
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ }*/
+ TALER_ARL_report (
+ report_wire_out_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("row",
+ serial),
+ TALER_JSON_pack_amount ("amount_justified",
+ &roi->details.amount),
+ TALER_JSON_pack_amount ("amount_wired",
+ &amount),
+ GNUNET_JSON_pack_data_auto ("wtid",
+ &roi->details.wtid),
+ TALER_JSON_pack_time_abs_human ("timestamp",
+ roi->details.execution_date.abs_time
+ ),
+ GNUNET_JSON_pack_string ("account",
+ wa->ai->section_name),
+ GNUNET_JSON_pack_string ("diagnostic",
+ "profit drain amount incorrect")));
+#endif
+ TALER_ARL_amount_add (&total_bad_amount_out_minus,
+ &total_bad_amount_out_minus,
+ &roi->details.amount);
+ TALER_ARL_amount_add (&total_bad_amount_out_plus,
+ &total_bad_amount_out_plus,
+ &amount);
+ }
+ GNUNET_free (account_section);
+ GNUNET_free (payto_uri);
+ /* profit drain was correct */
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_drained),
+ &TALER_ARL_USE_AB (total_drained),
+ &amount);
+ return GNUNET_OK;
+ }
+ }
+#if FIXME
+ // TODO fix woi
+ /* woi3.
+
+ qs = TALER_ARL_adb->insert_reserve_balance_insufficient_inconsistency (
+ TALER_ARL_adb->cls,
+ &rbiil);
+
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ }*/
+ TALER_ARL_report (
+ report_wire_out_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("row",
+ 0),
+ TALER_JSON_pack_amount ("amount_wired",
+ &roi->details.amount),
+ TALER_JSON_pack_amount ("amount_justified",
+ &zero),
+ GNUNET_JSON_pack_data_auto ("wtid",
+ &roi->details.wtid),
+ TALER_JSON_pack_time_abs_human ("timestamp",
+ roi->details.execution_date.abs_time),
+ GNUNET_JSON_pack_string ("account_section",
+ wa->ai->section_name),
+ GNUNET_JSON_pack_string ("diagnostic",
+ "justification for wire transfer not found")));
+#endif
+ TALER_ARL_amount_add (&total_bad_amount_out_plus,
+ &total_bad_amount_out_plus,
+ &roi->details.amount);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Main function for processing 'reserves_out' data. We start by going over
+ * the DEBIT transactions this time, and then verify that all of them are
+ * justified by 'reserves_out'.
+ *
+ * @param cls `struct WireAccount` with a wire account list to process
+ */
+static void
+process_debits (void *cls);
+
+
+/**
+ * Go over the "wire_out" table of the exchange and
+ * verify that all wire outs are in that table.
+ *
+ * @param wa wire account we are processing
+ */
+static void
+check_exchange_wire_out (struct WireAccount *wa)
+{
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_assert (NULL == wa->dhh);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Analyzing exchange's wire OUT table for account `%s'\n",
+ wa->ai->section_name);
+ qs = TALER_ARL_edb->select_wire_out_above_serial_id_by_account (
+ TALER_ARL_edb->cls,
+ wa->ai->section_name,
+ wa->pp.last_wire_out_serial_id,
+ &wire_out_cb,
+ wa);
+ if (0 > qs)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ GNUNET_CONTAINER_multihashmap_iterate (out_map,
+ &complain_out_not_found,
+ wa);
+ /* clean up */
+ GNUNET_CONTAINER_multihashmap_iterate (out_map,
+ &free_roi,
+ NULL);
+ process_debits (wa->next);
+}
+
+
+/**
+ * This function is called for all transactions that
+ * are debited from the exchange's account (outgoing
+ * transactions).
+ *
+ * @param cls `struct WireAccount` with current wire account to process
+ * @param dhr HTTP response details
+ */
+static void
+history_debit_cb (void *cls,
+ const struct TALER_BANK_DebitHistoryResponse *dhr)
+{
+ struct WireAccount *wa = cls;
+ struct ReserveOutInfo *roi;
+ size_t slen;
+ // struct TALER_AUDITORDB_WireFormatInconsistency wfi;
+
+ wa->dhh = NULL;
+ switch (dhr->http_status)
+ {
+ case MHD_HTTP_OK:
+ for (unsigned int i = 0; i < dhr->details.ok.details_length; i++)
+ {
+ const struct TALER_BANK_DebitDetails *dd
+ = &dhr->details.ok.details[i];
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Analyzing bank DEBIT at %s of %s with WTID %s\n",
+ GNUNET_TIME_timestamp2s (dd->execution_date),
+ TALER_amount2s (&dd->amount),
+ TALER_B2S (&dd->wtid));
+ /* Update offset */
+ wa->wire_off_out = dd->serial_id;
+ slen = strlen (dd->credit_account_uri) + 1;
+ roi = GNUNET_malloc (sizeof (struct ReserveOutInfo)
+ + slen);
+ GNUNET_CRYPTO_hash (&dd->wtid,
+ sizeof (dd->wtid),
+ &roi->subject_hash);
+ roi->details.amount = dd->amount;
+ roi->details.execution_date = dd->execution_date;
+ roi->details.wtid = dd->wtid;
+ roi->details.credit_account_uri = (const char *) &roi[1];
+ GNUNET_memcpy (&roi[1],
+ dd->credit_account_uri,
+ slen);
+ if (GNUNET_OK !=
+ GNUNET_CONTAINER_multihashmap_put (out_map,
+ &roi->subject_hash,
+ roi,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
+ {
+ char *diagnostic;
+
+ GNUNET_asprintf (&diagnostic,
+ "duplicate subject hash `%s'",
+ TALER_B2S (&roi->subject_hash));
+ TALER_ARL_amount_add (&total_wire_format_amount,
+ &total_wire_format_amount,
+ &dd->amount);
+#if FIXME
+ wfi.diagnostic = diagnostic;
+ wfi.amount = &dd->amount;
+ wfi.wire_offset = dd->serial_id;
+
+ qs = TALER_ARL_adb->insert_wire_format_inconsistency (
+ TALER_ARL_adb->cls,
+ &wfi);
+
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ }
+ TALER_ARL_report (report_wire_format_inconsistencies,
+ GNUNET_JSON_PACK (
+ TALER_JSON_pack_amount ("amount",
+ &dd->amount),
+ GNUNET_JSON_pack_uint64 ("wire_offset",
+ dd->serial_id),
+ GNUNET_JSON_pack_string ("diagnostic",
+ diagnostic)));
+#endif
+ GNUNET_free (diagnostic);
+ }
+ }
+ check_exchange_wire_out (wa);
+ return;
+ case MHD_HTTP_NO_CONTENT:
+ check_exchange_wire_out (wa);
+ return;
+ case MHD_HTTP_NOT_FOUND:
+ if (ignore_account_404)
+ {
+ check_exchange_wire_out (wa);
+ return;
+ }
+ break;
+ default:
+ break;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Error fetching debit history of account %s: %u/%u!\n",
+ wa->ai->section_name,
+ dhr->http_status,
+ (unsigned int) dhr->ec);
+ commit (GNUNET_DB_STATUS_HARD_ERROR);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+}
+
+
+/**
+ * Main function for processing 'reserves_out' data. We start by going over
+ * the DEBIT transactions this time, and then verify that all of them are
+ * justified by 'reserves_out'.
+ *
+ * @param cls `struct WireAccount` with a wire account list to process
+ */
+static void
+process_debits (void *cls)
+{
+ struct WireAccount *wa = cls;
+
+ /* skip accounts where DEBIT is not enabled */
+ while ((NULL != wa) &&
+ (GNUNET_NO == wa->ai->debit_enabled))
+ wa = wa->next;
+ if (NULL == wa)
+ {
+ /* end of iteration, now check wire_out to see
+ if it matches #out_map */
+ conclude_wire_out ();
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Checking bank DEBIT records of account `%s'\n",
+ wa->ai->section_name);
+ GNUNET_assert (NULL == wa->dhh);
+ // FIXME: handle the case where more than INT32_MAX transactions exist.
+ // (CG: used to be INT64_MAX, changed by MS to INT32_MAX, why? To be discussed with him!)
+ wa->dhh = TALER_BANK_debit_history (ctx,
+ wa->ai->auth,
+ wa->wire_off_out,
+ INT32_MAX,
+ GNUNET_TIME_UNIT_ZERO,
+ &history_debit_cb,
+ wa);
+ if (NULL == wa->dhh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to obtain bank transaction history for `%s'\n",
+ wa->ai->section_name);
+ commit (GNUNET_DB_STATUS_HARD_ERROR);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+}
+
+
+/**
+ * Begin analyzing wire_out.
+ */
+static void
+begin_debit_audit (void)
+{
+ GNUNET_assert (NULL == out_map);
+ out_map = GNUNET_CONTAINER_multihashmap_create (1024,
+ true);
+ process_debits (wa_head);
+}
+
+
+/* ***************************** Analyze reserves_in ************************ */
+
+/**
+ * Conclude the credit history check by logging entries that
+ * were not found and freeing resources. Then move on to
+ * processing debits.
+ */
+static void
+conclude_credit_history (void)
+{
+ if (NULL != in_map)
+ {
+ GNUNET_CONTAINER_multihashmap_destroy (in_map);
+ in_map = NULL;
+ }
+ /* credit done, now check debits */
+ begin_debit_audit ();
+}
+
+
+/**
+ * Function called with details about incoming wire transfers
+ * as claimed by the exchange DB.
+ *
+ * @param cls a `struct WireAccount` we are processing
+ * @param rowid unique serial ID for the entry in our DB
+ * @param reserve_pub public key of the reserve (also the WTID)
+ * @param credit amount that was received
+ * @param sender_account_details payto://-URL of the sender's bank account
+ * @param wire_reference unique identifier for the wire transfer
+ * @param execution_date when did we receive the funds
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+static enum GNUNET_GenericReturnValue
+reserve_in_cb (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_Timestamp execution_date)
+{
+ struct WireAccount *wa = cls;
+ struct ReserveInInfo *rii;
+ size_t slen;
+ // struct TALER_AUDITORDB_RowInconsistency ri;
+ // enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Analyzing exchange wire IN (%llu) at %s of %s with reserve_pub %s\n",
+ (unsigned long long) rowid,
+ GNUNET_TIME_timestamp2s (execution_date),
+ TALER_amount2s (credit),
+ TALER_B2S (reserve_pub));
+ TALER_ARL_amount_add (&total_wire_in,
+ &total_wire_in,
+ credit);
+ slen = strlen (sender_account_details) + 1;
+ rii = GNUNET_malloc (sizeof (struct ReserveInInfo) + slen);
+ rii->rowid = rowid;
+ rii->credit_details.type = TALER_BANK_CT_RESERVE;
+ rii->credit_details.amount = *credit;
+ rii->credit_details.execution_date = execution_date;
+ rii->credit_details.details.reserve.reserve_pub = *reserve_pub;
+ rii->credit_details.debit_account_uri = (const char *) &rii[1];
+ GNUNET_memcpy (&rii[1],
+ sender_account_details,
+ slen);
+ GNUNET_CRYPTO_hash (&wire_reference,
+ sizeof (uint64_t),
+ &rii->row_off_hash);
+ if (GNUNET_OK !=
+ GNUNET_CONTAINER_multihashmap_put (in_map,
+ &rii->row_off_hash,
+ rii,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
+ {
+#if FIXME
+ ri.row_table = "reserves_in";
+ ri.diagnostic = "duplicate wire offset";
+
+ qs = TALER_ARL_adb->insert_row_inconsistency (
+ TALER_ARL_adb->cls,
+ &ri);
+
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ }
+ TALER_ARL_report (report_row_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("table",
+ "reserves_in"),
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ GNUNET_JSON_pack_data_auto ("id",
+ &rii->row_off_hash),
+ GNUNET_JSON_pack_string ("diagnostic",
+ "duplicate wire offset")));
+#endif
+ GNUNET_free (rii);
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
+ return GNUNET_OK;
+ }
+ wa->pp.last_reserve_in_serial_id = rowid + 1;
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
+ return GNUNET_OK;
+}
+
+
+/**
+ * Complain that we failed to match an entry from #in_map.
+ *
+ * @param cls a `struct WireAccount`
+ * @param key unused key
+ * @param value the `struct ReserveInInfo` to free
+ * @return #GNUNET_OK
+ */
+static enum GNUNET_GenericReturnValue
+complain_in_not_found (void *cls,
+ const struct GNUNET_HashCode *key,
+ void *value)
+{
+ // struct WireAccount *wa = cls;
+ struct ReserveInInfo *rii = value;
+ // enum GNUNET_DB_QueryStatus qs;
+ // struct TALER_AUDITORDB_ReserveInInconsistency riiDb;
+
+ (void) key;
+#if FIXME
+ riiDb.diagnostic = "incoming wire transfer claimed by exchange not found";
+ riiDb.account = (char *) wa->ai->section_name;
+ riiDb.amount_exchange_expected = &rii->details.amount;
+ riiDb.amount_wired = &zero;
+ riiDb.reserve_pub = &rii->details.reserve_pub;
+ riiDb.timestamp = rii->details.execution_date.abs_time;
+
+ qs = TALER_ARL_adb->insert_reserve_in_inconsistency (
+ TALER_ARL_adb->cls,
+ &riiDb);
+
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ }
+ GNUNET_assert (TALER_BANK_CT_RESERVE ==
+ rii->credit_details.type);
+ TALER_ARL_report (
+ report_reserve_in_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("row",
+ rii->rowid),
+ TALER_JSON_pack_amount ("amount_exchange_expected",
+ &rii->credit_details.amount),
+ TALER_JSON_pack_amount ("amount_wired",
+ &zero),
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ &rii->credit_details.reserve.reserve_pub),
+ TALER_JSON_pack_time_abs_human ("timestamp",
+ rii->credit_details.execution_date.
+ abs_time),
+ GNUNET_JSON_pack_string ("account",
+ wa->ai->section_name),
+ GNUNET_JSON_pack_string ("diagnostic",
+ "incoming wire transfer claimed by exchange not found")));
+#endif
+ TALER_ARL_amount_add (&total_bad_amount_in_minus,
+ &total_bad_amount_in_minus,
+ &rii->credit_details.amount);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Start processing the next wire account.
+ * Shuts down if we are done.
+ *
+ * @param cls `struct WireAccount` with a wire account list to process
+ */
+static void
+process_credits (void *cls);
+
+
+/**
+ * We got all of the incoming transactions for @a wa,
+ * finish processing the account.
+ *
+ * @param[in,out] wa wire account to process
+ */
+static void
+conclude_account (struct WireAccount *wa)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Reconciling CREDIT processing of account `%s'\n",
+ wa->ai->section_name);
+ if (NULL != in_map)
+ {
+ GNUNET_CONTAINER_multihashmap_iterate (in_map,
+ &complain_in_not_found,
+ wa);
+ /* clean up before 2nd phase */
+ GNUNET_CONTAINER_multihashmap_iterate (in_map,
+ &free_rii,
+ NULL);
+ }
+ process_credits (wa->next);
+}
+
+
+/**
+ * Analyze credit transaction @a details into @a wa.
+ *
+ * @param[in,out] wa account that received the transfer
+ * @param credit_details transfer details
+ * @return true on success, false to stop loop at this point
+ */
+static bool
+analyze_credit (
+ struct WireAccount *wa,
+ const struct TALER_BANK_CreditDetails *credit_details)
+{
+ struct ReserveInInfo *rii;
+ struct GNUNET_HashCode key;
+ // enum GNUNET_DB_QueryStatus qs;
+ /*struct TALER_AUDITORDB_ReserveInInconsistency riiDb;
+ struct TALER_AUDITORDB_ReserveInInconsistency riiDb2;
+ struct TALER_AUDITORDB_ReserveInInconsistency riiDb3;*/
+ // struct TALER_AUDITORDB_MisattributionInInconsistency mii;
+ // struct TALER_AUDITORDB_RowMinorInconsistencies rmi;
+
+ GNUNET_assert (TALER_BANK_CT_RESERVE ==
+ credit_details->type);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Analyzing bank CREDIT at %s of %s with Reserve-pub %s\n",
+ GNUNET_TIME_timestamp2s (credit_details->execution_date),
+ TALER_amount2s (&credit_details->amount),
+ TALER_B2S (&credit_details->details.reserve.reserve_pub));
+ GNUNET_CRYPTO_hash (&credit_details->serial_id,
+ sizeof (credit_details->serial_id),
+ &key);
+ rii = GNUNET_CONTAINER_multihashmap_get (in_map,
+ &key);
+ if (NULL == rii)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Failed to find wire transfer at `%s' in exchange database. Audit ends at this point in time.\n",
+ GNUNET_TIME_timestamp2s (credit_details->execution_date));
+ process_credits (wa->next);
+ return false; /* not an error, just end of processing */
+ }
+
+ /* Update offset */
+ wa->wire_off_in = credit_details->serial_id;
+ /* compare records with expected data */
+ if (0 != GNUNET_memcmp (&credit_details->details.reserve.reserve_pub,
+ &rii->credit_details.details.reserve.reserve_pub))
+ {
+#if FIXME
+ riiDb.diagnostic = "wire subject does not match";
+ riiDb.account = details->serial_id;
+ riiDb.amount_exchange_expected = &rii->credit_details.amount;
+ riiDb.amount_wired = &zero;
+ riiDb.reserve_pub = &rii->credit_details.detaisl.reserve.reserve_pub;
+ riiDb.timestamp = rii->credit_details.execution_date.abs_time;
+
+ qs = TALER_ARL_adb->insert_reserve_in_inconsistency (
+ TALER_ARL_adb->cls,
+ &riiDb);
+
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ }
+ TALER_ARL_report (
+ report_reserve_in_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("row",
+ rii->rowid),
+ GNUNET_JSON_pack_uint64 ("bank_row",
+ credit_details->serial_id),
+ TALER_JSON_pack_amount ("amount_exchange_expected",
+ &rii->credit_details.amount),
+ TALER_JSON_pack_amount ("amount_wired",
+ &zero),
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ &rii->credit_details.details.reserve.
+ reserve_pub),
+ TALER_JSON_pack_time_abs_human ("timestamp",
+ rii->credit_details.execution_date.
+ abs_time),
+ GNUNET_JSON_pack_string ("diagnostic",
+ "wire subject does not match")));
+#endif
+ TALER_ARL_amount_add (&total_bad_amount_in_minus,
+ &total_bad_amount_in_minus,
+ &rii->credit_details.amount);
+#if FIXME
+ riiDb2.diagnostic = "wire subject does not match";
+ riiDb2.account = credit_details->serial_id;
+ riiDb2.amount_exchange_expected = &rii->credit_details.amount;
+ riiDb2.amount_wired = &zero;
+ riiDb2.reserve_pub = &rii->credit_details.details.reserve.reserve_pub;
+ riiDb2.timestamp = rii->credit_details.execution_date.abs_time;
+
+ qs = TALER_ARL_adb->insert_reserve_in_inconsistency (
+ TALER_ARL_adb->cls,
+ &riiDb2);
+
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ }
+ TALER_ARL_report (
+ report_reserve_in_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("row",
+ rii->rowid),
+ GNUNET_JSON_pack_uint64 ("bank_row",
+ credit_details->serial_id),
+ TALER_JSON_pack_amount ("amount_exchange_expected",
+ &zero),
+ TALER_JSON_pack_amount ("amount_wired",
+ &credit_details->amount),
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ &credit_details->details.reserve.reserve_pub
+ ),
+ TALER_JSON_pack_time_abs_human ("timestamp",
+ credit_details->execution_date.abs_time)
+ ,
+ GNUNET_JSON_pack_string ("diagnostic",
+ "wire subject does not match")));
+#endif
+ TALER_ARL_amount_add (&total_bad_amount_in_plus,
+ &total_bad_amount_in_plus,
+ &credit_details->amount);
+ goto cleanup;
+ }
+ if (0 != TALER_amount_cmp (&rii->credit_details.amount,
+ &credit_details->amount))
+ {
+#if FIXME
+ riiDb3.diagnostic = "wire amount does not match";
+ riiDb3.account = credit_details->serial_id;
+ riiDb3.amount_exchange_expected = &rii->credit_details.amount;
+ riiDb3.amount_wired = &credit_details->amount;
+ riiDb3.reserve_pub = &rii->credit_details.details.reserve.reserve_pub;
+ riiDb3.timestamp = rii->credit_details.execution_date.abs_time;
+
+ qs = TALER_ARL_adb->insert_reserve_in_inconsistency (
+ TALER_ARL_adb->cls,
+ &riiDb3);
+
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ }
+ TALER_ARL_report (
+ report_reserve_in_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("row",
+ rii->rowid),
+ GNUNET_JSON_pack_uint64 ("bank_row",
+ credit_details->serial_id),
+ TALER_JSON_pack_amount ("amount_exchange_expected",
+ &rii->credit_details.amount),
+ TALER_JSON_pack_amount ("amount_wired",
+ &credit_details->amount),
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ &credit_details->details.reserve.reserve_pub
+ ),
+ TALER_JSON_pack_time_abs_human ("timestamp",
+ credit_details->execution_date.abs_time)
+ ,
+ GNUNET_JSON_pack_string ("diagnostic",
+ "wire amount does not match")));
+#endif
+ if (0 < TALER_amount_cmp (&credit_details->amount,
+ &rii->credit_details.amount))
+ {
+ /* details->amount > rii->details.amount: wire transfer was larger than it should have been */
+ struct TALER_Amount delta;
+
+ TALER_ARL_amount_subtract (&delta,
+ &credit_details->amount,
+ &rii->credit_details.amount);
+ TALER_ARL_amount_add (&total_bad_amount_in_plus,
+ &total_bad_amount_in_plus,
+ &delta);
+ }
+ else
+ {
+ /* rii->details.amount < details->amount: wire transfer was smaller than it should have been */
+ struct TALER_Amount delta;
+
+ TALER_ARL_amount_subtract (&delta,
+ &rii->credit_details.amount,
+ &credit_details->amount);
+ TALER_ARL_amount_add (&total_bad_amount_in_minus,
+ &total_bad_amount_in_minus,
+ &delta);
+ }
+ goto cleanup;
+ }
+ if (0 != strcasecmp (credit_details->debit_account_uri,
+ rii->credit_details.debit_account_uri))
+ {
+#if FIXME
+ mii.reserve_pub = &rii->credit_details.details.reserve.reserve_pub;
+ mii.amount = &rii->credit_details.amount;
+ mii.bank_row = credit_details->serial_id;
+
+ qs = TALER_ARL_adb->insert_misattribution_in_inconsistency (
+ TALER_ARL_adb->cls,
+ &mii);
+
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ }
+ TALER_ARL_report (report_misattribution_in_inconsistencies,
+ GNUNET_JSON_PACK (
+ TALER_JSON_pack_amount ("amount",
+ &rii->credit_details.amount),
+ GNUNET_JSON_pack_uint64 ("row",
+ rii->rowid),
+ GNUNET_JSON_pack_uint64 ("bank_row",
+ credit_details->serial_id),
+ GNUNET_JSON_pack_data_auto (
+ "reserve_pub",
+ &rii->credit_details.details.reserve.reserve_pub)));
+#endif
+ TALER_ARL_amount_add (&total_misattribution_in,
+ &total_misattribution_in,
+ &rii->credit_details.amount);
+ }
+ if (GNUNET_TIME_timestamp_cmp (credit_details->execution_date,
+ !=,
+ rii->credit_details.execution_date))
+ {
+#if FIXME
+ rmi.diagnostic = "execution date mismatch";
+ rmi.row_table = "reserves_in";
+
+ qs = TALER_ARL_adb->insert_row_minor_inconsistencies (
+ TALER_ARL_adb->cls,
+ &rmi);
+
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ }
+ TALER_ARL_report (report_row_minor_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("table",
+ "reserves_in"),
+ GNUNET_JSON_pack_uint64 ("row",
+ rii->rowid),
+ GNUNET_JSON_pack_uint64 ("bank_row",
+ credit_details->serial_id),
+ GNUNET_JSON_pack_string ("diagnostic",
+ "execution date mismatch")));
+#endif
+ }
+cleanup:
+ GNUNET_assert (GNUNET_OK ==
+ free_rii (NULL,
+ &key,
+ rii));
+ return true;
+}
+
+
+/**
+ * This function is called for all transactions that
+ * are credited to the exchange's account (incoming
+ * transactions).
+ *
+ * @param cls `struct WireAccount` we are processing
+ * @param chr HTTP response returned by the bank
+ */
+static void
+history_credit_cb (void *cls,
+ const struct TALER_BANK_CreditHistoryResponse *chr)
+{
+ struct WireAccount *wa = cls;
+
+ wa->chh = NULL;
+ switch (chr->http_status)
+ {
+ case MHD_HTTP_OK:
+ for (unsigned int i = 0; i < chr->details.ok.details_length; i++)
+ {
+ const struct TALER_BANK_CreditDetails *cd
+ = &chr->details.ok.details[i];
+
+ if (! analyze_credit (wa,
+ cd))
+ return;
+ }
+ conclude_account (wa);
+ return;
+ case MHD_HTTP_NO_CONTENT:
+ conclude_account (wa);
+ return;
+ case MHD_HTTP_NOT_FOUND:
+ if (ignore_account_404)
+ {
+ conclude_account (wa);
+ return;
+ }
+ break;
+ default:
+ break;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Error fetching credit history of account %s: %u/%s!\n",
+ wa->ai->section_name,
+ chr->http_status,
+ TALER_ErrorCode_get_hint (chr->ec));
+ commit (GNUNET_DB_STATUS_HARD_ERROR);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+}
+
+
+/* ***************************** Setup logic ************************ */
+
+
+/**
+ * Start processing the next wire account.
+ * Shuts down if we are done.
+ *
+ * @param cls `struct WireAccount` with a wire account list to process
+ */
+static void
+process_credits (void *cls)
+{
+ struct WireAccount *wa = cls;
+ enum GNUNET_DB_QueryStatus qs;
+
+ /* skip accounts where CREDIT is not enabled */
+ while ((NULL != wa) &&
+ (GNUNET_NO == wa->ai->credit_enabled))
+ wa = wa->next;
+ if (NULL == wa)
+ {
+ /* done with all accounts, conclude check */
+ conclude_credit_history ();
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Analyzing exchange's wire IN table for account `%s'\n",
+ wa->ai->section_name);
+ qs = TALER_ARL_edb->select_reserves_in_above_serial_id_by_account (
+ TALER_ARL_edb->cls,
+ wa->ai->section_name,
+ wa->pp.last_reserve_in_serial_id,
+ &reserve_in_cb,
+ wa);
+ if (0 > qs)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Starting bank CREDIT history of account `%s'\n",
+ wa->ai->section_name);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "user `%s'\n",
+ wa->ai->auth->details.basic.username);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "pass `%s'\n",
+ wa->ai->auth->details.basic.password);
+ // NOTE: handle the case where more than INT32_MAX transactions exist.
+ // (CG: used to be INT64_MAX, changed by MS to INT32_MAX, why? To be discussed with him!)
+ wa->chh = TALER_BANK_credit_history (ctx,
+ wa->ai->auth,
+ wa->wire_off_in,
+ INT32_MAX,
+ GNUNET_TIME_UNIT_ZERO,
+ &history_credit_cb,
+ wa);
+ if (NULL == wa->chh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to obtain bank transaction history\n");
+ commit (GNUNET_DB_STATUS_HARD_ERROR);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+}
+
+
+/**
+ * Begin audit of CREDITs to the exchange.
+ */
+static void
+begin_credit_audit (void)
+{
+ GNUNET_assert (NULL == in_map);
+ in_map = GNUNET_CONTAINER_multihashmap_create (1024,
+ GNUNET_YES);
+ /* now go over all bank accounts and check delta with in_map */
+ process_credits (wa_head);
+}
+
+
+/**
+ * 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, in payto://-format
+ * @param wtid identifier used for the wire transfer
+ * @param close_request_row which close request triggered the operation?
+ * 0 if it was a timeout (not used)
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+static enum GNUNET_GenericReturnValue
+reserve_closed_cb (void *cls,
+ uint64_t rowid,
+ struct GNUNET_TIME_Timestamp 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 *wtid,
+ uint64_t close_request_row)
+{
+ struct ReserveClosure *rc;
+ struct GNUNET_HashCode key;
+ // enum GNUNET_DB_QueryStatus qs;
+ // struct TALER_AUDITORDB_RowInconsistency ri;
+
+ (void) cls;
+ (void) close_request_row;
+ rc = GNUNET_new (struct ReserveClosure);
+ if (TALER_ARL_SR_INVALID_NEGATIVE ==
+ TALER_ARL_amount_subtract_neg (&rc->amount,
+ amount_with_fee,
+ closing_fee))
+ {
+#if FIXME
+// TODO fix, something seems not right
+ ri.row_table = "reserves_closures";
+ ri.diagnostic = "closing fee above total amount";
+
+ qs = TALER_ARL_adb->insert_row_inconsistency (
+ TALER_ARL_adb->cls,
+ &ri);
+
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ }
+ TALER_ARL_report (report_row_inconsistencies,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("table",
+ "reserves_closures"),
+ GNUNET_JSON_pack_uint64 ("row",
+ rowid),
+ GNUNET_JSON_pack_data_auto ("id",
+ reserve_pub),
+ TALER_JSON_pack_amount ("amount_with_fee",
+ amount_with_fee),
+ TALER_JSON_pack_amount ("closing_fee",
+ closing_fee),
+ GNUNET_JSON_pack_string ("diagnostic",
+ "closing fee above total amount")));
+#endif
+ GNUNET_free (rc);
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
+ return GNUNET_OK;
+ }
+ TALER_ARL_USE_PP (wire_reserve_close_id)
+ = GNUNET_MAX (TALER_ARL_USE_PP (wire_reserve_close_id),
+ rowid + 1);
+ rc->receiver_account = GNUNET_strdup (receiver_account);
+ rc->wtid = *wtid;
+ rc->execution_date = execution_date;
+ rc->rowid = rowid;
+ hash_rc (receiver_account,
+ wtid,
+ &key);
+ (void) GNUNET_CONTAINER_multihashmap_put (reserve_closures,
+ &key,
+ rc,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
+ if (TALER_ARL_do_abort ())
+ return GNUNET_SYSERR;
+ return GNUNET_OK;
+}
+
+
+/**
+ * Start the database transactions and begin the audit.
+ *
+ * @return transaction status code
+ */
+static enum GNUNET_DB_QueryStatus
+begin_transaction (void)
+{
+ enum GNUNET_DB_QueryStatus qs;
+
+ if (GNUNET_SYSERR ==
+ TALER_ARL_edb->preflight (TALER_ARL_edb->cls))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to initialize exchange database connection.\n");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (GNUNET_SYSERR ==
+ TALER_ARL_adb->preflight (TALER_ARL_adb->cls))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to initialize auditor database session.\n");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (GNUNET_OK !=
+ TALER_ARL_adb->start (TALER_ARL_adb->cls))
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ TALER_ARL_edb->preflight (TALER_ARL_edb->cls);
+ if (GNUNET_OK !=
+ TALER_ARL_edb->start (TALER_ARL_edb->cls,
+ "wire auditor"))
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &total_bad_amount_out_plus));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &total_bad_amount_out_minus));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &total_bad_amount_in_plus));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &total_bad_amount_in_minus));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &total_misattribution_in));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &total_amount_lag));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &total_closure_amount_lag));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &total_wire_format_amount));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &zero));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &total_wire_in));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &total_wire_out));
+ qs = TALER_ARL_adb->get_balance (
+ TALER_ARL_adb->cls,
+ TALER_ARL_GET_AB (total_drained),
+ TALER_ARL_GET_AB (final_balance),
+ NULL);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return qs;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &TALER_ARL_USE_AB (total_drained)));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &TALER_ARL_USE_AB (final_balance)));
+ had_start_balance = false;
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ had_start_balance = true;
+ break;
+ }
+ for (struct WireAccount *wa = wa_head;
+ NULL != wa;
+ wa = wa->next)
+ {
+ GNUNET_asprintf (&wa->label_reserve_in_serial_id,
+ "wire-%s-%s",
+ wa->ai->section_name,
+ "reserve_in_serial_id");
+ GNUNET_asprintf (&wa->label_wire_out_serial_id,
+ "wire-%s-%s",
+ wa->ai->section_name,
+ "wire_out_serial_id");
+ GNUNET_asprintf (&wa->label_wire_off_in,
+ "wire-%s-%s",
+ wa->ai->section_name,
+ "wire_off_in");
+ GNUNET_asprintf (&wa->label_wire_off_out,
+ "wire-%s-%s",
+ wa->ai->section_name,
+ "wire_off_out");
+ wa->qsx = TALER_ARL_adb->get_auditor_progress (
+ TALER_ARL_adb->cls,
+ wa->label_reserve_in_serial_id,
+ &wa->pp.last_reserve_in_serial_id,
+ wa->label_wire_out_serial_id,
+ &wa->pp.last_wire_out_serial_id,
+ wa->label_wire_off_in,
+ &wa->wire_off_in,
+ wa->label_wire_off_out,
+ &wa->wire_off_out,
+ NULL);
+ if (0 > wa->qsx)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == wa->qsx);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ wa->start_pp = wa->pp;
+ }
+ qsx_gwap = TALER_ARL_adb->get_auditor_progress (
+ TALER_ARL_adb->cls,
+ TALER_ARL_GET_PP (wire_reserve_close_id),
+ TALER_ARL_GET_PP (wire_batch_deposit_id),
+ TALER_ARL_GET_PP (wire_aggregation_id),
+ NULL);
+ if (0 > qsx_gwap)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qsx_gwap);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qsx_gwap)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "First analysis of with wire auditor, starting audit from scratch\n");
+ }
+ else
+ {
+ if (TALER_ARL_USE_PP (wire_reserve_close_id) == 0)
+ had_start_progress = false;
+ else
+ had_start_progress = true;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Resuming wire audit at %llu / %llu / %llu\n",
+ (unsigned long long) TALER_ARL_USE_PP (wire_reserve_close_id),
+ (unsigned long long) TALER_ARL_USE_PP (wire_batch_deposit_id),
+ (unsigned long long) TALER_ARL_USE_PP (wire_aggregation_id));
+ }
+
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TALER_ARL_edb->select_reserve_closed_above_serial_id (
+ TALER_ARL_edb->cls,
+ TALER_ARL_USE_PP (wire_reserve_close_id),
+ &reserve_closed_cb,
+ NULL);
+ if (0 > qs)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ }
+ begin_credit_audit ();
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+}
+
+
+/**
+ * Function called with information about a wire account. Adds the
+ * account to our list for processing (if it is enabled and we can
+ * load the plugin).
+ *
+ * @param cls closure, NULL
+ * @param ai account information
+ */
+static void
+process_account_cb (void *cls,
+ const struct TALER_EXCHANGEDB_AccountInfo *ai)
+{
+ struct WireAccount *wa;
+
+ (void) cls;
+ if ((! ai->debit_enabled) &&
+ (! ai->credit_enabled))
+ return; /* not an active exchange account */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Found exchange account `%s'\n",
+ ai->section_name);
+ wa = GNUNET_new (struct WireAccount);
+ wa->ai = ai;
+ GNUNET_CONTAINER_DLL_insert (wa_head,
+ wa_tail,
+ wa);
+}
+
+
+/**
+ * Function called on events received from Postgres.
+ *
+ * @param cls closure, NULL
+ * @param extra additional event data provided
+ * @param extra_size number of bytes in @a extra
+ */
+static void
+db_notify (void *cls,
+ const void *extra,
+ size_t extra_size)
+{
+ (void) cls;
+ (void) extra;
+ (void) extra_size;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received notification to wake wire helper\n");
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ begin_transaction ())
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Audit failed\n");
+ GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ }
+
+
+}
+
+
+/**
+ * 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;
+ cfg = c;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Launching wire auditor\n");
+ if (GNUNET_OK !=
+ TALER_ARL_init (c))
+ {
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+
+ reserve_closures
+ = GNUNET_CONTAINER_multihashmap_create (1024,
+ GNUNET_NO);
+ if (NULL ==
+ (db_plugin = TALER_AUDITORDB_plugin_load (cfg)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to initialize DB subsystem\n");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (GNUNET_OK !=
+ db_plugin->preflight (db_plugin->cls))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to connect to database\n");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+
+ if (GNUNET_OK !=
+ TALER_config_get_amount (TALER_ARL_cfg,
+ "auditor",
+ "TINY_AMOUNT",
+ &tiny_amount))
+ {
+ global_ret = EXIT_NOTCONFIGURED;
+ return;
+ }
+ GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
+ NULL);
+ ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
+ &rc);
+ rc = GNUNET_CURL_gnunet_rc_create (ctx);
+ if (NULL == ctx)
+ {
+ GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+ reserve_closures = GNUNET_CONTAINER_multihashmap_create (1024,
+ GNUNET_NO);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &total_bad_amount_out_plus));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &total_bad_amount_out_minus));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &total_bad_amount_in_plus));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &total_bad_amount_in_minus));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &total_misattribution_in));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &total_amount_lag));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &total_closure_amount_lag));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &total_wire_format_amount));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &zero));
+ if (GNUNET_OK !=
+ TALER_EXCHANGEDB_load_accounts (TALER_ARL_cfg,
+ TALER_EXCHANGEDB_ALO_DEBIT
+ | TALER_EXCHANGEDB_ALO_CREDIT
+ | TALER_EXCHANGEDB_ALO_AUTHDATA))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "No bank accounts configured\n");
+ global_ret = EXIT_NOTCONFIGURED;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ TALER_EXCHANGEDB_find_accounts (&process_account_cb,
+ NULL);
+
+ {
+ struct GNUNET_DB_EventHeaderP es = {
+ .size = htons (sizeof (es)),
+ .type = htons (TALER_DBEVENT_EXCHANGE_AUDITOR_WAKE_HELPER_WIRE)
+ };
+
+ eh = db_plugin->event_listen (db_plugin->cls,
+ &es,
+ GNUNET_TIME_UNIT_FOREVER_REL,
+ &db_notify,
+ NULL);
+ GNUNET_assert (NULL != eh);
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ begin_transaction ())
+ {
+ GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+}
+
+
+/**
+ * The main function of the wire auditing tool. Checks that
+ * the exchange's records of wire transfers match that of
+ * the wire gateway.
+ *
+ * @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 ('I',
+ "ignore-not-found",
+ "continue, even if the bank account of the exchange was not found",
+ &ignore_account_404),
+ 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-wire",
+ gettext_noop (
+ "Audit exchange database for consistency with the bank's wire transfers"),
+ 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-wire.c */
diff --git a/src/auditor/taler-helper-auditor-wire.c b/src/auditor/taler-helper-auditor-wire-debit.c
index c2c56e7a0..c2c56e7a0 100644
--- a/src/auditor/taler-helper-auditor-wire.c
+++ b/src/auditor/taler-helper-auditor-wire-debit.c