add test for emergencies, and associated bugfixes to auditor and auditor report

This commit is contained in:
Christian Grothoff 2019-09-28 20:53:44 +02:00
parent e3564de010
commit 160a4ef52c
No known key found for this signature in database
GPG Key ID: 939E6BE1E29FC3CC
3 changed files with 194 additions and 48 deletions

View File

@ -224,15 +224,25 @@ exposure} is the amount of coins in circulation for a particular
denomination and the maximum loss for the exchange from this type of denomination and the maximum loss for the exchange from this type of
compromise. compromise.
{% if (data.emergencies|length() != 0) {% if (data.emergencies|length() != 0) %}
or (data.emergencies_by_count|length() != 0) %} The total risk from emergencies detected by amount is
The total risk from emergencies is {\bf {{ data.emergencies_risk_by_amount }} }.
{\bf {{ data.emergencies_risk_total }} } The total loss from emergencies detected by amount is
{\bf {{ data.emergencies_loss }} }.
{% endif %}
{% if (data.emergencies_by_count|length() != 0) %}
The total risk from emergencies detected by counting coins is
{\bf {{ data.emergencies_risk_by_count }} }
The total loss from emergencies detected by counting coins could be up to
{\bf {{ data.emergencies_loss_by_count }} }.
{% endif %} {% endif %}
\subsubsection{Emergencies by counting coins} \subsubsection{Emergencies by counting coins}
% Table generation tested by testcase #18 in test-auditor.sh
{% if data.emergencies_by_count|length() == 0 %} {% if data.emergencies_by_count|length() == 0 %}
{\bf No emergencies detected by counting coins.} {\bf No emergencies detected by counting coins.}
{% else %} {% else %}
@ -251,7 +261,7 @@ The total risk from emergencies is
\label{table:emergencies_coin_counting} \label{table:emergencies_coin_counting}
\endlastfoot \endlastfoot
{% for item in data.emergencies_by_count %} {% for item in data.emergencies_by_count %}
\multicolumn{4}{l}{ {\tt \truncate{\textwidth}{ {{ item.denompub_hash }} } } } \\ \multicolumn{4}{l}{ {\tt \truncate{0.95\textwidth}{ {{ item.denompub_hash }} } } } \\
\nopagebreak \nopagebreak
{{ item.value }} & {{ item.value }} &
{\tiny \begin{tabular}{c} {\tiny \begin{tabular}{c}
@ -264,8 +274,11 @@ The total risk from emergencies is
\end{longtable} \end{longtable}
{% endif %} {% endif %}
\subsubsection{Emergencies by value deposited} \subsubsection{Emergencies by value deposited}
% Table generation tested by testcase #18 in test-auditor.sh
{% if data.emergencies|length() == 0 %} {% if data.emergencies|length() == 0 %}
{\bf No emergencies by value detected.} {\bf No emergencies by value detected.}
{% else %} {% else %}
@ -284,7 +297,7 @@ The total risk from emergencies is
\label{table:emergencies} \label{table:emergencies}
\endlastfoot \endlastfoot
{% for item in data.emergencies %} {% for item in data.emergencies %}
\multicolumn{4}{l}{ {\tt \truncate{\textwidth}{ {{ item.denompub_hash }} } } } \\ \multicolumn{4}{l}{ {\tt \truncate{0.95\textwidth}{ {{ item.denompub_hash }} } } } \\
\nopagebreak \nopagebreak
{{ item.value }} & {{ item.value }} &
{\tiny \begin{tabular}{c} {\tiny \begin{tabular}{c}
@ -306,32 +319,40 @@ Disagreements imply that either the exchange made a loss (sending out
too much money), or screwed a customer (and thus at least needs to fix too much money), or screwed a customer (and thus at least needs to fix
the financial damage done to the customer). the financial damage done to the customer).
% Table generation tested by testcase #18 in test-auditor.sh
{% if data.amount_arithmetic_inconsistencies|length() == 0 %} {% if data.amount_arithmetic_inconsistencies|length() == 0 %}
{\bf No arithmetic problems detected.} {\bf No arithmetic problems detected.}
{% else %} {% else %}
\begin{longtable}{p{3.5cm}|l|r|r} \begin{longtable}{p{3.5cm}|r|r|r|c}
{\bf Operation} & {\bf Table row} & \multicolumn{2}{|c|}{ {\bf Exchange}} & \multicolumn{2}{|c}{ {\bf Auditor}} \\ {\bf Operation} & {\bf Row} & {\bf Exchange} & {\bf Auditor} & {\bf P} \\
\hline \hline \hline \hline
\endfirsthead \endfirsthead
{\bf Operation} & {\bf Table row} & \multicolumn{2}{|c|}{ {\bf Exchange}} & \multicolumn{2}{|c}{ {\bf Auditor}} \\ \hline \hline {\bf Operation} & {\bf Row} & {\bf Exchange} & {\bf Auditor} & {\bf P} \\ \hline \hline
\endhead \endhead
\hline \hline \hline \hline
{\bf Operation} & {\bf Table row} & \multicolumn{2}{|c|}{ {\bf Exchange}} & \multicolumn{2}{|c}{ {\bf Auditor}} \\ {\bf Operation} & {\bf Row} & {\bf Exchange} & {\bf Auditor} & {\bf P} \\
\endfoot \endfoot
\hline \hline \hline \hline
\multicolumn{2}{l|}{ {\bf $\sum$ Deltas (Auditor-Exchange)} } & \multicolumn{2}{l|}{ {\bf $\sum$ Deltas (Auditor-Exchange)} } &
+ {{ data.total_arithmetic_delta_plus }} & + {{ data.total_arithmetic_delta_plus }} &
- {{ data.total_arithmetic_delta_minus }} \\ - {{ data.total_arithmetic_delta_minus }} & \\
\caption{Arithmetic inconsistencies.} \caption{Arithmetic inconsistencies.}
\label{table:amount:arithmetic:inconsistencies} \label{table:amount:arithmetic:inconsistencies}
\endlastfoot \endlastfoot
{% for item in data.amount_arithmetic_inconsistencies %} {% for item in data.amount_arithmetic_inconsistencies %}
{{ item.operation }} & \truncate{3.3cm}{ {\tiny {{ item.operation }} } } &
{{ item.rowid }} & {{ item.rowid }} &
{{ item.exchange }} & {{ item.exchange }} &
{{ item.auditor }} \\ \hline {{ item.auditor }} &
{{ item.profitable }} \\ \hline
{% endfor %} {% endfor %}
\end{longtable} \end{longtable}
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.
{% endif %} {% endif %}

View File

@ -268,10 +268,25 @@ static json_int_t number_missed_deposit_confirmations;
*/ */
static struct TALER_Amount total_missed_deposit_confirmations; 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(). * Total amount reported in all calls to #report_emergency().
*/ */
static struct TALER_Amount reported_emergency_sum; static struct TALER_Amount reported_emergency_risk_by_amount;
/**
* Total amount in losses reported in all calls to #report_emergency().
*/
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. * Expected balance in the escrow account.
@ -419,9 +434,13 @@ report_emergency_by_amount (const struct
"value", "value",
TALER_JSON_from_amount_nbo (&dki->properties.value))); TALER_JSON_from_amount_nbo (&dki->properties.value)));
GNUNET_assert (GNUNET_OK == GNUNET_assert (GNUNET_OK ==
TALER_amount_add (&reported_emergency_sum, TALER_amount_add (&reported_emergency_risk_by_amount,
&reported_emergency_sum, &reported_emergency_risk_by_amount,
risk)); risk));
GNUNET_assert (GNUNET_OK ==
TALER_amount_add (&reported_emergency_loss,
&reported_emergency_loss,
loss));
} }
@ -446,6 +465,8 @@ report_emergency_by_count (const struct
uint64_t num_known, uint64_t num_known,
const struct TALER_Amount *risk) const struct TALER_Amount *risk)
{ {
struct TALER_Amount denom_value;
report (report_emergencies_by_count, report (report_emergencies_by_count,
json_pack ("{s:o, s:I, s:I, s:o, s:o, s:o, s:o}", json_pack ("{s:o, s:I, s:I, s:o, s:o, s:o, s:o}",
"denompub_hash", "denompub_hash",
@ -463,9 +484,17 @@ report_emergency_by_count (const struct
"value", "value",
TALER_JSON_from_amount_nbo (&dki->properties.value))); TALER_JSON_from_amount_nbo (&dki->properties.value)));
GNUNET_assert (GNUNET_OK == GNUNET_assert (GNUNET_OK ==
TALER_amount_add (&reported_emergency_sum, TALER_amount_add (&reported_emergency_risk_by_count,
&reported_emergency_sum, &reported_emergency_risk_by_count,
risk)); risk));
TALER_amount_ntoh (&denom_value,
&dki->properties.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));
} }
@ -1650,7 +1679,7 @@ verify_reserve_balance (void *cls,
ret = GNUNET_SYSERR; ret = GNUNET_SYSERR;
rc->qs = qs; rc->qs = qs;
} }
cleanup: cleanup:
GNUNET_assert (GNUNET_YES == GNUNET_assert (GNUNET_YES ==
GNUNET_CONTAINER_multihashmap_remove (rc->reserves, GNUNET_CONTAINER_multihashmap_remove (rc->reserves,
key, key,
@ -3859,16 +3888,28 @@ refresh_session_cb (void *cls,
{ {
dso->denom_balance = tmp; dso->denom_balance = tmp;
} }
if (GNUNET_SYSERR == if (-1 == TALER_amount_cmp (&total_escrow_balance,
TALER_amount_subtract (&total_escrow_balance, amount_with_fee))
&total_escrow_balance,
amount_with_fee))
{ {
/* This should not be possible, unless the AUDITOR /* This can theoretically happen if for example the exchange
has a bug in tracking total balance. */ never issued any coins (i.e. escrow balance is zero), but
GNUNET_break (0); accepted a forged coin (i.e. emergency situation after
cc->qs = GNUNET_DB_STATUS_HARD_ERROR; private key compromise). In that case, we cannot even
return GNUNET_SYSERR; 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, GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
@ -4030,16 +4071,29 @@ deposit_cb (void *cls,
{ {
ds->denom_balance = tmp; ds->denom_balance = tmp;
} }
if (GNUNET_SYSERR ==
TALER_amount_subtract (&total_escrow_balance, if (-1 == TALER_amount_cmp (&total_escrow_balance,
&total_escrow_balance, amount_with_fee))
amount_with_fee))
{ {
/* This should not be possible, unless the AUDITOR /* This can theoretically happen if for example the exchange
has a bug in tracking total balance. */ never issued any coins (i.e. escrow balance is zero), but
GNUNET_break (0); accepted a forged coin (i.e. emergency situation after
cc->qs = GNUNET_DB_STATUS_HARD_ERROR; private key compromise). In that case, we cannot even
return GNUNET_SYSERR; 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, GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
@ -5094,7 +5148,16 @@ run (void *cls,
"Starting audit\n"); "Starting audit\n");
GNUNET_assert (GNUNET_OK == GNUNET_assert (GNUNET_OK ==
TALER_amount_get_zero (currency, TALER_amount_get_zero (currency,
&reported_emergency_sum)); &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 == GNUNET_assert (GNUNET_OK ==
TALER_amount_get_zero (currency, TALER_amount_get_zero (currency,
&total_escrow_balance)); &total_escrow_balance));
@ -5213,7 +5276,8 @@ run (void *cls,
" 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:I,"
" s:o, s:o, s:o }", " s:o, s:o, s:o, s:o, s:o,"
" s:o }",
/* blocks of 5 for easier counting/matching to format string */ /* blocks of 5 for easier counting/matching to format string */
/* block */ /* block */
"reserve_balance_insufficient_inconsistencies", "reserve_balance_insufficient_inconsistencies",
@ -5248,8 +5312,9 @@ run (void *cls,
TALER_JSON_from_amount (&income_fee_total), TALER_JSON_from_amount (&income_fee_total),
"emergencies", "emergencies",
report_emergencies, report_emergencies,
"emergencies_risk_total", "emergencies_risk_by_amount",
TALER_JSON_from_amount (&reported_emergency_sum), TALER_JSON_from_amount (
&reported_emergency_risk_by_amount),
"reserve_not_closed_inconsistencies", "reserve_not_closed_inconsistencies",
report_reserve_not_closed_inconsistencies, report_reserve_not_closed_inconsistencies,
/* block */ /* block */
@ -5309,7 +5374,15 @@ run (void *cls,
"total_payback_loss", "total_payback_loss",
TALER_JSON_from_amount (&total_payback_loss), TALER_JSON_from_amount (&total_payback_loss),
"emergencies_by_count", "emergencies_by_count",
report_emergencies_by_count report_emergencies_by_count,
"emergencies_risk_by_count",
TALER_JSON_from_amount (
&reported_emergency_risk_by_count),
"emergencies_loss",
TALER_JSON_from_amount (&reported_emergency_loss),
/* block */
"emergencies_loss_by_count",
TALER_JSON_from_amount (&reported_emergency_loss_by_count)
); );
GNUNET_break (NULL != report); GNUNET_break (NULL != report);
json_dumpf (report, json_dumpf (report,

View File

@ -83,7 +83,7 @@ function audit_only () {
# Cleanup to run after the auditor # Cleanup to run after the auditor
function post_audit () { function post_audit () {
kill -9 `jobs -p` >/dev/null 2>/dev/null || true kill -TERM `jobs -p` >/dev/null 2>/dev/null || true
echo -n "TeXing ." echo -n "TeXing ."
../../contrib/render.py test-audit.json test-wire-audit.json < ../../contrib/auditor-report.tex.j2 > test-report.tex || exit_fail "Renderer failed" ../../contrib/render.py test-audit.json test-wire-audit.json < ../../contrib/auditor-report.tex.j2 > test-report.tex || exit_fail "Renderer failed"
@ -980,6 +980,58 @@ echo "UPDATE app_banktransaction SET date='${OLD_DATE}' WHERE id='${OLD_ID}';" |
# Test where we trigger an emergency.
function test_18() {
echo "===========18: emergency================="
echo "DELETE FROM reserves_out;" | psql -Aqt $DB
run_audit
echo -n "Testing emergency detection... "
jq -e .reserve_balance_summary_wrong_inconsistencies[0] < test-audit.json > /dev/null || exit_fail "Reserve balance inconsistency not detected"
jq -e .emergencies[0] < test-audit.json > /dev/null || exit_fail "Emergency not detected"
jq -e .emergencies_by_count[0] < test-audit.json > /dev/null || exit_fail "Emergency by count not detected"
jq -e .amount_arithmetic_inconsistencies[0] < test-audit.json > /dev/null || exit_fail "Escrow balance calculation impossibility not detected"
echo PASS
echo -n "Testing risk/loss calculation... "
AMOUNT=`jq -r .emergencies_risk_by_amount < test-audit.json`
if test "x$AMOUNT" == "xTESTKUDOS:0"
then
exit_fail "Reported amount wrong: $AMOUNT"
fi
AMOUNT=`jq -r .emergencies_risk_by_count < test-audit.json`
if test "x$AMOUNT" == "xTESTKUDOS:0"
then
exit_fail "Reported amount wrong: $AMOUNT"
fi
AMOUNT=`jq -r .emergencies_loss < test-audit.json`
if test "x$AMOUNT" == "xTESTKUDOS:0"
then
exit_fail "Reported amount wrong: $AMOUNT"
fi
AMOUNT=`jq -r .emergencies_loss_by_count < test-audit.json`
if test "x$AMOUNT" == "xTESTKUDOS:0"
then
exit_fail "Reported amount wrong: $AMOUNT"
fi
echo PASS
# cannot easily undo broad DELETE operation, hence full reload
echo -n "Reloading database ..."
full_reload
echo "DONE"
}
# ************************************************** # **************************************************
# FIXME: Add more tests here! :-) # FIXME: Add more tests here! :-)
# Specifically: # Specifically: