diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | contrib/Makefile.am | 10 | ||||
-rw-r--r-- | contrib/auditor-report.tex.j2 | 86 | ||||
-rw-r--r-- | doc/Makefile.am | 1 | ||||
-rw-r--r-- | src/auditor/.gitignore | 1 | ||||
-rw-r--r-- | src/auditor/Makefile.am | 39 | ||||
-rw-r--r-- | src/auditor/report-lib.c | 25 | ||||
-rw-r--r-- | src/auditor/taler-auditor-dbinit.c | 16 | ||||
-rw-r--r-- | src/auditor/taler-auditor-exchange.c | 11 | ||||
-rw-r--r-- | src/auditor/taler-auditor.c | 5765 | ||||
-rwxr-xr-x | src/auditor/taler-auditor.in | 23 | ||||
-rw-r--r-- | src/auditor/taler-helper-auditor-aggregation.c | 10 | ||||
-rw-r--r-- | src/auditor/taler-helper-auditor-coins.c | 50 | ||||
-rw-r--r-- | src/auditor/taler-helper-auditor-deposits.c | 10 | ||||
-rwxr-xr-x | src/auditor/taler-helper-auditor-render.py (renamed from contrib/render.py) | 4 | ||||
-rw-r--r-- | src/auditor/taler-helper-auditor-reserves.c | 10 | ||||
-rw-r--r-- | src/auditor/taler-helper-auditor-wire.c | 63 | ||||
-rwxr-xr-x | src/auditor/test-auditor.sh | 21 | ||||
-rw-r--r-- | src/bank-lib/bank_api_credit.c | 1 | ||||
-rw-r--r-- | src/exchange/test_taler_exchange_httpd.conf | 8 |
20 files changed, 229 insertions, 5926 deletions
diff --git a/.gitignore b/.gitignore index 60f141749..09c0fe457 100644 --- a/.gitignore +++ b/.gitignore @@ -34,7 +34,6 @@ GTAGS *.swp src/lib/test_exchange_api doc/doxygen/doxygen_sqlite3.db -src/auditor/taler-auditor src/auditor/taler-auditor-dbinit src/auditor/taler-auditor-sign src/bank-lib/taler-fakebank-run diff --git a/contrib/Makefile.am b/contrib/Makefile.am index eb4ad312d..76443cbca 100644 --- a/contrib/Makefile.am +++ b/contrib/Makefile.am @@ -6,6 +6,8 @@ tosendir=$(pkgdatadir)/tos/en # English (en) ppendir=$(pkgdatadir)/pp/en +rdatadir=$(pkgdatadir) + tosen_DATA = \ tos/en/0.txt \ tos/en/0.pdf \ @@ -20,6 +22,9 @@ ppen_DATA = \ pp/en/0.xml \ pp/en/0.html +rdata_DATA = \ + auditor-report.tex.j2 + bin_SCRIPTS = \ taler-bank-manage-testing \ taler-exchange-revoke @@ -39,11 +44,10 @@ EXTRA_DIST = \ pp/tos.rst \ pp/conf.py \ pp/locale/pp/LC_MESSAGES/pp.po \ - auditor-report.tex.j2 \ + $(data_DATA) \ coverage.sh \ gnunet.tag \ - microhttpd.tag \ - render.py + microhttpd.tag # Change the set of supported languages here. You should # also update tos'XX'data and EXTRA_DIST accordingly. diff --git a/contrib/auditor-report.tex.j2 b/contrib/auditor-report.tex.j2 index ccb249dd3..d826de446 100644 --- a/contrib/auditor-report.tex.j2 +++ b/contrib/auditor-report.tex.j2 @@ -467,9 +467,49 @@ the financial damage done to the customer). Note that the deltas only sum up the issues where $P \not= 0$ as only then we can tell if the problem lead to a profit or loss. +The {\bf P} colum is set to "1" if the arithmetic problem was be determined to be +profitable for the exchange, "-1" if the problem resulted in a net loss for +the exchange, and "0" if this is unclear or at least the gain/loss is not +easily determined from the amounts and thus not included in the totals. + +\subsubsection{For aggregation} + +% Table generation tested by testcase #XX in test-auditor.sh + +{% if aggregation.amount_arithmetic_inconsistencies|length() == 0 %} + {\bf No arithmetic problems detected.} +{% else %} + \begin{longtable}{p{3.5cm}|r|r|r|c} + {\bf Operation} & {\bf Row} & {\bf Exchange} & {\bf Auditor} & {\bf P} \\ + \hline \hline +\endfirsthead + {\bf Operation} & {\bf Row} & {\bf Exchange} & {\bf Auditor} & {\bf P} \\ \hline \hline +\endhead + \hline \hline + {\bf Operation} & {\bf Row} & {\bf Exchange} & {\bf Auditor} & {\bf P} \\ +\endfoot + \hline \hline + \multicolumn{2}{l|}{ {\bf $\sum$ Deltas (Auditor-Exchange)} } & + + {{ aggregation.total_arithmetic_delta_plus }} & + - {{ aggregation.total_arithmetic_delta_minus }} & \\ + \caption{Arithmetic inconsistencies.} + \label{table:amount:arithmetic:inconsistencies:aggregation} +\endlastfoot +{% for item in aggregation.amount_arithmetic_inconsistencies %} + \truncate{3.3cm}{ {\tiny {{ item.operation }} } } & + {{ item.rowid }} & + {{ item.exchange }} & + {{ item.auditor }} & + {{ item.profitable }} \\ \hline +{% endfor %} + \end{longtable} +{% endif %} + +\subsubsection{For coins} + % Table generation tested by testcase #18 in test-auditor.sh -{% if data.amount_arithmetic_inconsistencies|length() == 0 %} +{% if coins.amount_arithmetic_inconsistencies|length() == 0 %} {\bf No arithmetic problems detected.} {% else %} \begin{longtable}{p{3.5cm}|r|r|r|c} @@ -483,12 +523,12 @@ then we can tell if the problem lead to a profit or loss. \endfoot \hline \hline \multicolumn{2}{l|}{ {\bf $\sum$ Deltas (Auditor-Exchange)} } & - + {{ data.total_arithmetic_delta_plus }} & - - {{ data.total_arithmetic_delta_minus }} & \\ + + {{ coins.total_arithmetic_delta_plus }} & + - {{ coins.total_arithmetic_delta_minus }} & \\ \caption{Arithmetic inconsistencies.} - \label{table:amount:arithmetic:inconsistencies} + \label{table:amount:arithmetic:inconsistencies:coins} \endlastfoot -{% for item in data.amount_arithmetic_inconsistencies %} +{% for item in coins.amount_arithmetic_inconsistencies %} \truncate{3.3cm}{ {\tiny {{ item.operation }} } } & {{ item.rowid }} & {{ item.exchange }} & @@ -496,11 +536,39 @@ then we can tell if the problem lead to a profit or loss. {{ item.profitable }} \\ \hline {% endfor %} \end{longtable} +{% endif %} -The {\bf P} colum is set to "1" if the arithmetic problem was be determined to be -profitable for the exchange, "-1" if the problem resulted in a net loss for -the exchange, and "0" if this is unclear or at least the gain/loss is not -easily determined from the amounts and thus not included in the totals. +\subsubsection{For reserves} + +% Table generation tested by testcase #XX in test-auditor.sh + +{% if reserves.amount_arithmetic_inconsistencies|length() == 0 %} + {\bf No arithmetic problems detected.} +{% else %} + \begin{longtable}{p{3.5cm}|r|r|r|c} + {\bf Operation} & {\bf Row} & {\bf Exchange} & {\bf Auditor} & {\bf P} \\ + \hline \hline +\endfirsthead + {\bf Operation} & {\bf Row} & {\bf Exchange} & {\bf Auditor} & {\bf P} \\ \hline \hline +\endhead + \hline \hline + {\bf Operation} & {\bf Row} & {\bf Exchange} & {\bf Auditor} & {\bf P} \\ +\endfoot + \hline \hline + \multicolumn{2}{l|}{ {\bf $\sum$ Deltas (Auditor-Exchange)} } & + + {{ reserves.total_arithmetic_delta_plus }} & + - {{ reserves.total_arithmetic_delta_minus }} & \\ + \caption{Arithmetic inconsistencies.} + \label{table:amount:arithmetic:inconsistencies:reserves} +\endlastfoot +{% for item in reserves.amount_arithmetic_inconsistencies %} + \truncate{3.3cm}{ {\tiny {{ item.operation }} } } & + {{ item.rowid }} & + {{ item.exchange }} & + {{ item.auditor }} & + {{ item.profitable }} \\ \hline +{% endfor %} + \end{longtable} {% endif %} diff --git a/doc/Makefile.am b/doc/Makefile.am index 72d540210..119c8046a 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -4,6 +4,7 @@ SUBDIRS = . doxygen man_MANS = \ prebuilt/man/taler-auditor.1 \ + prebuilt/man/taler-auditor-dbinit.1 \ prebuilt/man/taler-auditor-exchange.1 \ prebuilt/man/taler-auditor-sign.1 \ prebuilt/man/taler-bank-transfer.1 \ diff --git a/src/auditor/.gitignore b/src/auditor/.gitignore index 95757b5f8..4532fbe2b 100644 --- a/src/auditor/.gitignore +++ b/src/auditor/.gitignore @@ -1,6 +1,5 @@ taler-auditor-httpd taler-auditor -taler-wire-auditor taler-auditor-exchange test-report.aux test-report.pdf diff --git a/src/auditor/Makefile.am b/src/auditor/Makefile.am index 13410a16b..bfcb8dc9c 100644 --- a/src/auditor/Makefile.am +++ b/src/auditor/Makefile.am @@ -12,16 +12,27 @@ pkgcfg_DATA = \ auditor.conf bin_PROGRAMS = \ - taler-auditor \ - taler-helper-auditor-reserves \ - taler-helper-auditor-coins \ - taler-helper-auditor-aggregation \ - taler-helper-auditor-deposits \ - taler-helper-auditor-wire \ + taler-auditor-dbinit \ taler-auditor-exchange \ taler-auditor-httpd \ taler-auditor-sign \ - taler-auditor-dbinit + taler-helper-auditor-aggregation \ + taler-helper-auditor-coins \ + taler-helper-auditor-deposits \ + taler-helper-auditor-reserves \ + taler-helper-auditor-wire + +bin_SCRIPTS = \ + taler-auditor \ + taler-helper-auditor-render.py + +edit_script = $(SED) -e 's,%pkgdatadir%,$(pkgdatadir),'g $(NULL) + +taler-auditor: taler-auditor.in + rm -f $@ $@.tmp && \ + $(edit_script) $< >$@.tmp && \ + chmod a-w+x $@.tmp && \ + mv $@.tmp $@ noinst_LIBRARIES = \ libauditor.a @@ -116,20 +127,6 @@ taler_helper_auditor_wire_LDADD = \ -lgnunetutil -taler_auditor_SOURCES = \ - taler-auditor.c -taler_auditor_LDADD = \ - $(LIBGCRYPT_LIBS) \ - $(top_builddir)/src/util/libtalerutil.la \ - $(top_builddir)/src/json/libtalerjson.la \ - $(top_builddir)/src/bank-lib/libtalerbank.la \ - $(top_builddir)/src/exchangedb/libtalerexchangedb.la \ - $(top_builddir)/src/auditordb/libtalerauditordb.la \ - -ljansson \ - -lgnunetjson \ - -lgnunetutil - - taler_auditor_httpd_SOURCES = \ taler-auditor-httpd.c taler-auditor-httpd.h \ taler-auditor-httpd_deposit-confirmation.c taler-auditor-httpd_deposit-confirmation.h \ diff --git a/src/auditor/report-lib.c b/src/auditor/report-lib.c index 128ec6256..d6db597ec 100644 --- a/src/auditor/report-lib.c +++ b/src/auditor/report-lib.c @@ -22,11 +22,6 @@ #include "report-lib.h" /** - * Command-line option "-r": TALER_ARL_restart audit from scratch - */ -int TALER_ARL_restart; - -/** * Handle to access the exchange's database. */ struct TALER_EXCHANGEDB_Plugin *TALER_ARL_edb; @@ -518,26 +513,6 @@ TALER_ARL_init (const struct GNUNET_CONFIGURATION_Handle *c) TALER_EXCHANGEDB_plugin_unload (TALER_ARL_edb); return GNUNET_SYSERR; } - if (TALER_ARL_restart) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Full audit TALER_ARL_restart requested, dropping old audit data.\n"); - GNUNET_break (GNUNET_OK == - TALER_ARL_adb->drop_tables (TALER_ARL_adb->cls, - GNUNET_NO)); - TALER_AUDITORDB_plugin_unload (TALER_ARL_adb); - if (NULL == - (TALER_ARL_adb = TALER_AUDITORDB_plugin_load (TALER_ARL_cfg))) - { - fprintf (stderr, - "Failed to initialize auditor database plugin after drop.\n"); - TALER_EXCHANGEDB_plugin_unload (TALER_ARL_edb); - return GNUNET_SYSERR; - } - GNUNET_break (GNUNET_OK == - TALER_ARL_adb->create_tables (TALER_ARL_adb->cls)); - } - return GNUNET_OK; } diff --git a/src/auditor/taler-auditor-dbinit.c b/src/auditor/taler-auditor-dbinit.c index 162ad5589..bbf3b14b6 100644 --- a/src/auditor/taler-auditor-dbinit.c +++ b/src/auditor/taler-auditor-dbinit.c @@ -30,7 +30,12 @@ static int global_ret; /** - * -r option: do full DB reset + * -r option: do restart audits + */ +static int restart_db; + +/** + * -R option: do full DB reset */ static int reset_db; @@ -71,6 +76,11 @@ run (void *cls, (void) plugin->drop_tables (plugin->cls, GNUNET_YES); } + else if (restart_db) + { + (void) plugin->drop_tables (plugin->cls, + GNUNET_NO); + } if (GNUNET_OK != plugin->create_tables (plugin->cls)) { @@ -104,6 +114,10 @@ main (int argc, { const struct GNUNET_GETOPT_CommandLineOption options[] = { GNUNET_GETOPT_option_flag ('r', + "restart", + "restart audits (DANGEROUS: all audits resume from scratch)", + &restart_db), + GNUNET_GETOPT_option_flag ('R', "reset", "reset database (DANGEROUS: all existing data is lost!)", &reset_db), diff --git a/src/auditor/taler-auditor-exchange.c b/src/auditor/taler-auditor-exchange.c index e97fd64df..7e105bc69 100644 --- a/src/auditor/taler-auditor-exchange.c +++ b/src/auditor/taler-auditor-exchange.c @@ -197,12 +197,11 @@ main (int argc, } if (0 == qs) { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - (remove_flag) - ? - "Could not remove exchange from auditor database: entry already absent\n" - : - "Could not add exchange to auditor database: entry already existed\n"); + GNUNET_log ( + GNUNET_ERROR_TYPE_WARNING, + (remove_flag) + ? "Could not remove exchange from database: entry already absent\n" + : "Could not add exchange to database: entry already exists\n"); TALER_AUDITORDB_plugin_unload (adb); return 4; } diff --git a/src/auditor/taler-auditor.c b/src/auditor/taler-auditor.c deleted file mode 100644 index dedf828f1..000000000 --- a/src/auditor/taler-auditor.c +++ /dev/null @@ -1,5765 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2016-2020 Taler Systems SA - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU Affero Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU Affero Public License for more details. - - You should have received a copy of the GNU Affero Public License along with - TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> -*/ -/** - * @file auditor/taler-auditor.c - * @brief audits an exchange database. - * @author Christian Grothoff - * - * README-FIRST: - * - * This code is being split up into - * taler-auditor-{aggregation,coins,deposits,reserves}. It is still here as a - * reference, but this file should be obsolete once the split has been - * completed. DO NOT EDIT THIS FILE ANYMORE! -CG - * - * NOTE: - * - This auditor does not verify that 'reserves_in' actually matches - * the wire transfers from the bank. This needs to be checked separately! - * - Similarly, we do not check that the outgoing wire transfers match those - * given in the 'wire_out' table. This needs to be checked separately! - * - * TODO: - * - reorganize: different passes are combined in one tool and one - * file here, we should split this up! - * - likely should do an iteration over known_coins instead of checking - * those signatures again and again - * - might want to bite the bullet and do asynchronous signature - * verification to improve parallelism / speed -- we'll need to scale - * this eventually anyway! - * - * UNDECIDED: - * - do we care about checking the 'done' flag in deposit_cb? - */ -#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" - - -/** - * How many coin histories do we keep in RAM at any given point in - * time? Used bound memory consumption of the auditor. Larger values - * reduce database accesses. - * - * Set to a VERY low value here for testing. Practical values may be - * in the millions. - */ -#define MAX_COIN_SUMMARIES 4 - -/** - * Use a 1 day grace period to deal with clocks not being perfectly synchronized. - */ -#define DEPOSIT_GRACE_PERIOD GNUNET_TIME_UNIT_DAYS - -/** - * Use a 1 day grace period to deal with clocks not being perfectly synchronized. - */ -#define CLOSING_GRACE_PERIOD GNUNET_TIME_UNIT_DAYS - -/** - * Return value from main(). - */ -static int global_ret; - -/** - * Command-line option "-r": restart audit from scratch - */ -static int restart; - -/** - * Handle to access the exchange's database. - */ -static struct TALER_EXCHANGEDB_Plugin *edb; - -/** - * Which currency are we doing the audit for? - */ -static char *currency; - -/** - * How many fractional digits does the currency use? - */ -static struct TALER_Amount currency_round_unit; - -/** - * Our configuration. - */ -static const struct GNUNET_CONFIGURATION_Handle *cfg; - -/** - * Our session with the #edb. - */ -static struct TALER_EXCHANGEDB_Session *esession; - -/** - * Handle to access the auditor's database. - */ -static struct TALER_AUDITORDB_Plugin *adb; - -/** - * Our session with the #adb. - */ -static struct TALER_AUDITORDB_Session *asession; - -/** - * After how long should idle reserves be closed? - */ -static struct GNUNET_TIME_Relative idle_reserve_expiration_time; - -/** - * Master public key of the exchange to audit. - */ -static struct TALER_MasterPublicKeyP master_pub; - -/** - * Checkpointing our progress for reserves. - */ -static struct TALER_AUDITORDB_ProgressPointReserve ppr; - -/** - * Checkpointing our progress for aggregations. - */ -static struct TALER_AUDITORDB_ProgressPointAggregation ppa; - -/** - * Checkpointing our progress for coins. - */ -static struct TALER_AUDITORDB_ProgressPointCoin ppc; - -/** - * Checkpointing our progress for reserves. - */ -static struct TALER_AUDITORDB_ProgressPointReserve ppr_start; - -/** - * Checkpointing our progress for aggregations. - */ -static struct TALER_AUDITORDB_ProgressPointAggregation ppa_start; - -/** - * Checkpointing our progress for coins. - */ -static struct TALER_AUDITORDB_ProgressPointCoin ppc_start; - -/** - * Array of reports about denomination keys with an - * emergency (more value deposited than withdrawn) - */ -static json_t *report_emergencies; - -/** - * Array of reports about denomination keys with an - * emergency (more coins deposited than withdrawn) - */ -static json_t *report_emergencies_by_count; - -/** - * Array of reports about row inconsitencies. - */ -static json_t *report_row_inconsistencies; - -/** - * Array of reports about the denomination key not being - * valid at the time of withdrawal. - */ -static json_t *denomination_key_validity_withdraw_inconsistencies; - -/** - * Array of reports about reserve balance insufficient inconsitencies. - */ -static json_t *report_reserve_balance_insufficient_inconsistencies; - -/** - * Total amount reserves were charged beyond their balance. - */ -static struct TALER_Amount total_balance_insufficient_loss; - -/** - * Array of reports about reserve balance summary wrong in database. - */ -static json_t *report_reserve_balance_summary_wrong_inconsistencies; - -/** - * Total delta between expected and stored reserve balance summaries, - * for positive deltas. - */ -static struct TALER_Amount total_balance_summary_delta_plus; - -/** - * Total delta between expected and stored reserve balance summaries, - * for negative deltas. - */ -static struct TALER_Amount total_balance_summary_delta_minus; - -/** - * Array of reports about reserve's not being closed inconsitencies. - */ -static json_t *report_reserve_not_closed_inconsistencies; - -/** - * Total amount affected by reserves not having been closed on time. - */ -static struct TALER_Amount total_balance_reserve_not_closed; - -/** - * Array of reports about irregular wire out entries. - */ -static json_t *report_wire_out_inconsistencies; - -/** - * Array of reports about missing deposit confirmations. - */ -static json_t *report_deposit_confirmation_inconsistencies; - -/** - * Total delta between calculated and stored wire out transfers, - * for positive deltas. - */ -static struct TALER_Amount total_wire_out_delta_plus; - -/** - * Total delta between calculated and stored wire out transfers - * for negative deltas. - */ -static struct TALER_Amount total_wire_out_delta_minus; - -/** - * Array of reports about inconsistencies about coins. - */ -static json_t *report_coin_inconsistencies; - -/** - * Profits the exchange made by bad amount calculations on coins. - */ -static struct TALER_Amount total_coin_delta_plus; - -/** - * Losses the exchange made by bad amount calculations on coins. - */ -static struct TALER_Amount total_coin_delta_minus; - -/** - * Report about aggregate wire transfer fee profits. - */ -static json_t *report_aggregation_fee_balances; - -/** - * Report about amount calculation differences (causing profit - * or loss at the exchange). - */ -static json_t *report_amount_arithmetic_inconsistencies; - -/** - * Array of reports about wire fees being ambiguous in terms of validity periods. - */ -static json_t *report_fee_time_inconsistencies; - -/** - * Profits the exchange made by bad amount calculations. - */ -static struct TALER_Amount total_arithmetic_delta_plus; - -/** - * Losses the exchange made by bad amount calculations. - */ -static struct TALER_Amount total_arithmetic_delta_minus; - -/** - * Total number of deposit confirmations that we did not get. - */ -static json_int_t number_missed_deposit_confirmations; - -/** - * Total amount involved in deposit confirmations that we did not get. - */ -static struct TALER_Amount total_missed_deposit_confirmations; - -/** - * Total amount reported in all calls to #report_emergency_by_count(). - */ -static struct TALER_Amount reported_emergency_risk_by_count; - -/** - * Total amount reported in all calls to #report_emergency_by_amount(). - */ -static struct TALER_Amount reported_emergency_risk_by_amount; - -/** - * Total amount in losses reported in all calls to #report_emergency_by_amount(). - */ -static struct TALER_Amount reported_emergency_loss; - -/** - * Total amount in losses reported in all calls to #report_emergency_by_count(). - */ -static struct TALER_Amount reported_emergency_loss_by_count; - -/** - * Expected balance in the escrow account. - */ -static struct TALER_Amount total_escrow_balance; - -/** - * Active risk exposure. - */ -static struct TALER_Amount total_risk; - -/** - * Actualized risk (= loss) from recoups. - */ -static struct TALER_Amount total_recoup_loss; - -/** - * Recoups we made on denominations that were not revoked (!?). - */ -static struct TALER_Amount total_irregular_recoups; - -/** - * Total withdraw fees earned. - */ -static struct TALER_Amount total_withdraw_fee_income; - -/** - * Total deposit fees earned. - */ -static struct TALER_Amount total_deposit_fee_income; - -/** - * Total melt fees earned. - */ -static struct TALER_Amount total_melt_fee_income; - -/** - * Total refund fees earned. - */ -static struct TALER_Amount total_refund_fee_income; - -/** - * Total aggregation fees earned. - */ -static struct TALER_Amount total_aggregation_fee_income; - -/** - * Array of reports about coin operations with bad signatures. - */ -static json_t *report_bad_sig_losses; - -/** - * Total amount lost by operations for which signatures were invalid. - */ -static struct TALER_Amount total_bad_sig_loss; - -/** - * Array of refresh transactions where the /refresh/reveal has not yet - * happened (and may of course never happen). - */ -static json_t *report_refreshs_hanging; - -/** - * Total amount lost by operations for which signatures were invalid. - */ -static struct TALER_Amount total_refresh_hanging; - -/** - * At what time did the auditor process start? - */ -static struct GNUNET_TIME_Absolute start_time; - - -/* ********************************* helpers *************************** */ - -/** - * Convert absolute time to human-readable JSON string. - * - * @param at time to convert - * @return human-readable string representing the time - */ -static json_t * -json_from_time_abs_nbo (struct GNUNET_TIME_AbsoluteNBO at) -{ - return json_string - (GNUNET_STRINGS_absolute_time_to_string - (GNUNET_TIME_absolute_ntoh (at))); -} - - -/** - * Convert absolute time to human-readable JSON string. - * - * @param at time to convert - * @return human-readable string representing the time - */ -static json_t * -json_from_time_abs (struct GNUNET_TIME_Absolute at) -{ - return json_string - (GNUNET_STRINGS_absolute_time_to_string (at)); -} - - -/* ***************************** Report logic **************************** */ - - -/** - * Add @a object to the report @a array. Fail hard if this fails. - * - * @param array report array to append @a object to - * @param object object to append, should be check that it is not NULL - */ -static void -report (json_t *array, - json_t *object) -{ - GNUNET_assert (NULL != object); - GNUNET_assert (0 == - json_array_append_new (array, - object)); -} - - -/** - * Called in case we detect an emergency situation where the exchange - * is paying out a larger amount on a denomination than we issued in - * that denomination. This means that the exchange's private keys - * might have gotten compromised, and that we need to trigger an - * emergency request to all wallets to deposit pending coins for the - * denomination (and as an exchange suffer a huge financial loss). - * - * @param issue denomination key where the loss was detected - * @param risk maximum risk that might have just become real (coins created by this @a issue) - * @param loss actual losses already (actualized before denomination was revoked) - */ -static void -report_emergency_by_amount (const struct TALER_DenominationKeyValidityPS *issue, - const struct TALER_Amount *risk, - const struct TALER_Amount *loss) -{ - report (report_emergencies, - json_pack ("{s:o, s:o, s:o, s:o, s:o, s:o}", - "denompub_hash", - GNUNET_JSON_from_data_auto (&issue->denom_hash), - "denom_risk", - TALER_JSON_from_amount (risk), - "denom_loss", - TALER_JSON_from_amount (loss), - "start", - json_from_time_abs_nbo (issue->start), - "deposit_end", - json_from_time_abs_nbo (issue->expire_deposit), - "value", - TALER_JSON_from_amount_nbo (&issue->value))); - GNUNET_assert (GNUNET_OK == - TALER_amount_add (&reported_emergency_risk_by_amount, - &reported_emergency_risk_by_amount, - risk)); - GNUNET_assert (GNUNET_OK == - TALER_amount_add (&reported_emergency_loss, - &reported_emergency_loss, - loss)); -} - - -/** - * Called in case we detect an emergency situation where the exchange - * is paying out a larger NUMBER of coins of a denomination than we - * issued in that denomination. This means that the exchange's - * private keys might have gotten compromised, and that we need to - * trigger an emergency request to all wallets to deposit pending - * coins for the denomination (and as an exchange suffer a huge - * financial loss). - * - * @param issue denomination key where the loss was detected - * @param num_issued number of coins that were issued - * @param num_known number of coins that have been deposited - * @param risk amount that is at risk - */ -static void -report_emergency_by_count (const struct TALER_DenominationKeyValidityPS *issue, - uint64_t num_issued, - uint64_t num_known, - const struct TALER_Amount *risk) -{ - struct TALER_Amount denom_value; - - report (report_emergencies_by_count, - json_pack ("{s:o, s:I, s:I, s:o, s:o, s:o, s:o}", - "denompub_hash", - GNUNET_JSON_from_data_auto (&issue->denom_hash), - "num_issued", - (json_int_t) num_issued, - "num_known", - (json_int_t) num_known, - "denom_risk", - TALER_JSON_from_amount (risk), - "start", - json_from_time_abs_nbo (issue->start), - "deposit_end", - json_from_time_abs_nbo (issue->expire_deposit), - "value", - TALER_JSON_from_amount_nbo (&issue->value))); - GNUNET_assert (GNUNET_OK == - TALER_amount_add (&reported_emergency_risk_by_count, - &reported_emergency_risk_by_count, - risk)); - TALER_amount_ntoh (&denom_value, - &issue->value); - for (uint64_t i = num_issued; i<num_known; i++) - GNUNET_assert (GNUNET_OK == - TALER_amount_add (&reported_emergency_loss_by_count, - &reported_emergency_loss_by_count, - &denom_value)); - -} - - -/** - * Report a (serious) inconsistency in the exchange's database with - * respect to calculations involving amounts. - * - * @param operation what operation had the inconsistency - * @param rowid affected row, UINT64_MAX if row is missing - * @param exchange amount calculated by exchange - * @param auditor amount calculated by auditor - * @param profitable 1 if @a exchange being larger than @a auditor is - * profitable for the exchange for this operation, - * -1 if @a exchange being smaller than @a auditor is - * profitable for the exchange, and 0 if it is unclear - */ -static void -report_amount_arithmetic_inconsistency (const char *operation, - uint64_t rowid, - const struct TALER_Amount *exchange, - const struct TALER_Amount *auditor, - int profitable) -{ - struct TALER_Amount delta; - struct TALER_Amount *target; - - if (0 < TALER_amount_cmp (exchange, - auditor)) - { - /* exchange > auditor */ - GNUNET_break (GNUNET_OK == - TALER_amount_subtract (&delta, - exchange, - auditor)); - } - else - { - /* auditor < exchange */ - profitable = -profitable; - GNUNET_break (GNUNET_OK == - TALER_amount_subtract (&delta, - auditor, - exchange)); - } - report (report_amount_arithmetic_inconsistencies, - json_pack ("{s:s, s:I, s:o, s:o, s:I}", - "operation", operation, - "rowid", (json_int_t) rowid, - "exchange", TALER_JSON_from_amount (exchange), - "auditor", TALER_JSON_from_amount (auditor), - "profitable", (json_int_t) profitable)); - if (0 != profitable) - { - target = (1 == profitable) - ? &total_arithmetic_delta_plus - : &total_arithmetic_delta_minus; - GNUNET_break (GNUNET_OK == - TALER_amount_add (target, - target, - &delta)); - } -} - - -/** - * Report a (serious) inconsistency in the exchange's database with - * respect to calculations involving amounts of a coin. - * - * @param operation what operation had the inconsistency - * @param coin_pub affected coin - * @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_coin_arithmetic_inconsistency (const char *operation, - const struct - TALER_CoinSpendPublicKeyP *coin_pub, - const struct TALER_Amount *exchange, - const struct TALER_Amount *auditor, - int profitable) -{ - struct TALER_Amount delta; - struct TALER_Amount *target; - - if (0 < TALER_amount_cmp (exchange, - auditor)) - { - /* exchange > auditor */ - GNUNET_break (GNUNET_OK == - TALER_amount_subtract (&delta, - exchange, - auditor)); - } - else - { - /* auditor < exchange */ - profitable = -profitable; - GNUNET_break (GNUNET_OK == - TALER_amount_subtract (&delta, - auditor, - exchange)); - } - report (report_coin_inconsistencies, - json_pack ("{s:s, s:o, s:o, s:o, s:I}", - "operation", operation, - "coin_pub", GNUNET_JSON_from_data_auto (coin_pub), - "exchange", TALER_JSON_from_amount (exchange), - "auditor", TALER_JSON_from_amount (auditor), - "profitable", (json_int_t) profitable)); - if (0 != profitable) - { - target = (1 == profitable) - ? &total_coin_delta_plus - : &total_coin_delta_minus; - GNUNET_break (GNUNET_OK == - TALER_amount_add (target, - target, - &delta)); - } -} - - -/** - * Report a (serious) inconsistency in the exchange's database. - * - * @param table affected table - * @param rowid affected row, UINT64_MAX if row is missing - * @param diagnostic message explaining the problem - */ -static void -report_row_inconsistency (const char *table, - uint64_t rowid, - const char *diagnostic) -{ - report (report_row_inconsistencies, - json_pack ("{s:s, s:I, s:s}", - "table", table, - "row", (json_int_t) rowid, - "diagnostic", diagnostic)); -} - - -/* ************************* Transaction-global state ************************ */ - -/** - * Results about denominations, cached per-transaction, maps denomination pub hashes - * to `struct TALER_DenominationKeyValidityPS`. - */ -static struct GNUNET_CONTAINER_MultiHashMap *denominations; - - -/** - * Function called with the results of select_denomination_info() - * - * @param cls closure, NULL - * @param issue issuing information with value, fees and other info about the denomination. - * @return #GNUNET_OK (to continue) - */ -static int -add_denomination (void *cls, - const struct TALER_DenominationKeyValidityPS *issue) -{ - struct TALER_DenominationKeyValidityPS *i; - - (void) cls; - if (NULL != - GNUNET_CONTAINER_multihashmap_get (denominations, - &issue->denom_hash)) - return GNUNET_OK; /* value already known */ - { - struct TALER_Amount value; - - TALER_amount_ntoh (&value, - &issue->value); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Tracking denomination `%s' (%s)\n", - GNUNET_h2s (&issue->denom_hash), - TALER_amount2s (&value)); - TALER_amount_ntoh (&value, - &issue->fee_withdraw); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Withdraw fee is %s\n", - TALER_amount2s (&value)); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Start time is %s\n", - GNUNET_STRINGS_absolute_time_to_string - (GNUNET_TIME_absolute_ntoh (issue->start))); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Expire deposit time is %s\n", - GNUNET_STRINGS_absolute_time_to_string - (GNUNET_TIME_absolute_ntoh (issue->expire_deposit))); - } - i = GNUNET_new (struct TALER_DenominationKeyValidityPS); - *i = *issue; - GNUNET_assert (GNUNET_OK == - GNUNET_CONTAINER_multihashmap_put (denominations, - &issue->denom_hash, - i, - GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); - return GNUNET_OK; -} - - -/** - * Obtain information about a @a denom_pub. - * - * @param dh hash of the denomination public key to look up - * @param[out] issue set to detailed information about @a denom_pub, NULL if not found, must - * NOT be freed by caller - * @return transaction status code - */ -static enum GNUNET_DB_QueryStatus -get_denomination_info_by_hash (const struct GNUNET_HashCode *dh, - const struct - TALER_DenominationKeyValidityPS **issue) -{ - const struct TALER_DenominationKeyValidityPS *i; - enum GNUNET_DB_QueryStatus qs; - - if (NULL == denominations) - { - denominations = GNUNET_CONTAINER_multihashmap_create (256, - GNUNET_NO); - qs = adb->select_denomination_info (adb->cls, - asession, - &master_pub, - &add_denomination, - NULL); - if (0 > qs) - { - *issue = NULL; - return qs; - } - } - i = GNUNET_CONTAINER_multihashmap_get (denominations, - dh); - if (NULL != i) - { - /* cache hit */ - *issue = i; - return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; - } - /* maybe database changed since we last iterated, give it one more shot */ - qs = adb->select_denomination_info (adb->cls, - asession, - &master_pub, - &add_denomination, - NULL); - if (qs <= 0) - { - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Denomination %s not found\n", - TALER_B2S (dh)); - return qs; - } - i = GNUNET_CONTAINER_multihashmap_get (denominations, - dh); - if (NULL != i) - { - /* cache hit */ - *issue = i; - return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; - } - /* We found more keys, but not the denomination we are looking for :-( */ - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Denomination %s not found\n", - TALER_B2S (dh)); - return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; -} - - -/** - * Obtain information about a @a denom_pub. - * - * @param denom_pub key to look up - * @param[out] issue set to detailed information about @a denom_pub, NULL if not found, must - * NOT be freed by caller - * @param[out] dh set to the hash of @a denom_pub, may be NULL - * @return transaction status code - */ -static enum GNUNET_DB_QueryStatus -get_denomination_info (const struct TALER_DenominationPublicKey *denom_pub, - const struct - TALER_DenominationKeyValidityPS **issue, - struct GNUNET_HashCode *dh) -{ - struct GNUNET_HashCode hc; - - if (NULL == dh) - dh = &hc; - GNUNET_CRYPTO_rsa_public_key_hash (denom_pub->rsa_public_key, - dh); - return get_denomination_info_by_hash (dh, - issue); -} - - -/** - * Free denomination key information. - * - * @param cls NULL - * @param key unused - * @param value the `struct TALER_EXCHANGEDB_DenominationKeyInformationP *` to free - * @return #GNUNET_OK (continue to iterate) - */ -static int -free_dk_info (void *cls, - const struct GNUNET_HashCode *key, - void *value) -{ - struct TALER_DenominationKeyValidityPS *issue = value; - - (void) cls; - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Done with denomination `%s'\n", - GNUNET_h2s (key)); - GNUNET_free (issue); - return GNUNET_OK; -} - - -/** - * Purge transaction global state cache, the transaction is - * done and we do not want to have the state cross over to - * the next transaction. - */ -static void -clear_transaction_state_cache () -{ - if (NULL == denominations) - return; - GNUNET_CONTAINER_multihashmap_iterate (denominations, - &free_dk_info, - NULL); - GNUNET_CONTAINER_multihashmap_destroy (denominations); - denominations = NULL; -} - - -/* ***************************** Analyze reserves ************************ */ -/* This logic checks the reserves_in, reserves_out and reserves-tables */ - -/** - * Summary data we keep per reserve. - */ -struct ReserveSummary -{ - /** - * Public key of the reserve. - * Always set when the struct is first initialized. - */ - struct TALER_ReservePublicKeyP reserve_pub; - - /** - * Sum of all incoming transfers during this transaction. - * Updated only in #handle_reserve_in(). - */ - struct TALER_Amount total_in; - - /** - * Sum of all outgoing transfers during this transaction (includes fees). - * Updated only in #handle_reserve_out(). - */ - struct TALER_Amount total_out; - - /** - * Sum of withdraw fees encountered during this transaction. - */ - struct TALER_Amount total_fee; - - /** - * Previous balance of the reserve as remembered by the auditor. - * (updated based on @e total_in and @e total_out at the end). - */ - struct TALER_Amount a_balance; - - /** - * Previous withdraw fee balance of the reserve, as remembered by the auditor. - * (updated based on @e total_fee at the end). - */ - struct TALER_Amount a_withdraw_fee_balance; - - /** - * Previous reserve expiration data, as remembered by the auditor. - * (updated on-the-fly in #handle_reserve_in()). - */ - struct GNUNET_TIME_Absolute a_expiration_date; - - /** - * Which account did originally put money into the reserve? - */ - char *sender_account; - - /** - * Did we have a previous reserve info? Used to decide between - * UPDATE and INSERT later. Initialized in - * #load_auditor_reserve_summary() together with the a-* values - * (if available). - */ - int had_ri; - -}; - - -/** - * Load the auditor's remembered state about the reserve into @a rs. - * The "total_in" and "total_out" amounts of @a rs must already be - * initialized (so we can determine the currency). - * - * @param[in,out] rs reserve summary to (fully) initialize - * @return transaction status code - */ -static enum GNUNET_DB_QueryStatus -load_auditor_reserve_summary (struct ReserveSummary *rs) -{ - enum GNUNET_DB_QueryStatus qs; - uint64_t rowid; - - qs = adb->get_reserve_info (adb->cls, - asession, - &rs->reserve_pub, - &master_pub, - &rowid, - &rs->a_balance, - &rs->a_withdraw_fee_balance, - &rs->a_expiration_date, - &rs->sender_account); - if (0 > qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - return qs; - } - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) - { - rs->had_ri = GNUNET_NO; - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (rs->total_in.currency, - &rs->a_balance)); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (rs->total_in.currency, - &rs->a_withdraw_fee_balance)); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Creating fresh reserve `%s' with starting balance %s\n", - TALER_B2S (&rs->reserve_pub), - TALER_amount2s (&rs->a_balance)); - return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; - } - rs->had_ri = GNUNET_YES; - if ( (GNUNET_YES != - TALER_amount_cmp_currency (&rs->a_balance, - &rs->a_withdraw_fee_balance)) || - (GNUNET_YES != - TALER_amount_cmp_currency (&rs->total_in, - &rs->a_balance)) ) - { - GNUNET_break (0); - return GNUNET_DB_STATUS_HARD_ERROR; - } - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Auditor remembers reserve `%s' has balance %s\n", - TALER_B2S (&rs->reserve_pub), - TALER_amount2s (&rs->a_balance)); - return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; -} - - -/** - * Closure to the various callbacks we make while checking a reserve. - */ -struct ReserveContext -{ - /** - * Map from hash of reserve's public key to a `struct ReserveSummary`. - */ - struct GNUNET_CONTAINER_MultiHashMap *reserves; - - /** - * Map from hash of denomination's public key to a - * static string "revoked" for keys that have been revoked, - * or "master signature invalid" in case the revocation is - * there but bogus. - */ - struct GNUNET_CONTAINER_MultiHashMap *revoked; - - /** - * Transaction status code, set to error codes if applicable. - */ - enum GNUNET_DB_QueryStatus qs; - -}; - - -/** - * Function called with details about incoming wire transfers. - * - * @param cls our `struct ReserveContext` - * @param rowid unique serial ID for the refresh session in our DB - * @param reserve_pub public key of the reserve (also the WTID) - * @param credit amount that was received - * @param sender_account_details information about the sender's bank account - * @param wire_reference unique reference identifying the wire transfer - * @param execution_date when did we receive the funds - * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop - */ -static int -handle_reserve_in (void *cls, - uint64_t rowid, - const struct TALER_ReservePublicKeyP *reserve_pub, - const struct TALER_Amount *credit, - const char *sender_account_details, - uint64_t wire_reference, - struct GNUNET_TIME_Absolute execution_date) -{ - struct ReserveContext *rc = cls; - struct GNUNET_HashCode key; - struct ReserveSummary *rs; - struct GNUNET_TIME_Absolute expiry; - enum GNUNET_DB_QueryStatus qs; - - (void) wire_reference; - /* should be monotonically increasing */ - GNUNET_assert (rowid >= ppr.last_reserve_in_serial_id); - ppr.last_reserve_in_serial_id = rowid + 1; - - GNUNET_CRYPTO_hash (reserve_pub, - sizeof (*reserve_pub), - &key); - rs = GNUNET_CONTAINER_multihashmap_get (rc->reserves, - &key); - if (NULL == rs) - { - rs = GNUNET_new (struct ReserveSummary); - rs->sender_account = GNUNET_strdup (sender_account_details); - rs->reserve_pub = *reserve_pub; - rs->total_in = *credit; - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (credit->currency, - &rs->total_out)); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (credit->currency, - &rs->total_fee)); - if (0 > (qs = load_auditor_reserve_summary (rs))) - { - GNUNET_break (0); - GNUNET_free (rs); - rc->qs = qs; - return GNUNET_SYSERR; - } - GNUNET_assert (GNUNET_OK == - GNUNET_CONTAINER_multihashmap_put (rc->reserves, - &key, - rs, - GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); - } - else - { - GNUNET_assert (GNUNET_OK == - TALER_amount_add (&rs->total_in, - &rs->total_in, - credit)); - if (NULL == rs->sender_account) - rs->sender_account = GNUNET_strdup (sender_account_details); - } - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Additional incoming wire transfer for reserve `%s' of %s\n", - TALER_B2S (reserve_pub), - TALER_amount2s (credit)); - expiry = GNUNET_TIME_absolute_add (execution_date, - idle_reserve_expiration_time); - rs->a_expiration_date = GNUNET_TIME_absolute_max (rs->a_expiration_date, - expiry); - return GNUNET_OK; -} - - -/** - * Function called with details about withdraw operations. Verifies - * the signature and updates the reserve's balance. - * - * @param cls our `struct ReserveContext` - * @param rowid unique serial ID for the refresh session in our DB - * @param h_blind_ev blinded hash of the coin's public key - * @param denom_pub public denomination key of the deposited coin - * @param reserve_pub public key of the reserve - * @param reserve_sig signature over the withdraw operation - * @param execution_date when did the wallet withdraw the coin - * @param amount_with_fee amount that was withdrawn - * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop - */ -static int -handle_reserve_out (void *cls, - uint64_t rowid, - const struct GNUNET_HashCode *h_blind_ev, - const struct TALER_DenominationPublicKey *denom_pub, - const struct TALER_ReservePublicKeyP *reserve_pub, - const struct TALER_ReserveSignatureP *reserve_sig, - struct GNUNET_TIME_Absolute execution_date, - const struct TALER_Amount *amount_with_fee) -{ - struct ReserveContext *rc = cls; - struct TALER_WithdrawRequestPS wsrd; - struct GNUNET_HashCode key; - struct ReserveSummary *rs; - const struct TALER_DenominationKeyValidityPS *issue; - struct TALER_Amount withdraw_fee; - struct GNUNET_TIME_Absolute valid_start; - struct GNUNET_TIME_Absolute expire_withdraw; - enum GNUNET_DB_QueryStatus qs; - - /* should be monotonically increasing */ - GNUNET_assert (rowid >= ppr.last_reserve_out_serial_id); - ppr.last_reserve_out_serial_id = rowid + 1; - - /* lookup denomination pub data (make sure denom_pub is valid, establish fees) */ - qs = get_denomination_info (denom_pub, - &issue, - &wsrd.h_denomination_pub); - if (0 > qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - if (GNUNET_DB_STATUS_HARD_ERROR == qs) - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Hard database error trying to get denomination %s (%s) from database!\n", - TALER_B2S (denom_pub), - TALER_amount2s (amount_with_fee)); - rc->qs = qs; - return GNUNET_SYSERR; - } - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) - { - report_row_inconsistency ("withdraw", - rowid, - "denomination key not found"); - return GNUNET_OK; - } - - /* check that execution date is within withdraw range for denom_pub */ - valid_start = GNUNET_TIME_absolute_ntoh (issue->start); - expire_withdraw = GNUNET_TIME_absolute_ntoh (issue->expire_withdraw); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Checking withdraw timing: %llu, expire: %llu, timing: %llu\n", - (unsigned long long) valid_start.abs_value_us, - (unsigned long long) expire_withdraw.abs_value_us, - (unsigned long long) execution_date.abs_value_us); - if ( (valid_start.abs_value_us > execution_date.abs_value_us) || - (expire_withdraw.abs_value_us < execution_date.abs_value_us) ) - { - report (denomination_key_validity_withdraw_inconsistencies, - json_pack ("{s:I, s:o, s:o, s:o}", - "row", (json_int_t) rowid, - "execution_date", - json_from_time_abs (execution_date), - "reserve_pub", GNUNET_JSON_from_data_auto (reserve_pub), - "denompub_h", GNUNET_JSON_from_data_auto ( - &wsrd.h_denomination_pub))); - } - - /* check reserve_sig */ - wsrd.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW); - wsrd.purpose.size = htonl (sizeof (wsrd)); - wsrd.reserve_pub = *reserve_pub; - TALER_amount_hton (&wsrd.amount_with_fee, - amount_with_fee); - wsrd.withdraw_fee = issue->fee_withdraw; - wsrd.h_coin_envelope = *h_blind_ev; - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW, - &wsrd.purpose, - &reserve_sig->eddsa_signature, - &reserve_pub->eddsa_pub)) - { - report (report_bad_sig_losses, - json_pack ("{s:s, s:I, s:o, s:o}", - "operation", "withdraw", - "row", (json_int_t) rowid, - "loss", TALER_JSON_from_amount (amount_with_fee), - "key_pub", GNUNET_JSON_from_data_auto (reserve_pub))); - GNUNET_break (GNUNET_OK == - TALER_amount_add (&total_bad_sig_loss, - &total_bad_sig_loss, - amount_with_fee)); - return GNUNET_OK; - } - - GNUNET_CRYPTO_hash (reserve_pub, - sizeof (*reserve_pub), - &key); - rs = GNUNET_CONTAINER_multihashmap_get (rc->reserves, - &key); - if (NULL == rs) - { - rs = GNUNET_new (struct ReserveSummary); - rs->reserve_pub = *reserve_pub; - rs->total_out = *amount_with_fee; - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (amount_with_fee->currency, - &rs->total_in)); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (amount_with_fee->currency, - &rs->total_fee)); - qs = load_auditor_reserve_summary (rs); - if (0 > qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - GNUNET_free (rs); - rc->qs = qs; - return GNUNET_SYSERR; - } - GNUNET_assert (GNUNET_OK == - GNUNET_CONTAINER_multihashmap_put (rc->reserves, - &key, - rs, - GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); - } - else - { - GNUNET_assert (GNUNET_OK == - TALER_amount_add (&rs->total_out, - &rs->total_out, - amount_with_fee)); - } - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Reserve `%s' reduced by %s from withdraw\n", - TALER_B2S (reserve_pub), - TALER_amount2s (amount_with_fee)); - TALER_amount_ntoh (&withdraw_fee, - &issue->fee_withdraw); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Increasing withdraw profits by fee %s\n", - TALER_amount2s (&withdraw_fee)); - GNUNET_assert (GNUNET_OK == - TALER_amount_add (&rs->total_fee, - &rs->total_fee, - &withdraw_fee)); - - return GNUNET_OK; -} - - -/** - * Function called with details about withdraw operations. Verifies - * the signature and updates the reserve's balance. - * - * @param cls our `struct ReserveContext` - * @param rowid unique serial ID for the refresh session in our DB - * @param timestamp when did we receive the recoup request - * @param amount how much should be added back to the reserve - * @param reserve_pub public key of the reserve - * @param coin public information about the coin, denomination signature is - * already verified in #check_recoup() - * @param denom_pub public key of the denomionation of @a coin - * @param coin_sig signature with @e coin_pub of type #TALER_SIGNATURE_WALLET_COIN_RECOUP - * @param coin_blind blinding factor used to blind the coin - * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop - */ -static int -handle_recoup_by_reserve (void *cls, - uint64_t rowid, - struct GNUNET_TIME_Absolute timestamp, - const struct TALER_Amount *amount, - const struct TALER_ReservePublicKeyP *reserve_pub, - const struct TALER_CoinPublicInfo *coin, - const struct TALER_DenominationPublicKey *denom_pub, - const struct TALER_CoinSpendSignatureP *coin_sig, - const struct - TALER_DenominationBlindingKeyP *coin_blind) -{ - struct ReserveContext *rc = cls; - struct GNUNET_HashCode key; - struct ReserveSummary *rs; - struct GNUNET_TIME_Absolute expiry; - struct TALER_RecoupRequestPS pr; - struct TALER_MasterSignatureP msig; - uint64_t rev_rowid; - enum GNUNET_DB_QueryStatus qs; - const char *rev; - - (void) denom_pub; - /* should be monotonically increasing */ - GNUNET_assert (rowid >= ppr.last_reserve_recoup_serial_id); - ppr.last_reserve_recoup_serial_id = rowid + 1; - /* We know that denom_pub matches denom_pub_hash because this - is how the SQL statement joined the tables. */ - pr.h_denom_pub = coin->denom_pub_hash; - pr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_RECOUP); - pr.purpose.size = htonl (sizeof (pr)); - pr.coin_pub = coin->coin_pub; - pr.coin_blind = *coin_blind; - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_RECOUP, - &pr.purpose, - &coin_sig->eddsa_signature, - &coin->coin_pub.eddsa_pub)) - { - report (report_bad_sig_losses, - json_pack ("{s:s, s:I, s:o, s:o}", - "operation", "recoup", - "row", (json_int_t) rowid, - "loss", TALER_JSON_from_amount (amount), - "key_pub", GNUNET_JSON_from_data_auto ( - &coin->coin_pub))); - GNUNET_break (GNUNET_OK == - TALER_amount_add (&total_bad_sig_loss, - &total_bad_sig_loss, - amount)); - } - - /* check that the coin was eligible for recoup!*/ - rev = GNUNET_CONTAINER_multihashmap_get (rc->revoked, - &pr.h_denom_pub); - if (NULL == rev) - { - qs = edb->get_denomination_revocation (edb->cls, - esession, - &pr.h_denom_pub, - &msig, - &rev_rowid); - if (0 > qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - rc->qs = qs; - return GNUNET_SYSERR; - } - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) - { - report_row_inconsistency ("recoup", - rowid, - "denomination key not in revocation set"); - GNUNET_break (GNUNET_OK == - TALER_amount_add (&total_irregular_recoups, - &total_irregular_recoups, - amount)); - } - else - { - /* verify msig */ - struct TALER_MasterDenominationKeyRevocationPS kr; - - kr.purpose.purpose = htonl ( - TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED); - kr.purpose.size = htonl (sizeof (kr)); - kr.h_denom_pub = pr.h_denom_pub; - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify ( - TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED, - &kr.purpose, - &msig.eddsa_signature, - &master_pub.eddsa_pub)) - { - rev = "master signature invalid"; - } - else - { - rev = "revoked"; - } - GNUNET_assert (GNUNET_OK == - GNUNET_CONTAINER_multihashmap_put (rc->revoked, - &pr.h_denom_pub, - (void *) rev, - GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); - } - } - else - { - rev_rowid = 0; /* reported elsewhere */ - } - if ( (NULL != rev) && - (0 == strcmp (rev, "master signature invalid")) ) - { - report (report_bad_sig_losses, - json_pack ("{s:s, s:I, s:o, s:o}", - "operation", "recoup-master", - "row", (json_int_t) rev_rowid, - "loss", TALER_JSON_from_amount (amount), - "key_pub", GNUNET_JSON_from_data_auto (&master_pub))); - GNUNET_break (GNUNET_OK == - TALER_amount_add (&total_bad_sig_loss, - &total_bad_sig_loss, - amount)); - } - - GNUNET_CRYPTO_hash (reserve_pub, - sizeof (*reserve_pub), - &key); - rs = GNUNET_CONTAINER_multihashmap_get (rc->reserves, - &key); - if (NULL == rs) - { - rs = GNUNET_new (struct ReserveSummary); - rs->reserve_pub = *reserve_pub; - rs->total_in = *amount; - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (amount->currency, - &rs->total_out)); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (amount->currency, - &rs->total_fee)); - qs = load_auditor_reserve_summary (rs); - if (0 > qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - GNUNET_free (rs); - rc->qs = qs; - return GNUNET_SYSERR; - } - GNUNET_assert (GNUNET_OK == - GNUNET_CONTAINER_multihashmap_put (rc->reserves, - &key, - rs, - GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); - } - else - { - GNUNET_assert (GNUNET_OK == - TALER_amount_add (&rs->total_in, - &rs->total_in, - amount)); - } - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Additional /recoup value to for reserve `%s' of %s\n", - TALER_B2S (reserve_pub), - TALER_amount2s (amount)); - expiry = GNUNET_TIME_absolute_add (timestamp, - idle_reserve_expiration_time); - rs->a_expiration_date = GNUNET_TIME_absolute_max (rs->a_expiration_date, - expiry); - return GNUNET_OK; -} - - -/** - * Obtain the closing fee for a transfer at @a time for target - * @a receiver_account. - * - * @param receiver_account payto:// URI of the target account - * @param atime when was the transfer made - * @param[out] fee set to the closing fee - * @return #GNUNET_OK on success - */ -static int -get_closing_fee (const char *receiver_account, - struct GNUNET_TIME_Absolute atime, - struct TALER_Amount *fee) -{ - struct TALER_MasterSignatureP master_sig; - struct GNUNET_TIME_Absolute start_date; - struct GNUNET_TIME_Absolute end_date; - struct TALER_Amount wire_fee; - char *method; - - method = TALER_payto_get_method (receiver_account); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Method is `%s'\n", - method); - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != - edb->get_wire_fee (edb->cls, - esession, - method, - atime, - &start_date, - &end_date, - &wire_fee, - fee, - &master_sig)) - { - report_row_inconsistency ("closing-fee", - atime.abs_value_us, - "closing fee unavailable at given time"); - GNUNET_free (method); - return GNUNET_SYSERR; - } - GNUNET_free (method); - return GNUNET_OK; -} - - -/** - * Function called about reserve closing operations - * the aggregator triggered. - * - * @param cls closure - * @param rowid row identifier used to uniquely identify the reserve closing operation - * @param execution_date when did we execute the close operation - * @param amount_with_fee how much did we debit the reserve - * @param closing_fee how much did we charge for closing the reserve - * @param reserve_pub public key of the reserve - * @param receiver_account where did we send the funds - * @param transfer_details details about the wire transfer - * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop - */ -static int -handle_reserve_closed (void *cls, - uint64_t rowid, - struct GNUNET_TIME_Absolute execution_date, - const struct TALER_Amount *amount_with_fee, - const struct TALER_Amount *closing_fee, - const struct TALER_ReservePublicKeyP *reserve_pub, - const char *receiver_account, - const struct - TALER_WireTransferIdentifierRawP *transfer_details) -{ - struct ReserveContext *rc = cls; - struct GNUNET_HashCode key; - struct ReserveSummary *rs; - enum GNUNET_DB_QueryStatus qs; - - (void) transfer_details; - /* should be monotonically increasing */ - GNUNET_assert (rowid >= ppr.last_reserve_close_serial_id); - ppr.last_reserve_close_serial_id = rowid + 1; - - GNUNET_CRYPTO_hash (reserve_pub, - sizeof (*reserve_pub), - &key); - rs = GNUNET_CONTAINER_multihashmap_get (rc->reserves, - &key); - if (NULL == rs) - { - rs = GNUNET_new (struct ReserveSummary); - rs->reserve_pub = *reserve_pub; - rs->total_out = *amount_with_fee; - rs->total_fee = *closing_fee; - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (amount_with_fee->currency, - &rs->total_in)); - qs = load_auditor_reserve_summary (rs); - if (0 > qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - GNUNET_free (rs); - rc->qs = qs; - return GNUNET_SYSERR; - } - GNUNET_assert (GNUNET_OK == - GNUNET_CONTAINER_multihashmap_put (rc->reserves, - &key, - rs, - GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); - } - else - { - struct TALER_Amount expected_fee; - - GNUNET_assert (GNUNET_OK == - TALER_amount_add (&rs->total_out, - &rs->total_out, - amount_with_fee)); - GNUNET_assert (GNUNET_OK == - TALER_amount_add (&rs->total_fee, - &rs->total_fee, - closing_fee)); - /* verify closing_fee is correct! */ - if (GNUNET_OK != - get_closing_fee (receiver_account, - execution_date, - &expected_fee)) - { - GNUNET_break (0); - } - else if (0 != TALER_amount_cmp (&expected_fee, - closing_fee)) - { - report_amount_arithmetic_inconsistency ("closing aggregation fee", - rowid, - closing_fee, - &expected_fee, - 1); - } - } - if (NULL == rs->sender_account) - { - GNUNET_break (GNUNET_NO == rs->had_ri); - report_row_inconsistency ("reserves_close", - rowid, - "target account not verified, auditor does not know reserve"); - } - else if (0 != strcmp (rs->sender_account, - receiver_account)) - { - report_row_inconsistency ("reserves_close", - rowid, - "target account does not match origin account"); - } - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Additional closing operation for reserve `%s' of %s\n", - TALER_B2S (reserve_pub), - TALER_amount2s (amount_with_fee)); - return GNUNET_OK; -} - - -/** - * Check that the reserve summary matches what the exchange database - * thinks about the reserve, and update our own state of the reserve. - * - * Remove all reserves that we are happy with from the DB. - * - * @param cls our `struct ReserveContext` - * @param key hash of the reserve public key - * @param value a `struct ReserveSummary` - * @return #GNUNET_OK to process more entries - */ -static int -verify_reserve_balance (void *cls, - const struct GNUNET_HashCode *key, - void *value) -{ - struct ReserveContext *rc = cls; - struct ReserveSummary *rs = value; - struct TALER_EXCHANGEDB_Reserve reserve; - struct TALER_Amount balance; - struct TALER_Amount nbalance; - struct TALER_Amount cfee; - enum GNUNET_DB_QueryStatus qs; - int ret; - - ret = GNUNET_OK; - reserve.pub = rs->reserve_pub; - qs = edb->reserves_get (edb->cls, - esession, - &reserve); - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) - { - char *diag; - - GNUNET_asprintf (&diag, - "Failed to find summary for reserve `%s'\n", - TALER_B2S (&rs->reserve_pub)); - report_row_inconsistency ("reserve-summary", - UINT64_MAX, - diag); - GNUNET_free (diag); - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) - { - GNUNET_break (0); - qs = GNUNET_DB_STATUS_HARD_ERROR; - } - rc->qs = qs; - return GNUNET_OK; - } - - if (GNUNET_OK != - TALER_amount_add (&balance, - &rs->total_in, - &rs->a_balance)) - { - GNUNET_break (0); - goto cleanup; - } - - if (GNUNET_SYSERR == - TALER_amount_subtract (&nbalance, - &balance, - &rs->total_out)) - { - struct TALER_Amount loss; - - GNUNET_break (GNUNET_SYSERR != - TALER_amount_subtract (&loss, - &rs->total_out, - &balance)); - GNUNET_break (GNUNET_OK == - TALER_amount_add (&total_balance_insufficient_loss, - &total_balance_insufficient_loss, - &loss)); - report (report_reserve_balance_insufficient_inconsistencies, - json_pack ("{s:o, s:o}", - "reserve_pub", - GNUNET_JSON_from_data_auto (&rs->reserve_pub), - "loss", - TALER_JSON_from_amount (&loss))); - goto cleanup; - } - if (0 != TALER_amount_cmp (&nbalance, - &reserve.balance)) - { - struct TALER_Amount delta; - - if (0 < TALER_amount_cmp (&nbalance, - &reserve.balance)) - { - /* balance > reserve.balance */ - GNUNET_assert (GNUNET_OK == - TALER_amount_subtract (&delta, - &nbalance, - &reserve.balance)); - GNUNET_assert (GNUNET_OK == - TALER_amount_add (&total_balance_summary_delta_plus, - &total_balance_summary_delta_plus, - &delta)); - } - else - { - /* balance < reserve.balance */ - GNUNET_assert (GNUNET_OK == - TALER_amount_subtract (&delta, - &reserve.balance, - &nbalance)); - GNUNET_assert (GNUNET_OK == - TALER_amount_add (&total_balance_summary_delta_minus, - &total_balance_summary_delta_minus, - &delta)); - } - report (report_reserve_balance_summary_wrong_inconsistencies, - json_pack ("{s:o, s:o, s:o}", - "reserve_pub", - GNUNET_JSON_from_data_auto (&rs->reserve_pub), - "exchange", - TALER_JSON_from_amount (&reserve.balance), - "auditor", - TALER_JSON_from_amount (&nbalance))); - goto cleanup; - } - - /* Check that reserve is being closed if it is past its expiration date */ - - if (CLOSING_GRACE_PERIOD.rel_value_us < - GNUNET_TIME_absolute_get_duration (rs->a_expiration_date).rel_value_us) - { - if ( (NULL != rs->sender_account) && - (GNUNET_OK == - get_closing_fee (rs->sender_account, - rs->a_expiration_date, - &cfee)) ) - { - if (1 == TALER_amount_cmp (&nbalance, - &cfee)) - { - GNUNET_assert (GNUNET_OK == - TALER_amount_add (&total_balance_reserve_not_closed, - &total_balance_reserve_not_closed, - &nbalance)); - report (report_reserve_not_closed_inconsistencies, - json_pack ("{s:o, s:o, s:o}", - "reserve_pub", - GNUNET_JSON_from_data_auto (&rs->reserve_pub), - "balance", - TALER_JSON_from_amount (&nbalance), - "expiration_time", - json_from_time_abs (rs->a_expiration_date))); - } - } - else - { - GNUNET_assert (GNUNET_OK == - TALER_amount_add (&total_balance_reserve_not_closed, - &total_balance_reserve_not_closed, - &nbalance)); - report (report_reserve_not_closed_inconsistencies, - json_pack ("{s:o, s:o, s:o, s:s}", - "reserve_pub", - GNUNET_JSON_from_data_auto (&rs->reserve_pub), - "balance", - TALER_JSON_from_amount (&nbalance), - "expiration_time", - json_from_time_abs (rs->a_expiration_date), - "diagnostic", - "could not determine closing fee")); - } - } - - /* Add withdraw fees we encountered to totals */ - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Reserve reserve `%s' made %s in withdraw fees\n", - TALER_B2S (&rs->reserve_pub), - TALER_amount2s (&rs->total_fee)); - if (GNUNET_YES != - TALER_amount_add (&rs->a_withdraw_fee_balance, - &rs->a_withdraw_fee_balance, - &rs->total_fee)) - { - GNUNET_break (0); - ret = GNUNET_SYSERR; - goto cleanup; - } - if ( (GNUNET_YES != - TALER_amount_add (&total_escrow_balance, - &total_escrow_balance, - &rs->total_in)) || - (GNUNET_SYSERR == - TALER_amount_subtract (&total_escrow_balance, - &total_escrow_balance, - &rs->total_out)) || - (GNUNET_YES != - TALER_amount_add (&total_withdraw_fee_income, - &total_withdraw_fee_income, - &rs->total_fee)) ) - { - GNUNET_break (0); - ret = GNUNET_SYSERR; - goto cleanup; - } - - if ( (0ULL == balance.value) && - (0U == balance.fraction) ) - { - /* balance is zero, drop reserve details (and then do not update/insert) */ - if (rs->had_ri) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Final balance of reserve `%s' is %s, dropping it\n", - TALER_B2S (&rs->reserve_pub), - TALER_amount2s (&nbalance)); - qs = adb->del_reserve_info (adb->cls, - asession, - &rs->reserve_pub, - &master_pub); - if (0 >= qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - ret = GNUNET_SYSERR; - rc->qs = qs; - goto cleanup; - } - } - else - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Final balance of reserve `%s' is %s, no need to remember it\n", - TALER_B2S (&rs->reserve_pub), - TALER_amount2s (&nbalance)); - } - ret = GNUNET_OK; - goto cleanup; - } - - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Remembering final balance of reserve `%s' as %s\n", - TALER_B2S (&rs->reserve_pub), - TALER_amount2s (&nbalance)); - - if (rs->had_ri) - qs = adb->update_reserve_info (adb->cls, - asession, - &rs->reserve_pub, - &master_pub, - &nbalance, - &rs->a_withdraw_fee_balance, - rs->a_expiration_date); - else - qs = adb->insert_reserve_info (adb->cls, - asession, - &rs->reserve_pub, - &master_pub, - &nbalance, - &rs->a_withdraw_fee_balance, - rs->a_expiration_date, - rs->sender_account); - if (0 >= qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - ret = GNUNET_SYSERR; - rc->qs = qs; - } -cleanup: - GNUNET_assert (GNUNET_YES == - GNUNET_CONTAINER_multihashmap_remove (rc->reserves, - key, - rs)); - GNUNET_free_non_null (rs->sender_account); - GNUNET_free (rs); - return ret; -} - - -/** - * Analyze reserves for being well-formed. - * - * @param cls NULL - * @return transaction status code - */ -static enum GNUNET_DB_QueryStatus -analyze_reserves (void *cls) -{ - struct ReserveContext rc; - enum GNUNET_DB_QueryStatus qsx; - enum GNUNET_DB_QueryStatus qs; - enum GNUNET_DB_QueryStatus qsp; - - (void) cls; - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Analyzing reserves\n"); - qsp = adb->get_auditor_progress_reserve (adb->cls, - asession, - &master_pub, - &ppr); - if (0 > qsp) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qsp); - return qsp; - } - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qsp) - { - GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, - _ ( - "First analysis using this auditor, starting audit from scratch\n")); - } - else - { - ppr_start = ppr; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - _ ("Resuming reserve audit at %llu/%llu/%llu/%llu\n"), - (unsigned long long) ppr.last_reserve_in_serial_id, - (unsigned long long) ppr.last_reserve_out_serial_id, - (unsigned long long) ppr.last_reserve_recoup_serial_id, - (unsigned long long) ppr.last_reserve_close_serial_id); - } - rc.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; - qsx = adb->get_reserve_summary (adb->cls, - asession, - &master_pub, - &total_escrow_balance, - &total_withdraw_fee_income); - if (qsx < 0) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qsx); - return qsx; - } - rc.reserves = GNUNET_CONTAINER_multihashmap_create (512, - GNUNET_NO); - rc.revoked = GNUNET_CONTAINER_multihashmap_create (4, - GNUNET_NO); - - qs = edb->select_reserves_in_above_serial_id (edb->cls, - esession, - ppr.last_reserve_in_serial_id, - &handle_reserve_in, - &rc); - if (qs < 0) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - return qs; - } - qs = edb->select_withdrawals_above_serial_id (edb->cls, - esession, - ppr.last_reserve_out_serial_id, - &handle_reserve_out, - &rc); - if (qs < 0) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - return qs; - } - qs = edb->select_recoup_above_serial_id (edb->cls, - esession, - ppr.last_reserve_recoup_serial_id, - &handle_recoup_by_reserve, - &rc); - if (qs < 0) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - return qs; - } - qs = edb->select_reserve_closed_above_serial_id (edb->cls, - esession, - ppr. - last_reserve_close_serial_id, - &handle_reserve_closed, - &rc); - if (qs < 0) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - return qs; - } - - GNUNET_CONTAINER_multihashmap_iterate (rc.reserves, - &verify_reserve_balance, - &rc); - GNUNET_break (0 == - GNUNET_CONTAINER_multihashmap_size (rc.reserves)); - GNUNET_CONTAINER_multihashmap_destroy (rc.reserves); - GNUNET_CONTAINER_multihashmap_destroy (rc.revoked); - - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != rc.qs) - return qs; - - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qsx) - { - qs = adb->insert_reserve_summary (adb->cls, - asession, - &master_pub, - &total_escrow_balance, - &total_withdraw_fee_income); - } - else - { - qs = adb->update_reserve_summary (adb->cls, - asession, - &master_pub, - &total_escrow_balance, - &total_withdraw_fee_income); - } - if (0 >= qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - return qs; - } - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qsp) - qs = adb->update_auditor_progress_reserve (adb->cls, - asession, - &master_pub, - &ppr); - else - qs = adb->insert_auditor_progress_reserve (adb->cls, - asession, - &master_pub, - &ppr); - if (0 >= qs) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Failed to update auditor DB, not recording progress\n"); - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - return qs; - } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - _ ("Concluded reserve audit step at %llu/%llu/%llu/%llu\n"), - (unsigned long long) ppr.last_reserve_in_serial_id, - (unsigned long long) ppr.last_reserve_out_serial_id, - (unsigned long long) ppr.last_reserve_recoup_serial_id, - (unsigned long long) ppr.last_reserve_close_serial_id); - return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; -} - - -/* *********************** Analyze aggregations ******************** */ -/* This logic checks that the aggregator did the right thing - paying each merchant what they were due (and on time). */ - - -/** - * Information about wire fees charged by the exchange. - */ -struct WireFeeInfo -{ - - /** - * Kept in a DLL. - */ - struct WireFeeInfo *next; - - /** - * Kept in a DLL. - */ - struct WireFeeInfo *prev; - - /** - * When does the fee go into effect (inclusive). - */ - struct GNUNET_TIME_Absolute start_date; - - /** - * When does the fee stop being in effect (exclusive). - */ - struct GNUNET_TIME_Absolute end_date; - - /** - * How high is the wire fee. - */ - struct TALER_Amount wire_fee; - - /** - * How high is the closing fee. - */ - struct TALER_Amount closing_fee; - -}; - - -/** - * Closure for callbacks during #analyze_merchants(). - */ -struct AggregationContext -{ - - /** - * DLL of wire fees charged by the exchange. - */ - struct WireFeeInfo *fee_head; - - /** - * DLL of wire fees charged by the exchange. - */ - struct WireFeeInfo *fee_tail; - - /** - * Final result status. - */ - enum GNUNET_DB_QueryStatus qs; -}; - - -/** - * Closure for #wire_transfer_information_cb. - */ -struct WireCheckContext -{ - - /** - * Corresponding merchant context. - */ - struct AggregationContext *ac; - - /** - * Total deposits claimed by all transactions that were aggregated - * under the given @e wtid. - */ - struct TALER_Amount total_deposits; - - /** - * Hash of the wire transfer details of the receiver. - */ - struct GNUNET_HashCode h_wire; - - /** - * Execution time of the wire transfer. - */ - struct GNUNET_TIME_Absolute date; - - /** - * Database transaction status. - */ - enum GNUNET_DB_QueryStatus qs; - -}; - - -/** - * Check coin's transaction history for plausibility. Does NOT check - * the signatures (those are checked independently), but does calculate - * the amounts for the aggregation table and checks that the total - * claimed coin value is within the value of the coin's denomination. - * - * @param coin_pub public key of the coin (for reporting) - * @param h_contract_terms hash of the proposal for which we calculate the amount - * @param merchant_pub public key of the merchant (who is allowed to issue refunds) - * @param issue denomination information about the coin - * @param tl_head head of transaction history to verify - * @param[out] merchant_gain amount the coin contributes to the wire transfer to the merchant - * @param[out] deposit_gain amount the coin contributes excluding refunds - * @return #GNUNET_OK on success, #GNUNET_SYSERR on error - */ -static int -check_transaction_history_for_deposit (const struct - TALER_CoinSpendPublicKeyP *coin_pub, - const struct - GNUNET_HashCode *h_contract_terms, - const struct - TALER_MerchantPublicKeyP *merchant_pub, - const struct - TALER_DenominationKeyValidityPS *issue, - const struct - TALER_EXCHANGEDB_TransactionList *tl_head, - struct TALER_Amount *merchant_gain, - struct TALER_Amount *deposit_gain) -{ - struct TALER_Amount expenditures; - struct TALER_Amount refunds; - struct TALER_Amount spent; - struct TALER_Amount value; - struct TALER_Amount merchant_loss; - struct TALER_Amount final_gain; - const struct TALER_Amount *deposit_fee; - int refund_deposit_fee; - - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Checking transaction history of coin %s\n", - TALER_B2S (coin_pub)); - - GNUNET_assert (NULL != tl_head); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &expenditures)); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &refunds)); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - merchant_gain)); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &merchant_loss)); - /* Go over transaction history to compute totals; note that we do not - know the order, so instead of subtracting we compute positive - (deposit, melt) and negative (refund) values separately here, - and then subtract the negative from the positive after the loop. */ - refund_deposit_fee = GNUNET_NO; - deposit_fee = NULL; - for (const struct TALER_EXCHANGEDB_TransactionList *tl = tl_head; - NULL != tl; - tl = tl->next) - { - const struct TALER_Amount *amount_with_fee; - const struct TALER_Amount *fee; - const struct TALER_AmountNBO *fee_dki; - struct TALER_Amount tmp; - - switch (tl->type) - { - case TALER_EXCHANGEDB_TT_DEPOSIT: - /* check wire and h_wire are consistent */ - { - struct GNUNET_HashCode hw; - - if (GNUNET_OK != - TALER_JSON_merchant_wire_signature_hash ( - tl->details.deposit->receiver_wire_account, - &hw)) - { - report_row_inconsistency ("deposits", - tl->serial_id, - "wire value malformed"); - } - else if (0 != - GNUNET_memcmp (&hw, - &tl->details.deposit->h_wire)) - { - report_row_inconsistency ("deposits", - tl->serial_id, - "h(wire) does not match wire"); - } - } - amount_with_fee = &tl->details.deposit->amount_with_fee; - fee = &tl->details.deposit->deposit_fee; - fee_dki = &issue->fee_deposit; - if (GNUNET_OK != - TALER_amount_add (&expenditures, - &expenditures, - amount_with_fee)) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - /* Check if this deposit is within the remit of the aggregation - we are investigating, if so, include it in the totals. */ - if ( (0 == GNUNET_memcmp (merchant_pub, - &tl->details.deposit->merchant_pub)) && - (0 == GNUNET_memcmp (h_contract_terms, - &tl->details.deposit->h_contract_terms)) ) - { - struct TALER_Amount amount_without_fee; - - if (GNUNET_OK != - TALER_amount_subtract (&amount_without_fee, - amount_with_fee, - fee)) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_amount_add (merchant_gain, - merchant_gain, - &amount_without_fee)) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Detected applicable deposit of %s\n", - TALER_amount2s (&amount_without_fee)); - deposit_fee = fee; - } - /* Check that the fees given in the transaction list and in dki match */ - TALER_amount_ntoh (&tmp, - fee_dki); - if (0 != - TALER_amount_cmp (&tmp, - fee)) - { - /* Disagreement in fee structure within DB, should be impossible! */ - GNUNET_break (0); - return GNUNET_SYSERR; - } - break; - case TALER_EXCHANGEDB_TT_MELT: - amount_with_fee = &tl->details.melt->amount_with_fee; - fee = &tl->details.melt->melt_fee; - fee_dki = &issue->fee_refresh; - if (GNUNET_OK != - TALER_amount_add (&expenditures, - &expenditures, - amount_with_fee)) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - /* Check that the fees given in the transaction list and in dki match */ - TALER_amount_ntoh (&tmp, - fee_dki); - if (0 != - TALER_amount_cmp (&tmp, - fee)) - { - /* Disagreement in fee structure within DB, should be impossible! */ - GNUNET_break (0); - return GNUNET_SYSERR; - } - break; - case TALER_EXCHANGEDB_TT_REFUND: - amount_with_fee = &tl->details.refund->refund_amount; - fee = &tl->details.refund->refund_fee; - fee_dki = &issue->fee_refund; - if (GNUNET_OK != - TALER_amount_add (&refunds, - &refunds, - amount_with_fee)) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_amount_add (&expenditures, - &expenditures, - fee)) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - /* Check if this refund is within the remit of the aggregation - we are investigating, if so, include it in the totals. */ - if ( (0 == GNUNET_memcmp (merchant_pub, - &tl->details.refund->merchant_pub)) && - (0 == GNUNET_memcmp (h_contract_terms, - &tl->details.refund->h_contract_terms)) ) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Detected applicable refund of %s\n", - TALER_amount2s (amount_with_fee)); - if (GNUNET_OK != - TALER_amount_add (&merchant_loss, - &merchant_loss, - amount_with_fee)) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - refund_deposit_fee = GNUNET_YES; - } - /* Check that the fees given in the transaction list and in dki match */ - TALER_amount_ntoh (&tmp, - fee_dki); - if (0 != - TALER_amount_cmp (&tmp, - fee)) - { - /* Disagreement in fee structure within DB, should be impossible! */ - GNUNET_break (0); - return GNUNET_SYSERR; - } - break; - case TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP: - amount_with_fee = &tl->details.old_coin_recoup->value; - if (GNUNET_OK != - TALER_amount_add (&refunds, - &refunds, - amount_with_fee)) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - break; - case TALER_EXCHANGEDB_TT_RECOUP: - amount_with_fee = &tl->details.recoup->value; - if (GNUNET_OK != - TALER_amount_add (&expenditures, - &expenditures, - amount_with_fee)) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - break; - case TALER_EXCHANGEDB_TT_RECOUP_REFRESH: - amount_with_fee = &tl->details.recoup_refresh->value; - if (GNUNET_OK != - TALER_amount_add (&expenditures, - &expenditures, - amount_with_fee)) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - break; - } - } /* for 'tl' */ - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Deposits without fees are %s\n", - TALER_amount2s (merchant_gain)); - - /* Calculate total balance change, i.e. expenditures (recoup, deposit, refresh) - minus refunds (refunds, recoup-to-old) */ - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Subtracting refunds of %s from coin value loss\n", - TALER_amount2s (&refunds)); - if (GNUNET_SYSERR == - TALER_amount_subtract (&spent, - &expenditures, - &refunds)) - { - /* refunds above expenditures? Bad! */ - report_coin_arithmetic_inconsistency ("refund (balance)", - coin_pub, - &expenditures, - &refunds, - 1); - return GNUNET_SYSERR; - } - - /* Now check that 'spent' is less or equal than the total coin value */ - TALER_amount_ntoh (&value, - &issue->value); - if (1 == TALER_amount_cmp (&spent, - &value)) - { - /* spent > value */ - report_coin_arithmetic_inconsistency ("spend", - coin_pub, - &spent, - &value, - -1); - return GNUNET_SYSERR; - } - - /* Finally, update @a merchant_gain by subtracting what he "lost" - from refunds */ - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Merchant 'loss' due to refunds is %s\n", - TALER_amount2s (&merchant_loss)); - *deposit_gain = *merchant_gain; - if ( (GNUNET_YES == refund_deposit_fee) && - (NULL != deposit_fee) ) - { - /* We had a /deposit operation AND a /refund operation, - and should thus not charge the merchant the /deposit fee */ - GNUNET_assert (GNUNET_OK == - TALER_amount_add (merchant_gain, - merchant_gain, - deposit_fee)); - } - if (GNUNET_SYSERR == - TALER_amount_subtract (&final_gain, - merchant_gain, - &merchant_loss)) - { - /* refunds above deposits? Bad! */ - report_coin_arithmetic_inconsistency ("refund (merchant)", - coin_pub, - merchant_gain, - &merchant_loss, - 1); - return GNUNET_SYSERR; - } - *merchant_gain = final_gain; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Final merchant gain after refunds is %s\n", - TALER_amount2s (deposit_gain)); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Coin %s contributes %s to contract %s\n", - TALER_B2S (coin_pub), - TALER_amount2s (merchant_gain), - GNUNET_h2s (h_contract_terms)); - return GNUNET_OK; -} - - -/** - * Function called with the results of the lookup of the - * transaction data associated with a wire transfer identifier. - * - * @param cls a `struct WireCheckContext` - * @param rowid which row in the table is the information from (for diagnostics) - * @param merchant_pub public key of the merchant (should be same for all callbacks with the same @e cls) - * @param h_wire hash of wire transfer details of the merchant (should be same for all callbacks with the same @e cls) - * @param account_details where did we transfer the funds? - * @param exec_time execution time of the wire transfer (should be same for all callbacks with the same @e cls) - * @param h_contract_terms which proposal was this payment about - * @param denom_pub denomination of @a coin_pub - * @param coin_pub which public key was this payment about - * @param coin_value amount contributed by this coin in total (with fee), - * but excluding refunds by this coin - * @param deposit_fee applicable deposit fee for this coin, actual - * fees charged may differ if coin was refunded - */ -static void -wire_transfer_information_cb (void *cls, - uint64_t rowid, - const struct - TALER_MerchantPublicKeyP *merchant_pub, - const struct GNUNET_HashCode *h_wire, - const json_t *account_details, - struct GNUNET_TIME_Absolute exec_time, - const struct GNUNET_HashCode *h_contract_terms, - const struct - TALER_DenominationPublicKey *denom_pub, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - const struct TALER_Amount *coin_value, - const struct TALER_Amount *deposit_fee) -{ - struct WireCheckContext *wcc = cls; - const struct TALER_DenominationKeyValidityPS *issue; - struct TALER_Amount computed_value; - struct TALER_Amount coin_value_without_fee; - struct TALER_Amount total_deposit_without_refunds; - struct TALER_EXCHANGEDB_TransactionList *tl; - struct TALER_CoinPublicInfo coin; - enum GNUNET_DB_QueryStatus qs; - struct GNUNET_HashCode hw; - - if (GNUNET_OK != - TALER_JSON_merchant_wire_signature_hash (account_details, - &hw)) - { - report_row_inconsistency ("aggregation", - rowid, - "failed to compute hash of given wire data"); - } - else if (0 != - GNUNET_memcmp (&hw, - h_wire)) - { - report_row_inconsistency ("aggregation", - rowid, - "database contains wrong hash code for wire details"); - } - - /* Obtain coin's transaction history */ - qs = edb->get_coin_transactions (edb->cls, - esession, - coin_pub, - GNUNET_YES, - &tl); - if ( (qs < 0) || - (NULL == tl) ) - { - wcc->qs = qs; - report_row_inconsistency ("aggregation", - rowid, - "no transaction history for coin claimed in aggregation"); - return; - } - qs = edb->get_known_coin (edb->cls, - esession, - coin_pub, - &coin); - if (qs < 0) - { - GNUNET_break (0); /* this should be a foreign key violation at this point! */ - wcc->qs = qs; - report_row_inconsistency ("aggregation", - rowid, - "could not get coin details for coin claimed in aggregation"); - return; - } - - qs = get_denomination_info_by_hash (&coin.denom_pub_hash, - &issue); - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) - { - GNUNET_CRYPTO_rsa_signature_free (coin.denom_sig.rsa_signature); - edb->free_coin_transaction_list (edb->cls, - tl); - if (0 == qs) - report_row_inconsistency ("aggregation", - rowid, - "could not find denomination key for coin claimed in aggregation"); - else - wcc->qs = qs; - return; - } - if (GNUNET_OK != - TALER_test_coin_valid (&coin, - denom_pub)) - { - report (report_bad_sig_losses, - json_pack ("{s:s, s:I, s:o, s:o}", - "operation", "wire", - "row", (json_int_t) rowid, - "loss", TALER_JSON_from_amount (coin_value), - "key_pub", GNUNET_JSON_from_data_auto ( - &issue->denom_hash))); - GNUNET_break (GNUNET_OK == - TALER_amount_add (&total_bad_sig_loss, - &total_bad_sig_loss, - coin_value)); - GNUNET_CRYPTO_rsa_signature_free (coin.denom_sig.rsa_signature); - edb->free_coin_transaction_list (edb->cls, - tl); - wcc->qs = GNUNET_DB_STATUS_HARD_ERROR; - report_row_inconsistency ("deposit", - rowid, - "coin denomination signature invalid"); - return; - } - GNUNET_CRYPTO_rsa_signature_free (coin.denom_sig.rsa_signature); - coin.denom_sig.rsa_signature = NULL; /* just to be sure */ - GNUNET_assert (NULL != issue); /* mostly to help static analysis */ - /* Check transaction history to see if it supports aggregate - valuation */ - if (GNUNET_OK != - check_transaction_history_for_deposit (coin_pub, - h_contract_terms, - merchant_pub, - issue, - tl, - &computed_value, - &total_deposit_without_refunds)) - { - wcc->qs = GNUNET_DB_STATUS_HARD_ERROR; - report_row_inconsistency ("coin history", - rowid, - "failed to verify coin history (for deposit)"); - return; - } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Coin contributes %s to aggregate (deposits after fees and refunds)\n", - TALER_amount2s (&computed_value)); - if (GNUNET_SYSERR == - TALER_amount_subtract (&coin_value_without_fee, - coin_value, - deposit_fee)) - { - wcc->qs = GNUNET_DB_STATUS_HARD_ERROR; - report_amount_arithmetic_inconsistency ("aggregation (fee structure)", - rowid, - coin_value, - deposit_fee, - -1); - return; - } - if (0 != - TALER_amount_cmp (&total_deposit_without_refunds, - &coin_value_without_fee)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Expected coin contribution of %s to aggregate\n", - TALER_amount2s (&coin_value_without_fee)); - wcc->qs = GNUNET_DB_STATUS_HARD_ERROR; - report_amount_arithmetic_inconsistency ("aggregation (contribution)", - rowid, - &coin_value_without_fee, - &total_deposit_without_refunds, - -1); - } - edb->free_coin_transaction_list (edb->cls, - tl); - - /* Check other details of wire transfer match */ - if (0 != GNUNET_memcmp (h_wire, - &wcc->h_wire)) - { - wcc->qs = GNUNET_DB_STATUS_HARD_ERROR; - report_row_inconsistency ("aggregation", - rowid, - "target of outgoing wire transfer do not match hash of wire from deposit"); - } - if (exec_time.abs_value_us != wcc->date.abs_value_us) - { - /* This should be impossible from database constraints */ - GNUNET_break (0); - wcc->qs = GNUNET_DB_STATUS_HARD_ERROR; - report_row_inconsistency ("aggregation", - rowid, - "date given in aggregate does not match wire transfer date"); - } - - /* Add coin's contribution to total aggregate value */ - { - struct TALER_Amount res; - - if (GNUNET_OK != - TALER_amount_add (&res, - &wcc->total_deposits, - &computed_value)) - { - GNUNET_break (0); - wcc->qs = GNUNET_DB_STATUS_HARD_ERROR; - return; - } - wcc->total_deposits = res; - } -} - - -/** - * Lookup the wire fee that the exchange charges at @a timestamp. - * - * @param ac context for caching the result - * @param method method of the wire plugin - * @param timestamp time for which we need the fee - * @return NULL on error (fee unknown) - */ -static const struct TALER_Amount * -get_wire_fee (struct AggregationContext *ac, - const char *method, - struct GNUNET_TIME_Absolute timestamp) -{ - struct WireFeeInfo *wfi; - struct WireFeeInfo *pos; - struct TALER_MasterSignatureP master_sig; - - /* Check if fee is already loaded in cache */ - for (pos = ac->fee_head; NULL != pos; pos = pos->next) - { - if ( (pos->start_date.abs_value_us <= timestamp.abs_value_us) && - (pos->end_date.abs_value_us > timestamp.abs_value_us) ) - return &pos->wire_fee; - if (pos->start_date.abs_value_us > timestamp.abs_value_us) - break; - } - - /* Lookup fee in exchange database */ - wfi = GNUNET_new (struct WireFeeInfo); - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != - edb->get_wire_fee (edb->cls, - esession, - method, - timestamp, - &wfi->start_date, - &wfi->end_date, - &wfi->wire_fee, - &wfi->closing_fee, - &master_sig)) - { - GNUNET_break (0); - GNUNET_free (wfi); - return NULL; - } - - /* Check signature. (This is not terribly meaningful as the exchange can - easily make this one up, but it means that we have proof that the master - key was used for inconsistent wire fees if a merchant complains.) */ - { - struct TALER_MasterWireFeePS wf; - - wf.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_WIRE_FEES); - wf.purpose.size = htonl (sizeof (wf)); - GNUNET_CRYPTO_hash (method, - strlen (method) + 1, - &wf.h_wire_method); - wf.start_date = GNUNET_TIME_absolute_hton (wfi->start_date); - wf.end_date = GNUNET_TIME_absolute_hton (wfi->end_date); - TALER_amount_hton (&wf.wire_fee, - &wfi->wire_fee); - TALER_amount_hton (&wf.closing_fee, - &wfi->closing_fee); - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_WIRE_FEES, - &wf.purpose, - &master_sig.eddsa_signature, - &master_pub.eddsa_pub)) - { - report_row_inconsistency ("wire-fee", - timestamp.abs_value_us, - "wire fee signature invalid at given time"); - } - } - - /* Established fee, keep in sorted list */ - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Wire fee is %s starting at %s\n", - TALER_amount2s (&wfi->wire_fee), - GNUNET_STRINGS_absolute_time_to_string (wfi->start_date)); - if ( (NULL == pos) || - (NULL == pos->prev) ) - GNUNET_CONTAINER_DLL_insert (ac->fee_head, - ac->fee_tail, - wfi); - else - GNUNET_CONTAINER_DLL_insert_after (ac->fee_head, - ac->fee_tail, - pos->prev, - wfi); - /* Check non-overlaping fee invariant */ - if ( (NULL != wfi->prev) && - (wfi->prev->end_date.abs_value_us > wfi->start_date.abs_value_us) ) - { - report (report_fee_time_inconsistencies, - json_pack ("{s:s, s:s, s:o}", - "type", method, - "diagnostic", "start date before previous end date", - "time", json_from_time_abs (wfi->start_date))); - } - if ( (NULL != wfi->next) && - (wfi->next->start_date.abs_value_us >= wfi->end_date.abs_value_us) ) - { - report (report_fee_time_inconsistencies, - json_pack ("{s:s, s:s, s:o}", - "type", method, - "diagnostic", "end date date after next start date", - "time", json_from_time_abs (wfi->end_date))); - } - return &wfi->wire_fee; -} - - -/** - * Check that a wire transfer made by the exchange is valid - * (has matching deposits). - * - * @param cls a `struct AggregationContext` - * @param rowid identifier of the respective row in the database - * @param date timestamp of the wire transfer (roughly) - * @param wtid wire transfer subject - * @param wire wire transfer details of the receiver - * @param amount amount that was wired - * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop iteration - */ -static int -check_wire_out_cb (void *cls, - uint64_t rowid, - struct GNUNET_TIME_Absolute date, - const struct TALER_WireTransferIdentifierRawP *wtid, - const json_t *wire, - const struct TALER_Amount *amount) -{ - struct AggregationContext *ac = cls; - struct WireCheckContext wcc; - struct TALER_Amount final_amount; - struct TALER_Amount exchange_gain; - enum GNUNET_DB_QueryStatus qs; - char *method; - - /* should be monotonically increasing */ - GNUNET_assert (rowid >= ppa.last_wire_out_serial_id); - ppa.last_wire_out_serial_id = rowid + 1; - - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Checking wire transfer %s over %s performed on %s\n", - TALER_B2S (wtid), - TALER_amount2s (amount), - GNUNET_STRINGS_absolute_time_to_string (date)); - if (NULL == (method = TALER_JSON_wire_to_method (wire))) - { - report_row_inconsistency ("wire_out", - rowid, - "specified wire address lacks method"); - return GNUNET_OK; - } - - wcc.ac = ac; - wcc.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; - wcc.date = date; - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (amount->currency, - &wcc.total_deposits)); - if (GNUNET_OK != - TALER_JSON_merchant_wire_signature_hash (wire, - &wcc.h_wire)) - { - GNUNET_break (0); - GNUNET_free (method); - return GNUNET_SYSERR; - } - qs = edb->lookup_wire_transfer (edb->cls, - esession, - wtid, - &wire_transfer_information_cb, - &wcc); - if (0 > qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - ac->qs = qs; - GNUNET_free (method); - return GNUNET_SYSERR; - } - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != wcc.qs) - { - /* Note: detailed information was already logged - in #wire_transfer_information_cb, so here we - only log for debugging */ - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Inconsitency for wire_out %llu (WTID %s) detected\n", - (unsigned long long) rowid, - TALER_B2S (wtid)); - } - - - /* Subtract aggregation fee from total (if possible) */ - { - const struct TALER_Amount *wire_fee; - - wire_fee = get_wire_fee (ac, - method, - date); - if (NULL == wire_fee) - { - report_row_inconsistency ("wire-fee", - date.abs_value_us, - "wire fee unavailable for given time"); - /* If fee is unknown, we just assume the fee is zero */ - final_amount = wcc.total_deposits; - } - else if (GNUNET_SYSERR == - TALER_amount_subtract (&final_amount, - &wcc.total_deposits, - wire_fee)) - { - report_amount_arithmetic_inconsistency ("wire out (fee structure)", - rowid, - &wcc.total_deposits, - wire_fee, - -1); - /* If fee arithmetic fails, we just assume the fee is zero */ - final_amount = wcc.total_deposits; - } - } - GNUNET_free (method); - - /* Round down to amount supported by wire method */ - GNUNET_break (GNUNET_SYSERR != - TALER_amount_round_down (&final_amount, - ¤cy_round_unit)); - - /* Calculate the exchange's gain as the fees plus rounding differences! */ - if (GNUNET_SYSERR == - TALER_amount_subtract (&exchange_gain, - &wcc.total_deposits, - &final_amount)) - { - GNUNET_break (0); - ac->qs = GNUNET_DB_STATUS_HARD_ERROR; - return GNUNET_SYSERR; - } - - /* Sum up aggregation fees (we simply include the rounding gains) */ - if (GNUNET_OK != - TALER_amount_add (&total_aggregation_fee_income, - &total_aggregation_fee_income, - &exchange_gain)) - { - GNUNET_break (0); - ac->qs = GNUNET_DB_STATUS_HARD_ERROR; - return GNUNET_SYSERR; - } - - /* Check that calculated amount matches actual amount */ - if (0 != TALER_amount_cmp (amount, - &final_amount)) - { - struct TALER_Amount delta; - - if (0 < TALER_amount_cmp (amount, - &final_amount)) - { - /* amount > final_amount */ - GNUNET_assert (GNUNET_OK == - TALER_amount_subtract (&delta, - amount, - &final_amount)); - GNUNET_assert (GNUNET_OK == - TALER_amount_add (&total_wire_out_delta_plus, - &total_wire_out_delta_plus, - &delta)); - } - else - { - /* amount < final_amount */ - GNUNET_assert (GNUNET_OK == - TALER_amount_subtract (&delta, - &final_amount, - amount)); - GNUNET_assert (GNUNET_OK == - TALER_amount_add (&total_wire_out_delta_minus, - &total_wire_out_delta_minus, - &delta)); - } - - report (report_wire_out_inconsistencies, - json_pack ("{s:O, s:I, s:o, s:o}", - "destination_account", wire, - "rowid", (json_int_t) rowid, - "expected", - TALER_JSON_from_amount (&final_amount), - "claimed", - TALER_JSON_from_amount (amount))); - return GNUNET_OK; - } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Wire transfer %s is OK\n", - TALER_B2S (wtid)); - return GNUNET_OK; -} - - -/** - * Analyze the exchange aggregator's payment processing. - * - * @param cls closure - * @return transaction status code - */ -static enum GNUNET_DB_QueryStatus -analyze_aggregations (void *cls) -{ - struct AggregationContext ac; - struct WireFeeInfo *wfi; - enum GNUNET_DB_QueryStatus qsx; - enum GNUNET_DB_QueryStatus qs; - enum GNUNET_DB_QueryStatus qsp; - - (void) cls; - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Analyzing aggregations\n"); - qsp = adb->get_auditor_progress_aggregation (adb->cls, - asession, - &master_pub, - &ppa); - 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 - { - ppa_start = ppa; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - _ ("Resuming aggregation audit at %llu\n"), - (unsigned long long) ppa.last_wire_out_serial_id); - } - - memset (&ac, - 0, - sizeof (ac)); - qsx = adb->get_wire_fee_summary (adb->cls, - asession, - &master_pub, - &total_aggregation_fee_income); - if (0 > qsx) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qsx); - return qsx; - } - ac.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; - qs = edb->select_wire_out_above_serial_id (edb->cls, - esession, - ppa.last_wire_out_serial_id, - &check_wire_out_cb, - &ac); - if (0 > qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - ac.qs = qs; - } - while (NULL != (wfi = ac.fee_head)) - { - GNUNET_CONTAINER_DLL_remove (ac.fee_head, - ac.fee_tail, - wfi); - GNUNET_free (wfi); - } - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) - { - /* there were no wire out entries to be looked at, we are done */ - return qs; - } - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != ac.qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == ac.qs); - return ac.qs; - } - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qsx) - ac.qs = adb->insert_wire_fee_summary (adb->cls, - asession, - &master_pub, - &total_aggregation_fee_income); - else - ac.qs = adb->update_wire_fee_summary (adb->cls, - asession, - &master_pub, - &total_aggregation_fee_income); - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != ac.qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == ac.qs); - return ac.qs; - } - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qsp) - qs = adb->update_auditor_progress_aggregation (adb->cls, - asession, - &master_pub, - &ppa); - else - qs = adb->insert_auditor_progress_aggregation (adb->cls, - asession, - &master_pub, - &ppa); - 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 aggregation audit step at %llu\n"), - (unsigned long long) ppa.last_wire_out_serial_id); - - return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; -} - - -/* ************************* Analyze coins ******************** */ -/* This logic checks that the exchange did the right thing for each - coin, checking deposits, refunds, refresh* and known_coins - tables */ - - -/** - * Summary data we keep per denomination. - */ -struct DenominationSummary -{ - /** - * Total value of outstanding (not deposited) coins issued with this - * denomination key. - */ - struct TALER_Amount denom_balance; - - /** - * Total losses made (once coins deposited exceed - * coins withdrawn and thus the @e denom_balance is - * effectively negative). - */ - struct TALER_Amount denom_loss; - - /** - * Total value of coins issued with this denomination key. - */ - struct TALER_Amount denom_risk; - - /** - * Total value of coins subjected to recoup with this denomination key. - */ - struct TALER_Amount denom_recoup; - - /** - * How many coins (not their amount!) of this denomination - * did the exchange issue overall? - */ - uint64_t num_issued; - - /** - * Denomination key information for this denomination. - */ - const struct TALER_DenominationKeyValidityPS *issue; - - /** - * #GNUNET_YES if this record already existed in the DB. - * Used to decide between insert/update in - * #sync_denomination(). - */ - int in_db; - - /** - * Should we report an emergency for this denomination? - */ - int report_emergency; - - /** - * #GNUNET_YES if this denomination was revoked. - */ - int was_revoked; -}; - - -/** - * Closure for callbacks during #analyze_coins(). - */ -struct CoinContext -{ - - /** - * Map for tracking information about denominations. - */ - struct GNUNET_CONTAINER_MultiHashMap *denom_summaries; - - /** - * Current write/replace offset in the circular @e summaries buffer. - */ - unsigned int summaries_off; - - /** - * Transaction status code. - */ - enum GNUNET_DB_QueryStatus qs; - -}; - - -/** - * Initialize information about denomination from the database. - * - * @param denom_hash hash of the public key of the denomination - * @param[out] ds summary to initialize - * @return transaction status code - */ -static enum GNUNET_DB_QueryStatus -init_denomination (const struct GNUNET_HashCode *denom_hash, - struct DenominationSummary *ds) -{ - enum GNUNET_DB_QueryStatus qs; - struct TALER_MasterSignatureP msig; - uint64_t rowid; - - qs = adb->get_denomination_balance (adb->cls, - asession, - denom_hash, - &ds->denom_balance, - &ds->denom_loss, - &ds->denom_risk, - &ds->denom_recoup, - &ds->num_issued); - if (0 > qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - return qs; - } - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) - { - ds->in_db = GNUNET_YES; - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Starting balance for denomination `%s' is %s\n", - GNUNET_h2s (denom_hash), - TALER_amount2s (&ds->denom_balance)); - return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; - } - qs = edb->get_denomination_revocation (edb->cls, - esession, - denom_hash, - &msig, - &rowid); - if (0 > qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - return qs; - } - if (0 < qs) - { - /* check revocation signature */ - struct TALER_MasterDenominationKeyRevocationPS rm; - - rm.purpose.purpose = htonl ( - TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED); - rm.purpose.size = htonl (sizeof (rm)); - rm.h_denom_pub = *denom_hash; - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify ( - TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED, - &rm.purpose, - &msig.eddsa_signature, - &master_pub.eddsa_pub)) - { - report_row_inconsistency ("denomination revocation table", - rowid, - "revocation signature invalid"); - } - else - { - ds->was_revoked = GNUNET_YES; - } - } - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &ds->denom_balance)); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &ds->denom_loss)); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &ds->denom_risk)); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &ds->denom_recoup)); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Starting balance for denomination `%s' is %s\n", - GNUNET_h2s (denom_hash), - TALER_amount2s (&ds->denom_balance)); - return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; -} - - -/** - * Obtain the denomination summary for the given @a dh - * - * @param cc our execution context - * @param issue denomination key information for @a dh - * @param dh the denomination hash to use for the lookup - * @return NULL on error - */ -static struct DenominationSummary * -get_denomination_summary (struct CoinContext *cc, - const struct TALER_DenominationKeyValidityPS *issue, - const struct GNUNET_HashCode *dh) -{ - struct DenominationSummary *ds; - - ds = GNUNET_CONTAINER_multihashmap_get (cc->denom_summaries, - dh); - if (NULL != ds) - return ds; - ds = GNUNET_new (struct DenominationSummary); - ds->issue = issue; - if (0 > (cc->qs = init_denomination (dh, - ds))) - { - GNUNET_break (0); - GNUNET_free (ds); - return NULL; - } - GNUNET_assert (GNUNET_OK == - GNUNET_CONTAINER_multihashmap_put (cc->denom_summaries, - dh, - ds, - GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); - return ds; -} - - -/** - * Write information about the current knowledge about a denomination key - * back to the database and update our global reporting data about the - * denomination. Also remove and free the memory of @a value. - * - * @param cls the `struct CoinContext` - * @param denom_hash the hash of the denomination key - * @param value a `struct DenominationSummary` - * @return #GNUNET_OK (continue to iterate) - */ -static int -sync_denomination (void *cls, - const struct GNUNET_HashCode *denom_hash, - void *value) -{ - struct CoinContext *cc = cls; - struct DenominationSummary *ds = value; - const struct TALER_DenominationKeyValidityPS *issue = ds->issue; - struct GNUNET_TIME_Absolute now; - struct GNUNET_TIME_Absolute expire_deposit; - struct GNUNET_TIME_Absolute expire_deposit_grace; - enum GNUNET_DB_QueryStatus qs; - - now = GNUNET_TIME_absolute_get (); - expire_deposit = GNUNET_TIME_absolute_ntoh (issue->expire_deposit); - /* add day grace period to deal with clocks not being perfectly synchronized */ - expire_deposit_grace = GNUNET_TIME_absolute_add (expire_deposit, - DEPOSIT_GRACE_PERIOD); - if (now.abs_value_us > expire_deposit_grace.abs_value_us) - { - /* Denominationkey has expired, book remaining balance of - outstanding coins as revenue; and reduce cc->risk exposure. */ - if (ds->in_db) - qs = adb->del_denomination_balance (adb->cls, - asession, - denom_hash); - else - qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; - if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) && - ( (0 != ds->denom_risk.value) || - (0 != ds->denom_risk.fraction) ) ) - { - /* The denomination expired and carried a balance; we can now - book the remaining balance as profit, and reduce our risk - exposure by the accumulated risk of the denomination. */ - if (GNUNET_SYSERR == - TALER_amount_subtract (&total_risk, - &total_risk, - &ds->denom_risk)) - { - /* Holy smokes, our risk assessment was inconsistent! - This is really, really bad. */ - GNUNET_break (0); - cc->qs = GNUNET_DB_STATUS_HARD_ERROR; - } - } - if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) && - ( (0 != ds->denom_balance.value) || - (0 != ds->denom_balance.fraction) ) ) - { - /* book denom_balance coin expiration profits! */ - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Denomination `%s' expired, booking %s in expiration profits\n", - GNUNET_h2s (denom_hash), - TALER_amount2s (&ds->denom_balance)); - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != - (qs = adb->insert_historic_denom_revenue (adb->cls, - asession, - &master_pub, - denom_hash, - expire_deposit, - &ds->denom_balance, - &ds->denom_recoup))) - { - /* Failed to store profits? Bad database */ - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - cc->qs = qs; - } - } - } - else - { - long long cnt; - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Final balance for denomination `%s' is %s (%llu)\n", - GNUNET_h2s (denom_hash), - TALER_amount2s (&ds->denom_balance), - (unsigned long long) ds->num_issued); - cnt = edb->count_known_coins (edb->cls, - esession, - denom_hash); - if (0 > cnt) - { - /* Failed to obtain count? Bad database */ - qs = (enum GNUNET_DB_QueryStatus) cnt; - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - cc->qs = qs; - } - else - { - if (ds->num_issued < (uint64_t) cnt) - { - report_emergency_by_count (issue, - ds->num_issued, - cnt, - &ds->denom_risk); - } - if (GNUNET_YES == ds->report_emergency) - { - report_emergency_by_amount (issue, - &ds->denom_risk, - &ds->denom_loss); - - } - if (ds->in_db) - qs = adb->update_denomination_balance (adb->cls, - asession, - denom_hash, - &ds->denom_balance, - &ds->denom_loss, - &ds->denom_risk, - &ds->denom_recoup, - ds->num_issued); - else - qs = adb->insert_denomination_balance (adb->cls, - asession, - denom_hash, - &ds->denom_balance, - &ds->denom_loss, - &ds->denom_risk, - &ds->denom_recoup, - ds->num_issued); - } - } - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - cc->qs = qs; - } - GNUNET_assert (GNUNET_YES == - GNUNET_CONTAINER_multihashmap_remove (cc->denom_summaries, - denom_hash, - ds)); - GNUNET_free (ds); - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != cc->qs) - return GNUNET_SYSERR; - return GNUNET_OK; -} - - -/** - * Function called with details about all withdraw operations. - * Updates the denomination balance and the overall balance as - * we now have additional coins that have been issued. - * - * Note that the signature was already checked in - * #handle_reserve_out(), so we do not check it again here. - * - * @param cls our `struct CoinContext` - * @param rowid unique serial ID for the refresh session in our DB - * @param h_blind_ev blinded hash of the coin's public key - * @param denom_pub public denomination key of the deposited coin - * @param reserve_pub public key of the reserve - * @param reserve_sig signature over the withdraw operation (verified elsewhere) - * @param execution_date when did the wallet withdraw the coin - * @param amount_with_fee amount that was withdrawn - * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop - */ -static int -withdraw_cb (void *cls, - uint64_t rowid, - const struct GNUNET_HashCode *h_blind_ev, - const struct TALER_DenominationPublicKey *denom_pub, - const struct TALER_ReservePublicKeyP *reserve_pub, - const struct TALER_ReserveSignatureP *reserve_sig, - struct GNUNET_TIME_Absolute execution_date, - const struct TALER_Amount *amount_with_fee) -{ - struct CoinContext *cc = cls; - struct DenominationSummary *ds; - struct GNUNET_HashCode dh; - const struct TALER_DenominationKeyValidityPS *issue; - struct TALER_Amount value; - enum GNUNET_DB_QueryStatus qs; - - (void) h_blind_ev; - (void) reserve_pub; - (void) reserve_sig; - (void) execution_date; - (void) amount_with_fee; - GNUNET_assert (rowid >= ppc.last_withdraw_serial_id); /* should be monotonically increasing */ - ppc.last_withdraw_serial_id = rowid + 1; - - qs = get_denomination_info (denom_pub, - &issue, - &dh); - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) - { - report_row_inconsistency ("withdraw", - rowid, - "denomination key not found"); - return GNUNET_OK; - } - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) - { - /* This really ought to be a transient DB error. */ - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - cc->qs = qs; - return GNUNET_SYSERR; - } - ds = get_denomination_summary (cc, - issue, - &dh); - if (NULL == ds) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - TALER_amount_ntoh (&value, - &issue->value); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Issued coin in denomination `%s' of total value %s\n", - GNUNET_h2s (&dh), - TALER_amount2s (&value)); - ds->num_issued++; - if (GNUNET_OK != - TALER_amount_add (&ds->denom_balance, - &ds->denom_balance, - &value)) - { - GNUNET_break (0); - cc->qs = GNUNET_DB_STATUS_HARD_ERROR; - return GNUNET_SYSERR; - } - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "New balance of denomination `%s' is %s\n", - GNUNET_h2s (&dh), - TALER_amount2s (&ds->denom_balance)); - if (GNUNET_OK != - TALER_amount_add (&total_escrow_balance, - &total_escrow_balance, - &value)) - { - GNUNET_break (0); - cc->qs = GNUNET_DB_STATUS_HARD_ERROR; - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_amount_add (&total_risk, - &total_risk, - &value)) - { - GNUNET_break (0); - cc->qs = GNUNET_DB_STATUS_HARD_ERROR; - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_amount_add (&ds->denom_risk, - &ds->denom_risk, - &value)) - { - GNUNET_break (0); - cc->qs = GNUNET_DB_STATUS_HARD_ERROR; - return GNUNET_SYSERR; - } - return GNUNET_OK; -} - - -/** - * Closure for #reveal_data_cb(). - */ -struct RevealContext -{ - - /** - * Denomination public keys of the new coins. - */ - struct TALER_DenominationPublicKey *new_dps; - - /** - * Size of the @a new_dp and @a new_dps arrays. - */ - unsigned int num_freshcoins; -}; - - -/** - * Function called with information about a refresh order. - * - * @param cls closure - * @param num_freshcoins size of the @a rrcs array - * @param rrcs array of @a num_freshcoins information about coins to be created - * @param num_tprivs number of entries in @a tprivs, should be #TALER_CNC_KAPPA - 1 - * @param tprivs array of @e num_tprivs transfer private keys - * @param tp transfer public key information - */ -static void -reveal_data_cb (void *cls, - uint32_t num_freshcoins, - const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs, - unsigned int num_tprivs, - const struct TALER_TransferPrivateKeyP *tprivs, - const struct TALER_TransferPublicKeyP *tp) -{ - struct RevealContext *rctx = cls; - - (void) num_tprivs; - (void) tprivs; - (void) tp; - rctx->num_freshcoins = num_freshcoins; - rctx->new_dps = GNUNET_new_array (num_freshcoins, - struct TALER_DenominationPublicKey); - for (unsigned int i = 0; i<num_freshcoins; i++) - rctx->new_dps[i].rsa_public_key - = GNUNET_CRYPTO_rsa_public_key_dup (rrcs[i].denom_pub.rsa_public_key); -} - - -/** - * Check that the @a coin_pub is a known coin with a proper - * signature for denominatinon @a denom_pub. If not, report - * a loss of @a loss_potential. - * - * @param coin_pub public key of a coin - * @param denom_pub expected denomination of the coin - * @param loss_potential how big could the loss be if the coin is - * not properly signed - * @return database transaction status, on success - * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT - */ -static enum GNUNET_DB_QueryStatus -check_known_coin (const struct TALER_CoinSpendPublicKeyP *coin_pub, - const struct TALER_DenominationPublicKey *denom_pub, - const struct TALER_Amount *loss_potential) -{ - struct TALER_CoinPublicInfo ci; - enum GNUNET_DB_QueryStatus qs; - - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Checking denomination signature on %s\n", - TALER_B2S (coin_pub)); - qs = edb->get_known_coin (edb->cls, - esession, - coin_pub, - &ci); - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - return qs; - } - if (GNUNET_YES != - TALER_test_coin_valid (&ci, - denom_pub)) - { - report (report_bad_sig_losses, - json_pack ("{s:s, s:I, s:o, s:o}", - "operation", "known-coin", - "row", (json_int_t) -1, - "loss", TALER_JSON_from_amount (loss_potential), - "key_pub", GNUNET_JSON_from_data_auto (coin_pub))); - GNUNET_break (GNUNET_OK == - TALER_amount_add (&total_bad_sig_loss, - &total_bad_sig_loss, - loss_potential)); - - } - GNUNET_CRYPTO_rsa_signature_free (ci.denom_sig.rsa_signature); - return qs; -} - - -/** - * Function called with details about coins that were melted, with the - * goal of auditing the refresh's execution. Verifies the signature - * and updates our information about coins outstanding (the old coin's - * denomination has less, the fresh coins increased outstanding - * balances). - * - * @param cls closure - * @param rowid unique serial ID for the refresh session in our DB - * @param denom_pub denomination public key of @a coin_pub - * @param coin_pub public key of the coin - * @param coin_sig signature from the coin - * @param amount_with_fee amount that was deposited including fee - * @param noreveal_index which index was picked by the exchange in cut-and-choose - * @param rc what is the refresh commitment - * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop - */ -static int -refresh_session_cb (void *cls, - uint64_t rowid, - const struct TALER_DenominationPublicKey *denom_pub, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - const struct TALER_CoinSpendSignatureP *coin_sig, - const struct TALER_Amount *amount_with_fee, - uint32_t noreveal_index, - const struct TALER_RefreshCommitmentP *rc) -{ - struct CoinContext *cc = cls; - struct TALER_RefreshMeltCoinAffirmationPS rmc; - const struct TALER_DenominationKeyValidityPS *issue; - struct DenominationSummary *dso; - struct TALER_Amount amount_without_fee; - struct TALER_Amount tmp; - enum GNUNET_DB_QueryStatus qs; - - (void) noreveal_index; - GNUNET_assert (rowid >= ppc.last_melt_serial_id); /* should be monotonically increasing */ - ppc.last_melt_serial_id = rowid + 1; - - qs = get_denomination_info (denom_pub, - &issue, - NULL); - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) - { - report_row_inconsistency ("melt", - rowid, - "denomination key not found"); - return GNUNET_OK; - } - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - cc->qs = qs; - return GNUNET_SYSERR; - } - qs = check_known_coin (coin_pub, - denom_pub, - amount_with_fee); - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - cc->qs = qs; - return GNUNET_SYSERR; - } - - /* verify melt signature */ - rmc.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT); - rmc.purpose.size = htonl (sizeof (rmc)); - rmc.rc = *rc; - TALER_amount_hton (&rmc.amount_with_fee, - amount_with_fee); - rmc.melt_fee = issue->fee_refresh; - rmc.coin_pub = *coin_pub; - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_MELT, - &rmc.purpose, - &coin_sig->eddsa_signature, - &coin_pub->eddsa_pub)) - { - report (report_bad_sig_losses, - json_pack ("{s:s, s:I, s:o, s:o}", - "operation", "melt", - "row", (json_int_t) rowid, - "loss", TALER_JSON_from_amount (amount_with_fee), - "key_pub", GNUNET_JSON_from_data_auto (coin_pub))); - GNUNET_break (GNUNET_OK == - TALER_amount_add (&total_bad_sig_loss, - &total_bad_sig_loss, - amount_with_fee)); - return GNUNET_OK; - } - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Melting coin %s in denomination `%s' of value %s\n", - TALER_B2S (coin_pub), - GNUNET_h2s (&issue->denom_hash), - TALER_amount2s (amount_with_fee)); - - { - struct RevealContext reveal_ctx; - struct TALER_Amount refresh_cost; - int err; - - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (amount_with_fee->currency, - &refresh_cost)); - memset (&reveal_ctx, - 0, - sizeof (reveal_ctx)); - qs = edb->get_refresh_reveal (edb->cls, - esession, - rc, - &reveal_data_cb, - &reveal_ctx); - if (0 > qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - cc->qs = GNUNET_DB_STATUS_HARD_ERROR; - return GNUNET_SYSERR; - } - if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) || - (0 == reveal_ctx.num_freshcoins) ) - { - /* This can happen if /refresh/reveal was not yet called or only - with invalid data, even if the exchange is correctly - operating. We still report it. */ - report (report_refreshs_hanging, - json_pack ("{s:I, s:o, s:o}", - "row", (json_int_t) rowid, - "amount", TALER_JSON_from_amount (amount_with_fee), - "coin_pub", GNUNET_JSON_from_data_auto (coin_pub))); - GNUNET_break (GNUNET_OK == - TALER_amount_add (&total_refresh_hanging, - &total_refresh_hanging, - amount_with_fee)); - return GNUNET_OK; - } - - { - const struct TALER_DenominationKeyValidityPS *new_issues[reveal_ctx. - num_freshcoins]; - - /* Update outstanding amounts for all new coin's denominations, and check - that the resulting amounts are consistent with the value being refreshed. */ - err = GNUNET_OK; - for (unsigned int i = 0; i<reveal_ctx.num_freshcoins; i++) - { - /* lookup new coin denomination key */ - qs = get_denomination_info (&reveal_ctx.new_dps[i], - &new_issues[i], - NULL); - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) - { - report_row_inconsistency ("refresh_reveal", - rowid, - "denomination key not found"); - err = GNUNET_NO; /* terminate, but return "OK" */ - } - else if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - cc->qs = qs; - err = GNUNET_SYSERR; /* terminate, return GNUNET_SYSERR */ - } - GNUNET_CRYPTO_rsa_public_key_free ( - reveal_ctx.new_dps[i].rsa_public_key); - reveal_ctx.new_dps[i].rsa_public_key = NULL; - } - GNUNET_free (reveal_ctx.new_dps); - reveal_ctx.new_dps = NULL; - - if (GNUNET_OK != err) - return (GNUNET_SYSERR == err) ? GNUNET_SYSERR : GNUNET_OK; - - /* calculate total refresh cost */ - for (unsigned int i = 0; i<reveal_ctx.num_freshcoins; i++) - { - /* update cost of refresh */ - struct TALER_Amount fee; - struct TALER_Amount value; - - TALER_amount_ntoh (&fee, - &new_issues[i]->fee_withdraw); - TALER_amount_ntoh (&value, - &new_issues[i]->value); - if ( (GNUNET_OK != - TALER_amount_add (&refresh_cost, - &refresh_cost, - &fee)) || - (GNUNET_OK != - TALER_amount_add (&refresh_cost, - &refresh_cost, - &value)) ) - { - GNUNET_break (0); - cc->qs = GNUNET_DB_STATUS_HARD_ERROR; - return GNUNET_SYSERR; - } - } - - /* compute contribution of old coin */ - { - struct TALER_Amount melt_fee; - - TALER_amount_ntoh (&melt_fee, - &issue->fee_refresh); - if (GNUNET_OK != - TALER_amount_subtract (&amount_without_fee, - amount_with_fee, - &melt_fee)) - { - GNUNET_break (0); - cc->qs = GNUNET_DB_STATUS_HARD_ERROR; - return GNUNET_SYSERR; - } - } - - /* check old coin covers complete expenses */ - if (1 == TALER_amount_cmp (&refresh_cost, - &amount_without_fee)) - { - /* refresh_cost > amount_without_fee */ - report_amount_arithmetic_inconsistency ("melt (fee)", - rowid, - &amount_without_fee, - &refresh_cost, - -1); - return GNUNET_OK; - } - - /* update outstanding denomination amounts */ - for (unsigned int i = 0; i<reveal_ctx.num_freshcoins; i++) - { - struct DenominationSummary *dsi; - struct TALER_Amount value; - - dsi = get_denomination_summary (cc, - new_issues[i], - &new_issues[i]->denom_hash); - if (NULL == dsi) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - TALER_amount_ntoh (&value, - &new_issues[i]->value); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Created fresh coin in denomination `%s' of value %s\n", - GNUNET_h2s (&new_issues[i]->denom_hash), - TALER_amount2s (&value)); - dsi->num_issued++; - if (GNUNET_OK != - TALER_amount_add (&dsi->denom_balance, - &dsi->denom_balance, - &value)) - { - GNUNET_break (0); - cc->qs = GNUNET_DB_STATUS_HARD_ERROR; - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_amount_add (&dsi->denom_risk, - &dsi->denom_risk, - &value)) - { - GNUNET_break (0); - cc->qs = GNUNET_DB_STATUS_HARD_ERROR; - return GNUNET_SYSERR; - } - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "New balance of denomination `%s' is %s\n", - GNUNET_h2s (&new_issues[i]->denom_hash), - TALER_amount2s (&dsi->denom_balance)); - if (GNUNET_OK != - TALER_amount_add (&total_escrow_balance, - &total_escrow_balance, - &value)) - { - GNUNET_break (0); - cc->qs = GNUNET_DB_STATUS_HARD_ERROR; - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_amount_add (&total_risk, - &total_risk, - &value)) - { - GNUNET_break (0); - cc->qs = GNUNET_DB_STATUS_HARD_ERROR; - return GNUNET_SYSERR; - } - } - } - } - - /* update old coin's denomination balance */ - dso = get_denomination_summary (cc, - issue, - &issue->denom_hash); - if (NULL == dso) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - if (GNUNET_SYSERR == - TALER_amount_subtract (&tmp, - &dso->denom_balance, - amount_with_fee)) - { - GNUNET_assert (GNUNET_OK == - TALER_amount_add (&dso->denom_loss, - &dso->denom_loss, - amount_with_fee)); - dso->report_emergency = GNUNET_YES; - } - else - { - dso->denom_balance = tmp; - } - if (-1 == TALER_amount_cmp (&total_escrow_balance, - amount_with_fee)) - { - /* This can theoretically happen if for example the exchange - never issued any coins (i.e. escrow balance is zero), but - accepted a forged coin (i.e. emergency situation after - private key compromise). In that case, we cannot even - subtract the profit we make from the fee from the escrow - balance. Tested as part of test-auditor.sh, case #18 */report_amount_arithmetic_inconsistency ( - "subtracting refresh fee from escrow balance", - rowid, - &total_escrow_balance, - amount_with_fee, - 0); - } - else - { - GNUNET_assert (GNUNET_SYSERR != - TALER_amount_subtract (&total_escrow_balance, - &total_escrow_balance, - amount_with_fee)); - } - - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "New balance of denomination `%s' after melt is %s\n", - GNUNET_h2s (&issue->denom_hash), - TALER_amount2s (&dso->denom_balance)); - - /* update global melt fees */ - { - struct TALER_Amount rfee; - - TALER_amount_ntoh (&rfee, - &issue->fee_refresh); - if (GNUNET_OK != - TALER_amount_add (&total_melt_fee_income, - &total_melt_fee_income, - &rfee)) - { - GNUNET_break (0); - cc->qs = GNUNET_DB_STATUS_HARD_ERROR; - return GNUNET_SYSERR; - } - } - - /* We're good! */ - return GNUNET_OK; -} - - -/** - * Function called with details about deposits that have been made, - * with the goal of auditing the deposit's execution. - * - * @param cls closure - * @param rowid unique serial ID for the deposit in our DB - * @param timestamp when did the deposit happen - * @param merchant_pub public key of the merchant - * @param denom_pub denomination public key of @a coin_pub - * @param coin_pub public key of the coin - * @param coin_sig signature from the coin - * @param amount_with_fee amount that was deposited including fee - * @param h_contract_terms hash of the proposal data known to merchant and customer - * @param refund_deadline by which the merchant adviced that he might want - * to get a refund - * @param wire_deadline by which the merchant adviced that he would like the - * wire transfer to be executed - * @param receiver_wire_account wire details for the merchant, NULL from iterate_matching_deposits() - * @param done flag set if the deposit was already executed (or not) - * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop - */ -static int -deposit_cb (void *cls, - uint64_t rowid, - struct GNUNET_TIME_Absolute timestamp, - const struct TALER_MerchantPublicKeyP *merchant_pub, - const struct TALER_DenominationPublicKey *denom_pub, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - const struct TALER_CoinSpendSignatureP *coin_sig, - const struct TALER_Amount *amount_with_fee, - const struct GNUNET_HashCode *h_contract_terms, - struct GNUNET_TIME_Absolute refund_deadline, - struct GNUNET_TIME_Absolute wire_deadline, - const json_t *receiver_wire_account, - int done) -{ - struct CoinContext *cc = cls; - const struct TALER_DenominationKeyValidityPS *issue; - struct DenominationSummary *ds; - struct TALER_DepositRequestPS dr; - struct TALER_Amount tmp; - enum GNUNET_DB_QueryStatus qs; - - (void) wire_deadline; - (void) done; - GNUNET_assert (rowid >= ppc.last_deposit_serial_id); /* should be monotonically increasing */ - ppc.last_deposit_serial_id = rowid + 1; - - qs = get_denomination_info (denom_pub, - &issue, - NULL); - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) - { - report_row_inconsistency ("deposits", - rowid, - "denomination key not found"); - return GNUNET_OK; - } - - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - cc->qs = qs; - return GNUNET_SYSERR; - } - qs = check_known_coin (coin_pub, - denom_pub, - amount_with_fee); - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - cc->qs = qs; - return GNUNET_SYSERR; - } - - /* Verify deposit signature */ - dr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT); - dr.purpose.size = htonl (sizeof (dr)); - dr.h_contract_terms = *h_contract_terms; - if (GNUNET_OK != - TALER_JSON_merchant_wire_signature_hash (receiver_wire_account, - &dr.h_wire)) - { - report (report_bad_sig_losses, - json_pack ("{s:s, s:I, s:o, s:o}", - "operation", "deposit", - "row", (json_int_t) rowid, - "loss", TALER_JSON_from_amount (amount_with_fee), - "key_pub", GNUNET_JSON_from_data_auto (coin_pub))); - GNUNET_break (GNUNET_OK == - TALER_amount_add (&total_bad_sig_loss, - &total_bad_sig_loss, - amount_with_fee)); - return GNUNET_OK; - } - dr.timestamp = GNUNET_TIME_absolute_hton (timestamp); - dr.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline); - TALER_amount_hton (&dr.amount_with_fee, - amount_with_fee); - dr.deposit_fee = issue->fee_deposit; - dr.merchant = *merchant_pub; - dr.coin_pub = *coin_pub; - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_DEPOSIT, - &dr.purpose, - &coin_sig->eddsa_signature, - &coin_pub->eddsa_pub)) - { - report (report_bad_sig_losses, - json_pack ("{s:s, s:I, s:o, s:o}", - "operation", "deposit", - "row", (json_int_t) rowid, - "loss", TALER_JSON_from_amount (amount_with_fee), - "key_pub", GNUNET_JSON_from_data_auto (coin_pub))); - GNUNET_break (GNUNET_OK == - TALER_amount_add (&total_bad_sig_loss, - &total_bad_sig_loss, - amount_with_fee)); - return GNUNET_OK; - } - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Deposited coin %s in denomination `%s' of value %s\n", - TALER_B2S (coin_pub), - GNUNET_h2s (&issue->denom_hash), - TALER_amount2s (amount_with_fee)); - - /* update old coin's denomination balance */ - ds = get_denomination_summary (cc, - issue, - &issue->denom_hash); - if (NULL == ds) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - if (GNUNET_SYSERR == - TALER_amount_subtract (&tmp, - &ds->denom_balance, - amount_with_fee)) - { - GNUNET_assert (GNUNET_OK == - TALER_amount_add (&ds->denom_loss, - &ds->denom_loss, - amount_with_fee)); - ds->report_emergency = GNUNET_YES; - } - else - { - ds->denom_balance = tmp; - } - - if (-1 == TALER_amount_cmp (&total_escrow_balance, - amount_with_fee)) - { - /* This can theoretically happen if for example the exchange - never issued any coins (i.e. escrow balance is zero), but - accepted a forged coin (i.e. emergency situation after - private key compromise). In that case, we cannot even - subtract the profit we make from the fee from the escrow - balance. Tested as part of test-auditor.sh, case #18 */report_amount_arithmetic_inconsistency ( - "subtracting deposit fee from escrow balance", - rowid, - &total_escrow_balance, - amount_with_fee, - 0); - } - else - { - GNUNET_assert (GNUNET_SYSERR != - TALER_amount_subtract (&total_escrow_balance, - &total_escrow_balance, - amount_with_fee)); - } - - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "New balance of denomination `%s' after deposit is %s\n", - GNUNET_h2s (&issue->denom_hash), - TALER_amount2s (&ds->denom_balance)); - - /* update global up melt fees */ - { - struct TALER_Amount dfee; - - TALER_amount_ntoh (&dfee, - &issue->fee_deposit); - if (GNUNET_OK != - TALER_amount_add (&total_deposit_fee_income, - &total_deposit_fee_income, - &dfee)) - { - GNUNET_break (0); - cc->qs = GNUNET_DB_STATUS_HARD_ERROR; - return GNUNET_SYSERR; - } - } - - return GNUNET_OK; -} - - -/** - * Function called with details about coins that were refunding, - * with the goal of auditing the refund's execution. Adds the - * refunded amount back to the outstanding balance of the respective - * denomination. - * - * @param cls closure - * @param rowid unique serial ID for the refund in our DB - * @param denom_pub denomination public key of @a coin_pub - * @param coin_pub public key of the coin - * @param merchant_pub public key of the merchant - * @param merchant_sig signature of the merchant - * @param h_contract_terms hash of the proposal data known to merchant and customer - * @param rtransaction_id refund transaction ID chosen by the merchant - * @param amount_with_fee amount that was deposited including fee - * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop - */ -static int -refund_cb (void *cls, - uint64_t rowid, - const struct TALER_DenominationPublicKey *denom_pub, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - const struct TALER_MerchantPublicKeyP *merchant_pub, - const struct TALER_MerchantSignatureP *merchant_sig, - const struct GNUNET_HashCode *h_contract_terms, - uint64_t rtransaction_id, - const struct TALER_Amount *amount_with_fee) -{ - struct CoinContext *cc = cls; - const struct TALER_DenominationKeyValidityPS *issue; - struct DenominationSummary *ds; - struct TALER_RefundRequestPS rr; - struct TALER_Amount amount_without_fee; - struct TALER_Amount refund_fee; - enum GNUNET_DB_QueryStatus qs; - - GNUNET_assert (rowid >= ppc.last_refund_serial_id); /* should be monotonically increasing */ - ppc.last_refund_serial_id = rowid + 1; - - qs = get_denomination_info (denom_pub, - &issue, - NULL); - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) - { - report_row_inconsistency ("refunds", - rowid, - "denomination key not found"); - return GNUNET_SYSERR; - } - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - return GNUNET_SYSERR; - } - - /* verify refund signature */ - rr.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND); - rr.purpose.size = htonl (sizeof (rr)); - rr.h_contract_terms = *h_contract_terms; - rr.coin_pub = *coin_pub; - rr.merchant = *merchant_pub; - rr.rtransaction_id = GNUNET_htonll (rtransaction_id); - TALER_amount_hton (&rr.refund_amount, - amount_with_fee); - rr.refund_fee = issue->fee_refund; - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_REFUND, - &rr.purpose, - &merchant_sig->eddsa_sig, - &merchant_pub->eddsa_pub)) - { - report (report_bad_sig_losses, - json_pack ("{s:s, s:I, s:o, s:o}", - "operation", "refund", - "row", (json_int_t) rowid, - "loss", TALER_JSON_from_amount (amount_with_fee), - "key_pub", GNUNET_JSON_from_data_auto (merchant_pub))); - GNUNET_break (GNUNET_OK == - TALER_amount_add (&total_bad_sig_loss, - &total_bad_sig_loss, - amount_with_fee)); - return GNUNET_OK; - } - - TALER_amount_ntoh (&refund_fee, - &issue->fee_refund); - if (GNUNET_OK != - TALER_amount_subtract (&amount_without_fee, - amount_with_fee, - &refund_fee)) - { - report_amount_arithmetic_inconsistency ("refund (fee)", - rowid, - &amount_without_fee, - &refund_fee, - -1); - return GNUNET_OK; - } - - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Refunding coin %s in denomination `%s' value %s\n", - TALER_B2S (coin_pub), - GNUNET_h2s (&issue->denom_hash), - TALER_amount2s (amount_with_fee)); - - /* update coin's denomination balance */ - ds = get_denomination_summary (cc, - issue, - &issue->denom_hash); - if (NULL == ds) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_amount_add (&ds->denom_balance, - &ds->denom_balance, - &amount_without_fee)) - { - GNUNET_break (0); - cc->qs = GNUNET_DB_STATUS_HARD_ERROR; - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_amount_add (&ds->denom_risk, - &ds->denom_risk, - &amount_without_fee)) - { - GNUNET_break (0); - cc->qs = GNUNET_DB_STATUS_HARD_ERROR; - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_amount_add (&total_escrow_balance, - &total_escrow_balance, - &amount_without_fee)) - { - GNUNET_break (0); - cc->qs = GNUNET_DB_STATUS_HARD_ERROR; - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_amount_add (&total_risk, - &total_risk, - &amount_without_fee)) - { - GNUNET_break (0); - cc->qs = GNUNET_DB_STATUS_HARD_ERROR; - return GNUNET_SYSERR; - } - - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "New balance of denomination `%s' after refund is %s\n", - GNUNET_h2s (&issue->denom_hash), - TALER_amount2s (&ds->denom_balance)); - - /* update total refund fee balance */ - if (GNUNET_OK != - TALER_amount_add (&total_refund_fee_income, - &total_refund_fee_income, - &refund_fee)) - { - GNUNET_break (0); - cc->qs = GNUNET_DB_STATUS_HARD_ERROR; - return GNUNET_SYSERR; - } - - return GNUNET_OK; -} - - -/** - * Check that the recoup operation was properly initiated by a coin - * and update the denomination's losses accordingly. - * - * @param cc the context with details about the coin - * @param rowid row identifier used to uniquely identify the recoup operation - * @param amount how much should be added back to the reserve - * @param coin public information about the coin - * @param denom_pub public key of the denomionation of @a coin - * @param coin_sig signature with @e coin_pub of type #TALER_SIGNATURE_WALLET_COIN_RECOUP - * @param coin_blind blinding factor used to blind the coin - * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop - */ -static int -check_recoup (struct CoinContext *cc, - uint64_t rowid, - const struct TALER_Amount *amount, - const struct TALER_CoinPublicInfo *coin, - const struct TALER_DenominationPublicKey *denom_pub, - const struct TALER_CoinSpendSignatureP *coin_sig, - const struct TALER_DenominationBlindingKeyP *coin_blind) -{ - struct TALER_RecoupRequestPS pr; - struct DenominationSummary *ds; - enum GNUNET_DB_QueryStatus qs; - const struct TALER_DenominationKeyValidityPS *issue; - - if (GNUNET_OK != - TALER_test_coin_valid (coin, - denom_pub)) - { - report (report_bad_sig_losses, - json_pack ("{s:s, s:I, s:o, s:o}", - "operation", "recoup", - "row", (json_int_t) rowid, - "loss", TALER_JSON_from_amount (amount), - "key_pub", GNUNET_JSON_from_data_auto ( - &pr.h_denom_pub))); - GNUNET_break (GNUNET_OK == - TALER_amount_add (&total_bad_sig_loss, - &total_bad_sig_loss, - amount)); - } - qs = get_denomination_info (denom_pub, - &issue, - &pr.h_denom_pub); - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) - { - report_row_inconsistency ("recoup", - rowid, - "denomination key not found"); - return GNUNET_OK; - } - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) - { - /* The key not existing should be prevented by foreign key constraints, - so must be a transient DB error. */ - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - cc->qs = qs; - return GNUNET_SYSERR; - } - pr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_RECOUP); - pr.purpose.size = htonl (sizeof (pr)); - pr.coin_pub = coin->coin_pub; - pr.coin_blind = *coin_blind; - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_RECOUP, - &pr.purpose, - &coin_sig->eddsa_signature, - &coin->coin_pub.eddsa_pub)) - { - report (report_bad_sig_losses, - json_pack ("{s:s, s:I, s:o, s:o}", - "operation", "recoup", - "row", (json_int_t) rowid, - "loss", TALER_JSON_from_amount (amount), - "coin_pub", GNUNET_JSON_from_data_auto ( - &coin->coin_pub))); - GNUNET_break (GNUNET_OK == - TALER_amount_add (&total_bad_sig_loss, - &total_bad_sig_loss, - amount)); - return GNUNET_OK; - } - ds = get_denomination_summary (cc, - issue, - &issue->denom_hash); - if (GNUNET_NO == ds->was_revoked) - { - /* Woopsie, we allowed recoup on non-revoked denomination!? */ - report (report_bad_sig_losses, - json_pack ("{s:s, s:I, s:o, s:o}", - "operation", "recoup (denomination not revoked)", - "row", (json_int_t) rowid, - "loss", TALER_JSON_from_amount (amount), - "coin_pub", GNUNET_JSON_from_data_auto ( - &coin->coin_pub))); - } - GNUNET_break (GNUNET_OK == - TALER_amount_add (&ds->denom_recoup, - &ds->denom_recoup, - amount)); - GNUNET_break (GNUNET_OK == - TALER_amount_add (&total_recoup_loss, - &total_recoup_loss, - amount)); - return GNUNET_OK; -} - - -/** - * Function called about recoups the exchange has to perform. - * - * @param cls a `struct CoinContext *` - * @param rowid row identifier used to uniquely identify the recoup operation - * @param timestamp when did we receive the recoup request - * @param amount how much should be added back to the reserve - * @param reserve_pub public key of the reserve - * @param coin public information about the coin - * @param denom_pub denomination public key of @a coin - * @param coin_sig signature with @e coin_pub of type #TALER_SIGNATURE_WALLET_COIN_RECOUP - * @param coin_blind blinding factor used to blind the coin - * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop - */ -static int -recoup_cb (void *cls, - uint64_t rowid, - struct GNUNET_TIME_Absolute timestamp, - const struct TALER_Amount *amount, - const struct TALER_ReservePublicKeyP *reserve_pub, - const struct TALER_CoinPublicInfo *coin, - const struct TALER_DenominationPublicKey *denom_pub, - const struct TALER_CoinSpendSignatureP *coin_sig, - const struct TALER_DenominationBlindingKeyP *coin_blind) -{ - struct CoinContext *cc = cls; - - (void) timestamp; - (void) reserve_pub; - return check_recoup (cc, - rowid, - amount, - coin, - denom_pub, - coin_sig, - coin_blind); -} - - -/** - * Function called about recoups on refreshed coins the exchange has to - * perform. - * - * @param cls a `struct CoinContext *` - * @param rowid row identifier used to uniquely identify the recoup operation - * @param timestamp when did we receive the recoup request - * @param amount how much should be added back to the reserve - * @param old_coin_pub original coin that was refreshed to create @a coin - * @param coin public information about the coin - * @param denom_pub denomination public key of @a coin - * @param coin_sig signature with @e coin_pub of type #TALER_SIGNATURE_WALLET_COIN_RECOUP - * @param coin_blind blinding factor used to blind the coin - * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop - */ -static int -recoup_refresh_cb (void *cls, - uint64_t rowid, - struct GNUNET_TIME_Absolute timestamp, - const struct TALER_Amount *amount, - const struct TALER_CoinSpendPublicKeyP *old_coin_pub, - const struct TALER_CoinPublicInfo *coin, - const struct TALER_DenominationPublicKey *denom_pub, - const struct TALER_CoinSpendSignatureP *coin_sig, - const struct TALER_DenominationBlindingKeyP *coin_blind) -{ - struct CoinContext *cc = cls; - - (void) timestamp; - (void) old_coin_pub; - return check_recoup (cc, - rowid, - amount, - coin, - denom_pub, - coin_sig, - coin_blind); -} - - -/** - * Analyze the exchange's processing of coins. - * - * @param cls closure - * @return transaction status code - */ -static enum GNUNET_DB_QueryStatus -analyze_coins (void *cls) -{ - struct CoinContext cc; - enum GNUNET_DB_QueryStatus qs; - enum GNUNET_DB_QueryStatus qsx; - enum GNUNET_DB_QueryStatus qsp; - - (void) cls; - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Analyzing coins\n"); - qsp = adb->get_auditor_progress_coin (adb->cls, - asession, - &master_pub, - &ppc); - if (0 > qsp) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qsp); - return qsp; - } - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qsp) - { - GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, - "First analysis using this auditor, starting from scratch\n"); - } - else - { - ppc_start = ppc; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Resuming coin audit at %llu/%llu/%llu/%llu/%llu\n", - (unsigned long long) ppc.last_deposit_serial_id, - (unsigned long long) ppc.last_melt_serial_id, - (unsigned long long) ppc.last_refund_serial_id, - (unsigned long long) ppc.last_withdraw_serial_id, - (unsigned long long) ppc.last_recoup_refresh_serial_id); - } - - /* setup 'cc' */ - cc.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; - cc.denom_summaries = GNUNET_CONTAINER_multihashmap_create (256, - GNUNET_NO); - qsx = adb->get_balance_summary (adb->cls, - asession, - &master_pub, - &total_escrow_balance, - &total_deposit_fee_income, - &total_melt_fee_income, - &total_refund_fee_income, - &total_risk, - &total_recoup_loss, - &total_irregular_recoups); - if (0 > qsx) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qsx); - return qsx; - } - - /* process withdrawals */ - if (0 > - (qs = edb->select_withdrawals_above_serial_id (edb->cls, - esession, - ppc. - last_withdraw_serial_id, - &withdraw_cb, - &cc)) ) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - return qs; - } - if (0 > cc.qs) - return cc.qs; - - /* process refunds */ - if (0 > - (qs = edb->select_refunds_above_serial_id (edb->cls, - esession, - ppc.last_refund_serial_id, - &refund_cb, - &cc))) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - return qs; - } - if (0 > cc.qs) - return cc.qs; - - /* process refreshs */ - if (0 > - (qs = edb->select_refreshes_above_serial_id (edb->cls, - esession, - ppc.last_melt_serial_id, - &refresh_session_cb, - &cc))) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - return qs; - } - if (0 > cc.qs) - return cc.qs; - - /* process deposits */ - if (0 > - (qs = edb->select_deposits_above_serial_id (edb->cls, - esession, - ppc.last_deposit_serial_id, - &deposit_cb, - &cc))) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - return qs; - } - if (0 > cc.qs) - return cc.qs; - - /* process recoups */ - if (0 > - (qs = edb->select_recoup_above_serial_id (edb->cls, - esession, - ppc.last_recoup_serial_id, - &recoup_cb, - &cc))) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - return qs; - } - if (0 > cc.qs) - return cc.qs; - if (0 > - (qs = edb->select_recoup_refresh_above_serial_id (edb->cls, - esession, - ppc. - last_recoup_refresh_serial_id, - &recoup_refresh_cb, - &cc))) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - return qs; - } - if (0 > cc.qs) - return cc.qs; - - /* sync 'cc' back to disk */ - cc.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; - GNUNET_CONTAINER_multihashmap_iterate (cc.denom_summaries, - &sync_denomination, - &cc); - GNUNET_CONTAINER_multihashmap_destroy (cc.denom_summaries); - if (0 > cc.qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == cc.qs); - return cc.qs; - } - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qsx) - qs = adb->update_balance_summary (adb->cls, - asession, - &master_pub, - &total_escrow_balance, - &total_deposit_fee_income, - &total_melt_fee_income, - &total_refund_fee_income, - &total_risk, - &total_recoup_loss, - &total_irregular_recoups); - else - qs = adb->insert_balance_summary (adb->cls, - asession, - &master_pub, - &total_escrow_balance, - &total_deposit_fee_income, - &total_melt_fee_income, - &total_refund_fee_income, - &total_risk, - &total_recoup_loss, - &total_irregular_recoups); - if (0 >= qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - return qs; - } - - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qsp) - qs = adb->update_auditor_progress_coin (adb->cls, - asession, - &master_pub, - &ppc); - else - qs = adb->insert_auditor_progress_coin (adb->cls, - asession, - &master_pub, - &ppc); - if (0 >= qs) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Failed to update auditor DB, not recording progress\n"); - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - return qs; - } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - _ ("Concluded coin audit step at %llu/%llu/%llu/%llu/%llu\n"), - (unsigned long long) ppc.last_deposit_serial_id, - (unsigned long long) ppc.last_melt_serial_id, - (unsigned long long) ppc.last_refund_serial_id, - (unsigned long long) ppc.last_withdraw_serial_id, - (unsigned long long) ppc.last_recoup_refresh_serial_id); - return qs; -} - - -/* *************************** Analysis of deposit-confirmations ********** */ - -/** - * Closure for #test_dc. - */ -struct DepositConfirmationContext -{ - - /** - * How many deposit confirmations did we NOT find in the #edb? - */ - unsigned long long missed_count; - - /** - * What is the total amount missing? - */ - struct TALER_Amount missed_amount; - - /** - * Lowest SerialID of the first coin we missed? (This is where we - * should resume next time). - */ - uint64_t first_missed_coin_serial; - - /** - * Lowest SerialID of the first coin we missed? (This is where we - * should resume next time). - */ - uint64_t last_seen_coin_serial; - - /** - * Success or failure of (exchange) database operations within - * #test_dc. - */ - enum GNUNET_DB_QueryStatus qs; - -}; - - -/** - * Given a deposit confirmation from #adb, check that it is also - * in #edb. Update the deposit confirmation context accordingly. - * - * @param cls our `struct DepositConfirmationContext` - * @param serial_id row of the @a dc in the database - * @param dc the deposit confirmation we know - */ -static void -test_dc (void *cls, - uint64_t serial_id, - const struct TALER_AUDITORDB_DepositConfirmation *dc) -{ - struct DepositConfirmationContext *dcc = cls; - enum GNUNET_DB_QueryStatus qs; - struct TALER_EXCHANGEDB_Deposit dep; - - dcc->last_seen_coin_serial = serial_id; - memset (&dep, - 0, - sizeof (dep)); - dep.coin.coin_pub = dc->coin_pub; - dep.h_contract_terms = dc->h_contract_terms; - dep.merchant_pub = dc->merchant; - dep.h_wire = dc->h_wire; - dep.refund_deadline = dc->refund_deadline; - - qs = edb->have_deposit (edb->cls, - esession, - &dep, - GNUNET_NO /* do not check refund deadline */); - if (qs > 0) - { - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Found deposit %s in exchange database\n", - GNUNET_h2s (&dc->h_contract_terms)); - return; /* found, all good */ - } - if (qs < 0) - { - GNUNET_break (0); /* DB error, complain */ - dcc->qs = qs; - return; - } - /* deposit confirmation missing! report! */ - report (report_deposit_confirmation_inconsistencies, - json_pack ("{s:o, s:o, s:I, s:o}", - "timestamp", - json_from_time_abs (dc->timestamp), - "amount", - TALER_JSON_from_amount (&dc->amount_without_fee), - "rowid", - (json_int_t) serial_id, - "account", - GNUNET_JSON_from_data_auto (&dc->h_wire))); - dcc->first_missed_coin_serial = GNUNET_MIN (dcc->first_missed_coin_serial, - serial_id); - dcc->missed_count++; - GNUNET_assert (GNUNET_OK == - TALER_amount_add (&dcc->missed_amount, - &dcc->missed_amount, - &dc->amount_without_fee)); -} - - -/** - * Check that the deposit-confirmations that were reported to - * us by merchants are also in the exchange's database. - * - * @param cls closure - * @return transaction status code - */ -static enum GNUNET_DB_QueryStatus -analyze_deposit_confirmations (void *cls) -{ - struct TALER_AUDITORDB_ProgressPointDepositConfirmation ppdc; - struct DepositConfirmationContext dcc; - enum GNUNET_DB_QueryStatus qs; - enum GNUNET_DB_QueryStatus qsx; - enum GNUNET_DB_QueryStatus qsp; - - (void) cls; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Analyzing deposit confirmations\n"); - ppdc.last_deposit_confirmation_serial_id = 0; - qsp = adb->get_auditor_progress_deposit_confirmation (adb->cls, - asession, - &master_pub, - &ppdc); - 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 deposit confirmation audit at %llu\n"), - (unsigned long long) ppdc.last_deposit_confirmation_serial_id); - } - - /* setup 'cc' */ - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &dcc.missed_amount)); - dcc.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; - dcc.missed_count = 0LLU; - dcc.first_missed_coin_serial = UINT64_MAX; - qsx = adb->get_deposit_confirmations (adb->cls, - asession, - &master_pub, - ppdc.last_deposit_confirmation_serial_id, - &test_dc, - &dcc); - if (0 > qsx) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qsx); - return qsx; - } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Analyzed %d deposit confirmations (above serial ID %llu)\n", - (int) qsx, - (unsigned long long) ppdc.last_deposit_confirmation_serial_id); - if (0 > dcc.qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == dcc.qs); - return dcc.qs; - } - if (UINT64_MAX == dcc.first_missed_coin_serial) - ppdc.last_deposit_confirmation_serial_id = dcc.last_seen_coin_serial; - else - ppdc.last_deposit_confirmation_serial_id = dcc.first_missed_coin_serial - 1; - - /* sync 'cc' back to disk */ - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qsp) - qs = adb->update_auditor_progress_deposit_confirmation (adb->cls, - asession, - &master_pub, - &ppdc); - else - qs = adb->insert_auditor_progress_deposit_confirmation (adb->cls, - asession, - &master_pub, - &ppdc); - 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; - } - number_missed_deposit_confirmations = (json_int_t) dcc.missed_count; - total_missed_deposit_confirmations = dcc.missed_amount; - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - _ ("Concluded deposit confirmation audit step at %llu\n"), - (unsigned long long) ppdc.last_deposit_confirmation_serial_id); - return qs; -} - - -/* *************************** General transaction logic ****************** */ - -/** - * Type of an analysis function. Each analysis function runs in - * its own transaction scope and must thus be internally consistent. - * - * @param cls closure - * @return transaction status code - */ -typedef enum GNUNET_DB_QueryStatus -(*Analysis)(void *cls); - - -/** - * Perform the given @a analysis within a transaction scope. - * Commit on success. - * - * @param analysis analysis to run - * @param analysis_cls closure for @a analysis - * @return #GNUNET_OK if @a analysis succeessfully committed, - * #GNUNET_NO if we had an error on commit (retry may help) - * #GNUNET_SYSERR on hard errors - */ -static int -transact (Analysis analysis, - void *analysis_cls) -{ - int ret; - enum GNUNET_DB_QueryStatus qs; - - ret = adb->start (adb->cls, - asession); - if (GNUNET_OK != ret) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - edb->preflight (edb->cls, - esession); - ret = edb->start (edb->cls, - esession, - "auditor"); - if (GNUNET_OK != ret) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - qs = analysis (analysis_cls); - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) - { - qs = edb->commit (edb->cls, - esession); - 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"); - adb->rollback (adb->cls, - asession); - } - else - { - qs = adb->commit (adb->cls, - asession); - 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 (or no changes), rolling back transaction\n"); - adb->rollback (adb->cls, - asession); - edb->rollback (edb->cls, - esession); - } - clear_transaction_state_cache (); - switch (qs) - { - case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: - return GNUNET_OK; - case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - return GNUNET_OK; - case GNUNET_DB_STATUS_SOFT_ERROR: - return GNUNET_NO; - case GNUNET_DB_STATUS_HARD_ERROR: - return GNUNET_SYSERR; - } - return GNUNET_OK; -} - - -/** - * Initialize DB sessions and run the analysis. - */ -static void -setup_sessions_and_run () -{ - esession = edb->get_session (edb->cls); - if (NULL == esession) - { - fprintf (stderr, - "Failed to initialize exchange session.\n"); - global_ret = 1; - return; - } - asession = adb->get_session (adb->cls); - if (NULL == asession) - { - fprintf (stderr, - "Failed to initialize auditor session.\n"); - global_ret = 1; - return; - } - - GNUNET_break (GNUNET_SYSERR != - transact (&analyze_reserves, - NULL)); - GNUNET_break (GNUNET_SYSERR != - transact (&analyze_aggregations, - NULL)); - GNUNET_break (GNUNET_SYSERR != - transact (&analyze_coins, - NULL)); - GNUNET_break (GNUNET_SYSERR != - transact (&analyze_deposit_confirmations, - NULL)); -} - - -/** - * Test if the given @a mpub matches the #master_pub. - * If so, set "found" to GNUNET_YES. - * - * @param cls a `int *` pointing to "found" - * @param mpub exchange master public key to compare - * @param exchange_url URL of the exchange (ignored) - */ -static void -test_master_present (void *cls, - const struct TALER_MasterPublicKeyP *mpub, - const char *exchange_url) -{ - int *found = cls; - - (void) exchange_url; - if (0 == GNUNET_memcmp (mpub, - &master_pub)) - *found = GNUNET_YES; -} - - -/** - * 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) -{ - static const struct TALER_MasterPublicKeyP zeromp; - struct TALER_Amount income_fee_total; - json_t *report; - struct TALER_AUDITORDB_Session *as; - int found; - - (void) cls; - (void) args; - (void) cfgfile; - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Launching auditor\n"); - cfg = c; - start_time = GNUNET_TIME_absolute_get (); - if (0 == GNUNET_memcmp (&zeromp, - &master_pub)) - { - /* -m option not given, try configuration */ - char *master_public_key_str; - - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (cfg, - "exchange", - "MASTER_PUBLIC_KEY", - &master_public_key_str)) - { - fprintf (stderr, - "Pass option -m or set it in the configuration!\n"); - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "exchange", - "MASTER_PUBLIC_KEY"); - global_ret = 1; - return; - } - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_public_key_from_string (master_public_key_str, - strlen ( - master_public_key_str), - &master_pub.eddsa_pub)) - { - fprintf (stderr, - "Invalid master public key given in configuration file."); - GNUNET_free (master_public_key_str); - global_ret = 1; - return; - } - GNUNET_free (master_public_key_str); - } /* end of -m not given */ - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Taler auditor running for exchange master public key %s\n", - TALER_B2S (&master_pub)); - - if (GNUNET_OK != - TALER_config_get_currency (cfg, - ¤cy)) - { - global_ret = 1; - return; - } - { - if (GNUNET_OK != - TALER_config_get_amount (cfg, - "taler", - "CURRENCY_ROUND_UNIT", - ¤cy_round_unit)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Invalid or missing amount in `TALER' under `CURRENCY_ROUND_UNIT'\n"); - global_ret = 1; - return; - } - } - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_time (cfg, - "exchangedb", - "IDLE_RESERVE_EXPIRATION_TIME", - &idle_reserve_expiration_time)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "exchangedb", - "IDLE_RESERVE_EXPIRATION_TIME"); - global_ret = 1; - return; - } - if (NULL == - (edb = TALER_EXCHANGEDB_plugin_load (cfg))) - { - fprintf (stderr, - "Failed to initialize exchange database plugin.\n"); - global_ret = 1; - return; - } - if (NULL == - (adb = TALER_AUDITORDB_plugin_load (cfg))) - { - fprintf (stderr, - "Failed to initialize auditor database plugin.\n"); - global_ret = 1; - TALER_EXCHANGEDB_plugin_unload (edb); - return; - } - found = GNUNET_NO; - as = adb->get_session (adb->cls); - if (NULL == as) - { - fprintf (stderr, - "Failed to start session with auditor database.\n"); - global_ret = 1; - TALER_AUDITORDB_plugin_unload (adb); - TALER_EXCHANGEDB_plugin_unload (edb); - return; - } - (void) adb->list_exchanges (adb->cls, - as, - &test_master_present, - &found); - if (GNUNET_NO == found) - { - fprintf (stderr, - "Exchange's master public key `%s' not known to auditor DB. Did you forget to run `taler-auditor-exchange`?\n", - GNUNET_p2s (&master_pub.eddsa_pub)); - global_ret = 1; - TALER_AUDITORDB_plugin_unload (adb); - TALER_EXCHANGEDB_plugin_unload (edb); - return; - } - if (restart) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Full audit restart requested, dropping old audit data.\n"); - GNUNET_break (GNUNET_OK == - adb->drop_tables (adb->cls, - GNUNET_NO)); - TALER_AUDITORDB_plugin_unload (adb); - if (NULL == - (adb = TALER_AUDITORDB_plugin_load (cfg))) - { - fprintf (stderr, - "Failed to initialize auditor database plugin after drop.\n"); - global_ret = 1; - TALER_EXCHANGEDB_plugin_unload (edb); - return; - } - GNUNET_break (GNUNET_OK == - adb->create_tables (adb->cls)); - } - - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Starting audit\n"); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &reported_emergency_loss)); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &reported_emergency_risk_by_amount)); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &reported_emergency_risk_by_count)); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &reported_emergency_loss_by_count)); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &total_escrow_balance)); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &total_risk)); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &total_recoup_loss)); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &total_irregular_recoups)); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &total_withdraw_fee_income)); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &total_deposit_fee_income)); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &total_melt_fee_income)); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &total_refund_fee_income)); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &total_aggregation_fee_income)); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &total_balance_insufficient_loss)); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &total_balance_summary_delta_plus)); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &total_balance_summary_delta_minus)); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &total_wire_out_delta_plus)); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &total_wire_out_delta_minus)); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &total_arithmetic_delta_plus)); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &total_arithmetic_delta_minus)); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &total_coin_delta_plus)); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &total_coin_delta_minus)); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &total_balance_reserve_not_closed)); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &total_bad_sig_loss)); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &total_refresh_hanging)); - GNUNET_assert (NULL != - (report_emergencies = json_array ())); - GNUNET_assert (NULL != - (report_emergencies_by_count = json_array ())); - GNUNET_assert (NULL != - (report_row_inconsistencies = json_array ())); - GNUNET_assert (NULL != - (denomination_key_validity_withdraw_inconsistencies = - json_array ())); - GNUNET_assert (NULL != - (report_reserve_balance_summary_wrong_inconsistencies = - json_array ())); - GNUNET_assert (NULL != - (report_reserve_balance_insufficient_inconsistencies = - json_array ())); - GNUNET_assert (NULL != - (report_reserve_not_closed_inconsistencies = json_array ())); - GNUNET_assert (NULL != - (report_wire_out_inconsistencies = json_array ())); - GNUNET_assert (NULL != - (report_deposit_confirmation_inconsistencies = json_array ())); - GNUNET_assert (NULL != - (report_coin_inconsistencies = json_array ())); - GNUNET_assert (NULL != - (report_aggregation_fee_balances = json_array ())); - GNUNET_assert (NULL != - (report_amount_arithmetic_inconsistencies = json_array ())); - GNUNET_assert (NULL != - (report_bad_sig_losses = json_array ())); - GNUNET_assert (NULL != - (report_refreshs_hanging = json_array ())); - GNUNET_assert (NULL != - (report_fee_time_inconsistencies = json_array ())); - setup_sessions_and_run (); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Audit complete\n"); - TALER_AUDITORDB_plugin_unload (adb); - TALER_EXCHANGEDB_plugin_unload (edb); - - GNUNET_assert (TALER_amount_add (&income_fee_total, - &total_withdraw_fee_income, - &total_deposit_fee_income)); - GNUNET_assert (TALER_amount_add (&income_fee_total, - &income_fee_total, - &total_melt_fee_income)); - GNUNET_assert (TALER_amount_add (&income_fee_total, - &income_fee_total, - &total_refund_fee_income)); - GNUNET_assert (TALER_amount_add (&income_fee_total, - &income_fee_total, - &total_aggregation_fee_income)); - report = json_pack ("{s:o, s:o, s:o, s:o, s:o," - " s:o, s:o, s:o, s:o, s:o," - " s:o, s:o, s:o, s:o, s:o," - " s:o, s:o, s:o, s:o, s:o," - " s:o, s:o, s:o, s:o, s:o," - " s:o, s:o, s:o, s:o, s:o," - " s:o, s:o, s:o, s:o, s:I," - " s:o, s:o, s:o, s:o, s:o," - " s:o, s:I, s:I, s:I, s:I," - " s:I, s:I, s:I, s:I, s:I," - " s:I, s:I, s:I, s:I, s:I," - " s:I, s:I, s:I, s:I, s:I," - " s:I, s:I, s:I, s:o, s:o," - " s:o }", - /* blocks of 5 for easier counting/matching to format string */ - /* block */ - "reserve_balance_insufficient_inconsistencies", - report_reserve_balance_insufficient_inconsistencies, - /* Tested in test-auditor.sh #3 */ - "total_loss_balance_insufficient", - TALER_JSON_from_amount (&total_balance_insufficient_loss), - /* Tested in test-auditor.sh #3 */ - "reserve_balance_summary_wrong_inconsistencies", - report_reserve_balance_summary_wrong_inconsistencies, - "total_balance_summary_delta_plus", - TALER_JSON_from_amount ( - &total_balance_summary_delta_plus), - "total_balance_summary_delta_minus", - TALER_JSON_from_amount ( - &total_balance_summary_delta_minus), - /* block */ - "total_escrow_balance", - TALER_JSON_from_amount (&total_escrow_balance), - "total_active_risk", - TALER_JSON_from_amount (&total_risk), - "total_withdraw_fee_income", - TALER_JSON_from_amount (&total_withdraw_fee_income), - "total_deposit_fee_income", - TALER_JSON_from_amount (&total_deposit_fee_income), - "total_melt_fee_income", - TALER_JSON_from_amount (&total_melt_fee_income), - /* block */ - "total_refund_fee_income", - TALER_JSON_from_amount (&total_refund_fee_income), - "income_fee_total", - TALER_JSON_from_amount (&income_fee_total), - /* Tested in test-auditor.sh #18 */ - "emergencies", - report_emergencies, - /* Tested in test-auditor.sh #18 */ - "emergencies_risk_by_amount", - TALER_JSON_from_amount ( - &reported_emergency_risk_by_amount), - /* Tested in test-auditor.sh #21 */ - "reserve_not_closed_inconsistencies", - report_reserve_not_closed_inconsistencies, - /* block */ - /* Tested in test-auditor.sh #21 */ - "total_balance_reserve_not_closed", - TALER_JSON_from_amount ( - &total_balance_reserve_not_closed), - "wire_out_inconsistencies", - report_wire_out_inconsistencies, - "total_wire_out_delta_plus", - TALER_JSON_from_amount (&total_wire_out_delta_plus), - "total_wire_out_delta_minus", - TALER_JSON_from_amount (&total_wire_out_delta_minus), - /* Tested in test-auditor.sh #4/#5/#6/#7/#13 */ - "bad_sig_losses", - report_bad_sig_losses, - /* block */ - /* Tested in test-auditor.sh #4/#5/#6/#7/#13 */ - "total_bad_sig_loss", - TALER_JSON_from_amount (&total_bad_sig_loss), - /* Tested in test-auditor.sh #14/#15 */ - "row_inconsistencies", - report_row_inconsistencies, - /* Tested in test-auditor.sh #23 */ - "denomination_key_validity_withdraw_inconsistencies", - denomination_key_validity_withdraw_inconsistencies, - "coin_inconsistencies", - report_coin_inconsistencies, - "total_coin_delta_plus", - TALER_JSON_from_amount (&total_coin_delta_plus), - /* block */ - "total_coin_delta_minus", - TALER_JSON_from_amount (&total_coin_delta_minus), - "amount_arithmetic_inconsistencies", - report_amount_arithmetic_inconsistencies, - "total_arithmetic_delta_plus", - TALER_JSON_from_amount (&total_arithmetic_delta_plus), - "total_arithmetic_delta_minus", - TALER_JSON_from_amount (&total_arithmetic_delta_minus), - "total_aggregation_fee_income", - TALER_JSON_from_amount (&total_aggregation_fee_income), - /* block */ - "wire_fee_time_inconsistencies", - report_fee_time_inconsistencies, - /* Tested in test-auditor.sh #12 */ - "total_refresh_hanging", - TALER_JSON_from_amount (&total_refresh_hanging), - /* Tested in test-auditor.sh #12 */ - "refresh_hanging", - report_refreshs_hanging, - "deposit_confirmation_inconsistencies", - report_deposit_confirmation_inconsistencies, - "missing_deposit_confirmation_count", - (json_int_t) number_missed_deposit_confirmations, - /* block */ - "missing_deposit_confirmation_total", - TALER_JSON_from_amount ( - &total_missed_deposit_confirmations), - "total_recoup_loss", - TALER_JSON_from_amount (&total_recoup_loss), - /* Tested in test-auditor.sh #18 */ - "emergencies_by_count", - report_emergencies_by_count, - /* Tested in test-auditor.sh #18 */ - "emergencies_risk_by_count", - TALER_JSON_from_amount ( - &reported_emergency_risk_by_count), - /* Tested in test-auditor.sh #18 */ - "emergencies_loss", - TALER_JSON_from_amount (&reported_emergency_loss), - /* block */ - /* Tested in test-auditor.sh #18 */ - "emergencies_loss_by_count", - TALER_JSON_from_amount ( - &reported_emergency_loss_by_count), - "start_ppr_reserve_in_serial_id", - (json_int_t) ppr_start.last_reserve_in_serial_id, - "start_ppr_reserve_out_serial_id", - (json_int_t) ppr_start.last_reserve_out_serial_id, - "start_ppr_reserve_recoup_serial_id", - (json_int_t) ppr_start.last_reserve_recoup_serial_id, - "start_ppr_reserve_close_serial_id", - (json_int_t) ppr_start.last_reserve_close_serial_id, - /* block */ - "end_ppr_reserve_in_serial_id", - (json_int_t) ppr.last_reserve_in_serial_id, - "end_ppr_reserve_out_serial_id", - (json_int_t) ppr.last_reserve_out_serial_id, - "end_ppr_reserve_recoup_serial_id", - (json_int_t) ppr.last_reserve_recoup_serial_id, - "end_ppr_reserve_close_serial_id", - (json_int_t) ppr.last_reserve_close_serial_id, - "start_ppa_wire_out_serial_id", - (json_int_t) ppa_start.last_wire_out_serial_id, - /* block */ - "end_ppa_wire_out_serial_id", - (json_int_t) ppa.last_wire_out_serial_id, - "start_ppc_withdraw_serial_id", - (json_int_t) ppc_start.last_withdraw_serial_id, - "start_ppc_deposit_serial_id", - (json_int_t) ppc_start.last_deposit_serial_id, - "start_ppc_melt_serial_id", - (json_int_t) ppc_start.last_melt_serial_id, - "start_ppc_refund_serial_id", - (json_int_t) ppc_start.last_refund_serial_id, - /* block */ - "start_ppc_recoup_serial_id", - (json_int_t) ppc_start.last_recoup_serial_id, - "start_ppc_recoup_refresh_serial_id", - (json_int_t) ppc_start.last_recoup_refresh_serial_id, - "end_ppc_withdraw_serial_id", - (json_int_t) ppc.last_withdraw_serial_id, - "end_ppc_deposit_serial_id", - (json_int_t) ppc.last_deposit_serial_id, - "end_ppc_melt_serial_id", - (json_int_t) ppc.last_melt_serial_id, - /* block */ - "end_ppc_refund_serial_id", - (json_int_t) ppc.last_refund_serial_id, - "end_ppc_recoup_serial_id", - (json_int_t) ppc.last_recoup_serial_id, - "end_ppc_recoup_refresh_serial_id", - (json_int_t) ppc.last_recoup_refresh_serial_id, - "auditor_start_time", json_string ( - GNUNET_STRINGS_absolute_time_to_string (start_time)), - "auditor_end_time", json_string ( - GNUNET_STRINGS_absolute_time_to_string ( - GNUNET_TIME_absolute_get ())), - /* block */ - "total_irregular_recoups", - TALER_JSON_from_amount (&total_irregular_recoups) - ); - GNUNET_break (NULL != report); - json_dumpf (report, - stdout, - JSON_INDENT (2)); - json_decref (report); -} - - -/** - * The main function of the database initialization tool. - * Used to initialize the Taler Exchange's database. - * - * @param argc number of arguments from the command line - * @param argv command line arguments - * @return 0 ok, 1 on error - */ -int -main (int argc, - char *const *argv) -{ - const struct GNUNET_GETOPT_CommandLineOption options[] = { - GNUNET_GETOPT_option_base32_auto ('m', - "exchange-key", - "KEY", - "public key of the exchange (Crockford base32 encoded)", - &master_pub), - GNUNET_GETOPT_option_flag ('r', - "restart", - "restart audit from the beginning (required on first run)", - &restart), - GNUNET_GETOPT_option_timetravel ('T', - "timetravel"), - GNUNET_GETOPT_OPTION_END - }; - - /* force linker to link against libtalerutil; if we do - not do this, the linker may "optimize" libtalerutil - away and skip #TALER_OS_init(), which we do need */ - (void) TALER_project_data_default (); - GNUNET_assert (GNUNET_OK == - GNUNET_log_setup ("taler-auditor", - "MESSAGE", - NULL)); - if (GNUNET_OK != - GNUNET_PROGRAM_run (argc, - argv, - "taler-auditor", - "Audit Taler exchange database", - options, - &run, - NULL)) - return 1; - return global_ret; -} - - -/* end of taler-auditor.c */ diff --git a/src/auditor/taler-auditor.in b/src/auditor/taler-auditor.in new file mode 100755 index 000000000..1cc50d130 --- /dev/null +++ b/src/auditor/taler-auditor.in @@ -0,0 +1,23 @@ +#!/bin/sh + +set -eu + +DIR=`mktemp -d reportXXXXXX` +for n in aggregation coins deposits reserves wire +do + taler-helper-auditor-$n "$@" > ${DIR}/$n.json +done + +taler-helper-auditor-render.py \ + ${DIR}/aggregation.json \ + ${DIR}/coins.json \ + ${DIR}/deposits.json \ + ${DIR}/reserves.json \ + ${DIR}/wire.json < %pkgdatadir%/auditor-report.tex.j2 > ${DIR}/auditor-report.tex +cd ${DIR} +pdflatex auditor-report.tex < /dev/null +pdflatex auditor-report.tex < /dev/null +pdflatex auditor-report.tex < /dev/null +cd .. + +echo "Result is in ${DIR}/auditor-report.pdf" diff --git a/src/auditor/taler-helper-auditor-aggregation.c b/src/auditor/taler-helper-auditor-aggregation.c index 27afcfd88..811bb66d4 100644 --- a/src/auditor/taler-helper-auditor-aggregation.c +++ b/src/auditor/taler-helper-auditor-aggregation.c @@ -1519,10 +1519,6 @@ main (int argc, "KEY", "public key of the exchange (Crockford base32 encoded)", &TALER_ARL_master_pub), - GNUNET_GETOPT_option_flag ('r', - "TALER_ARL_restart", - "TALER_ARL_restart audit from the beginning (required on first run)", - &TALER_ARL_restart), GNUNET_GETOPT_option_timetravel ('T', "timetravel"), GNUNET_GETOPT_OPTION_END @@ -1533,13 +1529,13 @@ main (int argc, away and skip #TALER_OS_init(), which we do need */ (void) TALER_project_data_default (); GNUNET_assert (GNUNET_OK == - GNUNET_log_setup ("taler-auditor-aggregation", + GNUNET_log_setup ("taler-helper-auditor-aggregation", "MESSAGE", NULL)); if (GNUNET_OK != GNUNET_PROGRAM_run (argc, argv, - "taler-auditor-aggregation", + "taler-helper-auditor-aggregation", "Audit Taler exchange aggregation activity", options, &run, @@ -1549,4 +1545,4 @@ main (int argc, } -/* end of taler-auditor-aggregation.c */ +/* end of taler-helper-auditor-aggregation.c */ diff --git a/src/auditor/taler-helper-auditor-coins.c b/src/auditor/taler-helper-auditor-coins.c index d1e91ac27..9412016b0 100644 --- a/src/auditor/taler-helper-auditor-coins.c +++ b/src/auditor/taler-helper-auditor-coins.c @@ -185,10 +185,10 @@ static struct TALER_Amount total_refresh_hanging; * @param loss actual losses already (actualized before denomination was revoked) */ static void -report_emergency_by_amount (const struct - TALER_DenominationKeyValidityPS *issue, - const struct TALER_Amount *risk, - const struct TALER_Amount *loss) +report_emergency_by_amount ( + const struct TALER_DenominationKeyValidityPS *issue, + const struct TALER_Amount *risk, + const struct TALER_Amount *loss) { TALER_ARL_report (report_emergencies, json_pack ("{s:o, s:o, s:o, s:o, s:o, s:o}", @@ -232,11 +232,11 @@ report_emergency_by_amount (const struct * @param risk amount that is at risk */ static void -report_emergency_by_count (const struct - TALER_DenominationKeyValidityPS *issue, - uint64_t num_issued, - uint64_t num_known, - const struct TALER_Amount *risk) +report_emergency_by_count ( + const struct TALER_DenominationKeyValidityPS *issue, + uint64_t num_issued, + uint64_t num_known, + const struct TALER_Amount *risk) { struct TALER_Amount denom_value; @@ -288,13 +288,12 @@ report_emergency_by_count (const struct * 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) +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; @@ -1427,6 +1426,9 @@ deposit_cb (void *cls, dr.deposit_fee = issue->fee_deposit; dr.merchant = *merchant_pub; dr.coin_pub = *coin_pub; + /* NOTE: This is one of the operations we might eventually + want to do in parallel in the background to improve + auditor performance! */ if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_DEPOSIT, &dr.purpose, @@ -2032,10 +2034,8 @@ analyze_coins (void *cls) (qs = TALER_ARL_edb->select_recoup_refresh_above_serial_id ( TALER_ARL_edb->cls, TALER_ARL_esession, - ppc. - last_recoup_refresh_serial_id, - & - recoup_refresh_cb, + ppc.last_recoup_refresh_serial_id, + &recoup_refresh_cb, &cc))) { GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); @@ -2342,10 +2342,6 @@ main (int argc, "KEY", "public key of the exchange (Crockford base32 encoded)", &TALER_ARL_master_pub), - GNUNET_GETOPT_option_flag ('r', - "TALER_ARL_restart", - "TALER_ARL_restart audit from the beginning (required on first run)", - &TALER_ARL_restart), GNUNET_GETOPT_option_timetravel ('T', "timetravel"), GNUNET_GETOPT_OPTION_END @@ -2356,14 +2352,14 @@ main (int argc, away and skip #TALER_OS_init(), which we do need */ (void) TALER_project_data_default (); GNUNET_assert (GNUNET_OK == - GNUNET_log_setup ("taler-auditor", + GNUNET_log_setup ("taler-helper-auditor-coins", "MESSAGE", NULL)); if (GNUNET_OK != GNUNET_PROGRAM_run (argc, argv, - "taler-auditor", - "Audit Taler exchange database", + "taler-helper-auditor-coins", + "Audit Taler coin processing", options, &run, NULL)) diff --git a/src/auditor/taler-helper-auditor-deposits.c b/src/auditor/taler-helper-auditor-deposits.c index 00042159c..1ca8b4f3c 100644 --- a/src/auditor/taler-helper-auditor-deposits.c +++ b/src/auditor/taler-helper-auditor-deposits.c @@ -342,10 +342,6 @@ main (int argc, "KEY", "public key of the exchange (Crockford base32 encoded)", &TALER_ARL_master_pub), - GNUNET_GETOPT_option_flag ('r', - "TALER_ARL_restart", - "TALER_ARL_restart audit from the beginning (required on first run)", - &TALER_ARL_restart), GNUNET_GETOPT_option_timetravel ('T', "timetravel"), GNUNET_GETOPT_OPTION_END @@ -356,13 +352,13 @@ main (int argc, away and skip #TALER_OS_init(), which we do need */ (void) TALER_project_data_default (); GNUNET_assert (GNUNET_OK == - GNUNET_log_setup ("taler-auditor-deposits", + GNUNET_log_setup ("taler-helper-auditor-deposits", "MESSAGE", NULL)); if (GNUNET_OK != GNUNET_PROGRAM_run (argc, argv, - "taler-auditor-deposits", + "taler-helper-auditor-deposits", "Audit Taler exchange database for deposit confirmation consistency", options, &run, @@ -372,4 +368,4 @@ main (int argc, } -/* end of taler-auditor-deposits.c */ +/* end of taler-helper-auditor-deposits.c */ diff --git a/contrib/render.py b/src/auditor/taler-helper-auditor-render.py index cd50f7164..4b086cb62 100755 --- a/contrib/render.py +++ b/src/auditor/taler-helper-auditor-render.py @@ -45,8 +45,6 @@ jsonData4 = json.load(jsonFile4) jsonFile5 = open (sys.argv[5], 'r') jsonData5 = json.load(jsonFile5) -jsonFile6 = open (sys.argv[6], 'r') -jsonData6 = json.load(jsonFile6) jinjaEnv = jinja2.Environment(loader=StdinLoader(), lstrip_blocks=True, @@ -55,4 +53,4 @@ jinjaEnv = jinja2.Environment(loader=StdinLoader(), autoescape=False) tmpl = jinjaEnv.get_template('stdin'); -print(tmpl.render(data = jsonData1, wire = jsonData2, aggregation = jsonData3, coins = jsonData4, deposits = jsonData5, reserves = jsonData6)) +print(tmpl.render(aggregation = jsonData1, coins = jsonData2, deposits = jsonData3, reserves = jsonData4, wire = jsonData5)) diff --git a/src/auditor/taler-helper-auditor-reserves.c b/src/auditor/taler-helper-auditor-reserves.c index 335eb899e..2e9385694 100644 --- a/src/auditor/taler-helper-auditor-reserves.c +++ b/src/auditor/taler-helper-auditor-reserves.c @@ -529,7 +529,7 @@ handle_reserve_out (void *cls, /* check that execution date is within withdraw range for denom_pub */ valid_start = GNUNET_TIME_absolute_ntoh (issue->start); expire_withdraw = GNUNET_TIME_absolute_ntoh (issue->expire_withdraw); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Checking withdraw timing: %llu, expire: %llu, timing: %llu\n", (unsigned long long) valid_start.abs_value_us, (unsigned long long) expire_withdraw.abs_value_us, @@ -1641,10 +1641,6 @@ main (int argc, "KEY", "public key of the exchange (Crockford base32 encoded)", &TALER_ARL_master_pub), - GNUNET_GETOPT_option_flag ('r', - "TALER_ARL_restart", - "TALER_ARL_restart audit from the beginning (required on first run)", - &TALER_ARL_restart), GNUNET_GETOPT_option_timetravel ('T', "timetravel"), GNUNET_GETOPT_OPTION_END @@ -1655,13 +1651,13 @@ main (int argc, away and skip #TALER_OS_init(), which we do need */ (void) TALER_project_data_default (); GNUNET_assert (GNUNET_OK == - GNUNET_log_setup ("taler-auditor-reserves", + GNUNET_log_setup ("taler-helper-auditor-reserves", "MESSAGE", NULL)); if (GNUNET_OK != GNUNET_PROGRAM_run (argc, argv, - "taler-auditor-reserves", + "taler-helper-auditor-reserves", "Audit Taler exchange reserve handling", options, &run, diff --git a/src/auditor/taler-helper-auditor-wire.c b/src/auditor/taler-helper-auditor-wire.c index bc982655e..acda4f2ff 100644 --- a/src/auditor/taler-helper-auditor-wire.c +++ b/src/auditor/taler-helper-auditor-wire.c @@ -451,17 +451,6 @@ do_shutdown (void *cls) struct WireAccount *wa; (void) cls; - if (NULL != ctx) - { - GNUNET_CURL_fini (ctx); - ctx = NULL; - } - if (NULL != rc) - { - GNUNET_CURL_gnunet_rc_destroy (rc); - rc = NULL; - } - if (NULL != report_row_inconsistencies) { json_t *report; @@ -608,6 +597,16 @@ do_shutdown (void *cls) GNUNET_free (wa->section_name); GNUNET_free (wa); } + if (NULL != ctx) + { + GNUNET_CURL_fini (ctx); + ctx = NULL; + } + if (NULL != rc) + { + GNUNET_CURL_gnunet_rc_destroy (rc); + rc = NULL; + } } @@ -874,7 +873,7 @@ wire_missing_cb (void *cls, * (based on deposits) have indeed happened. */ static void -check_for_required_transfers () +check_for_required_transfers (void) { struct GNUNET_TIME_Absolute next_timestamp; enum GNUNET_DB_QueryStatus qs; @@ -1932,11 +1931,24 @@ reserve_closed_cb (void *cls, * @return transaction status code */ static enum GNUNET_DB_QueryStatus -begin_transaction (void *cls) +begin_transaction (void) { int ret; - (void) cls; + TALER_ARL_esession = TALER_ARL_edb->get_session (TALER_ARL_edb->cls); + if (NULL == TALER_ARL_esession) + { + fprintf (stderr, + "Failed to initialize exchange session.\n"); + return GNUNET_SYSERR; + } + TALER_ARL_asession = TALER_ARL_adb->get_session (TALER_ARL_adb->cls); + if (NULL == TALER_ARL_asession) + { + fprintf (stderr, + "Failed to initialize auditor session.\n"); + return GNUNET_SYSERR; + } ret = TALER_ARL_adb->start (TALER_ARL_adb->cls, TALER_ARL_asession); if (GNUNET_OK != ret) @@ -1987,14 +1999,13 @@ begin_transaction (void *cls) if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qsx_gwap) { GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, - _ ( - "First analysis using this auditor, starting audit from scratch\n")); + "First analysis of with wire auditor, starting audit from scratch\n"); } else { start_pp = pp; GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Resuming audit at %s / %llu\n", + "Resuming wire audit at %s / %llu\n", GNUNET_STRINGS_absolute_time_to_string (pp.last_timestamp), (unsigned long long) pp.last_reserve_close_uuid); } @@ -2168,9 +2179,8 @@ run (void *cls, TALER_EXCHANGEDB_find_accounts (TALER_ARL_cfg, &process_account_cb, NULL); - if (GNUNET_OK != - TALER_ARL_setup_sessions_and_run (&begin_transaction, - NULL)) + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != + begin_transaction ()) { global_ret = 1; GNUNET_SCHEDULER_shutdown (); @@ -2179,8 +2189,9 @@ run (void *cls, /** - * The main function of the database initialization tool. - * Used to initialize the Taler Exchange's database. + * 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 @@ -2196,10 +2207,6 @@ main (int argc, "KEY", "public key of the exchange (Crockford base32 encoded)", &TALER_ARL_master_pub), - GNUNET_GETOPT_option_flag ('r', - "TALER_ARL_restart", - "TALER_ARL_restart audit from the beginning (required on first run)", - &TALER_ARL_restart), GNUNET_GETOPT_option_timetravel ('T', "timetravel"), GNUNET_GETOPT_OPTION_END @@ -2210,13 +2217,13 @@ main (int argc, away and skip #TALER_OS_init(), which we do need */ (void) TALER_project_data_default (); GNUNET_assert (GNUNET_OK == - GNUNET_log_setup ("taler-wire-auditor", + GNUNET_log_setup ("taler-helper-auditor-wire", "MESSAGE", NULL)); if (GNUNET_OK != GNUNET_PROGRAM_run (argc, argv, - "taler-wire-auditor", + "taler-helper-auditor-wire", "Audit exchange database for consistency with the bank's wire transfers", options, &run, diff --git a/src/auditor/test-auditor.sh b/src/auditor/test-auditor.sh index 0f76608b7..7ace0008e 100755 --- a/src/auditor/test-auditor.sh +++ b/src/auditor/test-auditor.sh @@ -80,30 +80,25 @@ function audit_only () { # Run the auditor! echo -n "Running audit(s) ..." - # NOTE: this should be removed soon... - $VALGRIND taler-auditor -L DEBUG -r -c $CONF -m $MASTER_PUB > test-audit.json 2> test-audit.log || exit_fail "auditor failed" - echo -n "." - # Also do incremental run - $VALGRIND taler-auditor -L DEBUG -c $CONF -m $MASTER_PUB > test-audit-inc.json 2> test-audit-inc.log || exit_fail "auditor failed" - echo -n "." - - $VALGRIND taler-helper-auditor-aggregation -L DEBUG -r -c $CONF -m $MASTER_PUB > test-audit-aggregation.json 2> test-audit-aggregation.log || exit_fail "aggregation audit failed" + # Restart so that first run is always fresh, and second one is incremental + taler-auditor-dbinit -r -c $CONF + $VALGRIND taler-helper-auditor-aggregation -L DEBUG -c $CONF -m $MASTER_PUB > test-audit-aggregation.json 2> test-audit-aggregation.log || exit_fail "aggregation audit failed" echo -n "." $VALGRIND taler-helper-auditor-aggregation -L DEBUG -c $CONF -m $MASTER_PUB > test-audit-aggregation-inc.json 2> test-audit-aggregation-inc.log || exit_fail "incremental aggregation audit failed" echo -n "." - $VALGRIND taler-helper-auditor-coins -L DEBUG -r -c $CONF -m $MASTER_PUB > test-audit-coins.json 2> test-audit-coins.log || exit_fail "coin audit failed" + $VALGRIND taler-helper-auditor-coins -L DEBUG -c $CONF -m $MASTER_PUB > test-audit-coins.json 2> test-audit-coins.log || exit_fail "coin audit failed" echo -n "." $VALGRIND taler-helper-auditor-coins -L DEBUG -c $CONF -m $MASTER_PUB > test-audit-coins-inc.json 2> test-audit-coins-inc.log || exit_fail "incremental coin audit failed" echo -n "." - $VALGRIND taler-helper-auditor-deposits -L DEBUG -r -c $CONF -m $MASTER_PUB > test-audit-deposits.json 2> test-audit-deposits.log || exit_fail "deposits audit failed" + $VALGRIND taler-helper-auditor-deposits -L DEBUG -c $CONF -m $MASTER_PUB > test-audit-deposits.json 2> test-audit-deposits.log || exit_fail "deposits audit failed" echo -n "." $VALGRIND taler-helper-auditor-deposits -L DEBUG -c $CONF -m $MASTER_PUB > test-audit-deposits-inc.json 2> test-audit-deposits-inc.log || exit_fail "incremental deposits audit failed" echo -n "." - $VALGRIND taler-helper-auditor-reserves -L DEBUG -r -c $CONF -m $MASTER_PUB > test-audit-reserves.json 2> test-audit-reserves.log || exit_fail "reserves audit failed" + $VALGRIND taler-helper-auditor-reserves -L DEBUG -c $CONF -m $MASTER_PUB > test-audit-reserves.json 2> test-audit-reserves.log || exit_fail "reserves audit failed" echo -n "." $VALGRIND taler-helper-auditor-reserves -L DEBUG -c $CONF -m $MASTER_PUB > test-audit-reserves-inc.json 2> test-audit-reserves-inc.log || exit_fail "incremental reserves audit failed" echo -n "." - $VALGRIND taler-helper-auditor-wire -L DEBUG -r -c $CONF -m $MASTER_PUB > test-audit-wire.json 2> test-wire-audit.log || exit_fail "wire audit failed" + $VALGRIND taler-helper-auditor-wire -L DEBUG -c $CONF -m $MASTER_PUB > test-audit-wire.json 2> test-wire-audit.log || exit_fail "wire audit failed" echo -n "." $VALGRIND taler-helper-auditor-wire -L DEBUG -c $CONF -m $MASTER_PUB > test-audit-wire-inc.json 2> test-wire-audit-inc.log || exit_fail "wire audit failed" echo -n "." @@ -119,7 +114,7 @@ function post_audit () { wait echo "DONE" echo -n "TeXing ." - ../../contrib/render.py test-audit.json test-audit-wire.json test-audit-aggregation.json test-audit-coins.json test-audit-deposits.json test-audit-reserves.json < ../../contrib/auditor-report.tex.j2 > test-report.tex || exit_fail "Renderer failed" + taler-helper-auditor-render.py test-audit-aggregation.json test-audit-coins.json test-audit-deposits.json test-audit-reserves.json test-audit-wire.json < ../../contrib/auditor-report.tex.j2 > test-report.tex || exit_fail "Renderer failed" echo -n "." timeout 10 pdflatex test-report.tex >/dev/null || exit_fail "pdflatex failed" diff --git a/src/bank-lib/bank_api_credit.c b/src/bank-lib/bank_api_credit.c index 4b6f40bbf..953d3d83b 100644 --- a/src/bank-lib/bank_api_credit.c +++ b/src/bank-lib/bank_api_credit.c @@ -152,7 +152,6 @@ handle_credit_history_finished (void *cls, switch (response_code) { case 0: - GNUNET_break_op (0); ec = TALER_EC_INVALID_RESPONSE; break; case MHD_HTTP_OK: diff --git a/src/exchange/test_taler_exchange_httpd.conf b/src/exchange/test_taler_exchange_httpd.conf index 63b59f905..e7f763e79 100644 --- a/src/exchange/test_taler_exchange_httpd.conf +++ b/src/exchange/test_taler_exchange_httpd.conf @@ -7,6 +7,9 @@ TALER_TEST_HOME = test_taler_exchange_httpd_home/ CURRENCY = EUR CURRENCY_ROUND_UNIT = EUR:0.01 +[auditor] +TINY_AMOUNT = EUR:0.01 + [exchange] # Directory with our terms of service. @@ -65,7 +68,10 @@ PAYTO_URI = "payto://x-taler-bank/localhost:8082/3" WIRE_RESPONSE = ${TALER_CONFIG_HOME}/account-1.json ENABLE_DEBIT = YES ENABLE_CREDIT = YES -TALER_BANK_AUTH_METHOD = NONE +WIRE_GATEWAY_AUTH_METHOD = basic +USERNAME = Exchange +PASSWORD = x +WIRE_GATEWAY_URL = "http://localhost:8082/3/" # Wire fees are specified by wire method |