add auditing of deposit confirmations to auditor (#5447)

This commit is contained in:
Christian Grothoff 2018-11-04 17:36:56 +01:00
parent 332341cb7b
commit ac850bfcd2
No known key found for this signature in database
GPG Key ID: 939E6BE1E29FC3CC
7 changed files with 490 additions and 17 deletions

View File

@ -148,6 +148,61 @@ the tiny threshold. Below, we report {\em non-tiny} wire transfers that are lagg
{% endif %}
\section{Deposit confirmation lag}
This section analyzes the lag, which is by how much the exchange's
database reporting is behind in providing us with information about
deposit confirmations. Merchants probabilisitcally report deposit
confirmations to the auditor directly, so if the exchange is slow at
synchronizing its database with the auditor, some deposit
confirmations may be known at the auditor only directly. However, any
delta not accounted for by database synchronization delays is an
indicator of a malicious exchange (or online singing key compromise)
and should be answered by revoking the exchange's online siging keys.
% FIXME: reference PhD thesis?
The total amount the exchange currently lags behind is
{\bf {{ data.missing_deposit_confirmation_total.value }}.{{ data.missing_deposit_confirmation_total.fraction }}
{{ data.missing_deposit_confirmation_total.currency }}} or
{\bf {{ data.total_missed_deposit_confirmations}} } deposit confirmations.
Note that some lag is perfectly normal.
Below, we report {\em all} deposit confirmations that are lagging behind.
{% if data.deposit_confirmation_inconsistencies|length() == 0 %}
{\bf No deposit confirmations that are lagging behind detected.}
{% else %}
\begin{longtable}{p{1.5cm}|rl|c|rl}
{\bf Timestamp} & {\bf Amount} & {\bf Row} \\
\multicolumn{3}{l}{\bf Target account} \\ \hline \hline
\endfirsthead
{\bf Timestamp} & {\bf Amount} & {\bf Row} \\
\multicolumn{3}{l}{\bf Target account} \\ \hline \hline
\endhead
\hline \hline
{\bf Timestamp} & {\bf Amount} & {\bf Row} \\
\multicolumn{3}{l}{\bf Target account} \\
\endfoot
\hline \hline
{\bf Timestamp} & {\bf Amount} & {\bf Row} \\
\multicolumn{3}{l}{\bf Target account} \\
\caption{Missing deposit confirmations.}
\label{table:missing_dc}
\endlastfoot
{% for item in data.deposit_confirmation_inconsistencies %}
&
{{ item.timestamp }} &
{{ item.amount.value }}.{{ item.amount.fraction }} &
{{ item.amount.currency }} &
{{ item.row }} \\
\nopagebreak
\multicolumn{3}{l}{ {\tt {{ item.account }} } } \\ \hline
{% endfor %}
\end{longtable}
{% endif %}
\section{Major irregularities}
This section describes the possible major irregularities that the

View File

@ -182,6 +182,11 @@ static struct TALER_Amount total_balance_reserve_not_closed;
*/
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.
@ -235,6 +240,16 @@ static struct TALER_Amount total_arithmetic_delta_plus;
*/
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().
*/
@ -4036,6 +4051,202 @@ analyze_coins (void *cls)
}
/* *************************** 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)
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:s, s:o, s:I, s:o}",
"timestamp",
GNUNET_STRINGS_absolute_time_to_string (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;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"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;
}
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 ****************** */
/**
@ -4151,6 +4362,8 @@ setup_sessions_and_run ()
NULL);
transact (&analyze_coins,
NULL);
transact (&analyze_deposit_confirmations,
NULL);
}
@ -4347,6 +4560,8 @@ run (void *cls,
(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 !=
@ -4383,7 +4598,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:I,"
" s:o }",
/* blocks of 5 for easier counting/matching to format string */
/* block */
"reserve_balance_insufficient_inconsistencies",
@ -4457,7 +4673,14 @@ run (void *cls,
"total_refresh_hanging",
TALER_JSON_from_amount (&total_refresh_hanging),
"refresh_hanging",
report_refreshs_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));
GNUNET_break (NULL != report);
json_dumpf (report,
stdout,

View File

@ -390,6 +390,7 @@ postgres_create_tables (void *cls)
we must check that the exchange reported these properly. */
GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS deposit_confirmations "
"(master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE"
",serial_id BIGSERIAL UNIQUE"
",h_contract_terms BYTEA CHECK (LENGTH(h_contract_terms)=64)"
",h_wire BYTEA CHECK (LENGTH(h_wire)=64)"
",timestamp INT8 NOT NULL"
@ -567,6 +568,25 @@ postgres_prepare (PGconn *db_conn)
",master_sig" /* master_sig could be normalized... */
") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13);",
11),
/* Used in #postgres_get_deposit_confirmations() */
GNUNET_PQ_make_prepare ("auditor_deposit_confirmation_select",
"SELECT"
" h_contract_terms"
",h_wire"
",timestamp"
",refund_deadline"
",amount_without_fee_val"
",amount_without_fee_frac"
",amount_without_fee_curr"
",coin_pub"
",merchant_pub"
",exchange_sig"
",exchange_pub"
",master_sig" /* master_sig could be normalized... */
" FROM deposit_confirmations"
" WHERE master_pub=$1"
" AND serial_id>$2",
2),
/* Used in #postgres_update_auditor_progress_reserve() */
GNUNET_PQ_make_prepare ("auditor_progress_update_reserve",
"UPDATE auditor_progress_reserve SET "
@ -1416,6 +1436,144 @@ postgres_insert_deposit_confirmation (void *cls,
}
/**
* Closure for #deposit_confirmation_cb().
*/
struct DepositConfirmationContext
{
/**
* Master public key that is being used.
*/
const struct TALER_MasterPublicKeyP *master_pub;
/**
* Function to call for each deposit confirmation.
*/
TALER_AUDITORDB_DepositConfirmationCallback cb;
/**
* Closure for @e cb
*/
void *cb_cls;
/**
* Query status to return.
*/
enum GNUNET_DB_QueryStatus qs;
};
/**
* Helper function for #postgres_get_deposit_confirmations().
* To be called with the results of a SELECT statement
* that has returned @a num_results results.
*
* @param cls closure of type `struct DepositConfirmationContext *`
* @param result the postgres result
* @param num_result the number of results in @a result
*/
static void
deposit_confirmation_cb (void *cls,
PGresult *result,
unsigned int num_results)
{
struct DepositConfirmationContext *dcc = cls;
for (unsigned int i = 0; i < num_results; i++)
{
uint64_t serial_id;
struct TALER_AUDITORDB_DepositConfirmation dc = {
.master_public_key = *dcc->master_pub
};
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_uint64 ("serial_id",
&serial_id),
GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
&dc.h_contract_terms),
GNUNET_PQ_result_spec_auto_from_type ("h_wire",
&dc.h_wire),
GNUNET_PQ_result_spec_absolute_time ("timetamp",
&dc.timestamp),
GNUNET_PQ_result_spec_absolute_time ("refund_deadline",
&dc.refund_deadline),
TALER_PQ_result_spec_amount ("amount_without_fee",
&dc.amount_without_fee),
GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
&dc.coin_pub),
GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
&dc.merchant),
GNUNET_PQ_result_spec_auto_from_type ("exchange_sig",
&dc.exchange_sig),
GNUNET_PQ_result_spec_auto_from_type ("exchange_pub",
&dc.exchange_pub),
GNUNET_PQ_result_spec_auto_from_type ("master_sig",
&dc.master_sig),
GNUNET_PQ_result_spec_end
};
if (GNUNET_OK !=
GNUNET_PQ_extract_result (result,
rs,
i))
{
GNUNET_break (0);
dcc->qs = GNUNET_DB_STATUS_HARD_ERROR;
return;
}
dcc->qs = i + 1;
dcc->cb (dcc->cb_cls,
serial_id,
&dc);
}
}
/**
* Get information about deposit confirmations from the database.
*
* @param cls the @e cls of this struct with the plugin-specific state
* @param session connection to the database
* @param master_pub for which exchange do we want to get deposit confirmations
* @param start_id row/serial ID where to start the iteration (0 from
* the start, exclusive, i.e. serial_ids must start from 1)
* @param cb function to call with results
* @param cb_cls closure for @a cb
* @return query result status
*/
static enum GNUNET_DB_QueryStatus
postgres_get_deposit_confirmations (void *cls,
struct TALER_AUDITORDB_Session *session,
const struct TALER_MasterPublicKeyP *master_public_key,
uint64_t start_id,
TALER_AUDITORDB_DepositConfirmationCallback cb,
void *cb_cls)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (master_public_key),
GNUNET_PQ_query_param_uint64 (&start_id),
GNUNET_PQ_query_param_end
};
struct DepositConfirmationContext dcc = {
.master_pub = master_public_key,
.cb = cb,
.cb_cls = cb_cls
};
enum GNUNET_DB_QueryStatus qs;
qs = GNUNET_PQ_eval_prepared_multi_select (session->conn,
"auditor_deposit_confirmation_select",
params,
&deposit_confirmation_cb,
&dcc);
if (qs > 0)
return dcc.qs;
GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs);
return qs;
}
/**
* Insert information about a denomination key and in particular
* the properties (value, fees, expiration times) the coins signed
@ -3264,6 +3422,7 @@ libtaler_plugin_auditordb_postgres_init (void *cls)
plugin->list_exchanges = &postgres_list_exchanges;
plugin->insert_exchange_signkey = &postgres_insert_exchange_signkey;
plugin->insert_deposit_confirmation = &postgres_insert_deposit_confirmation;
plugin->get_deposit_confirmations = &postgres_get_deposit_confirmations;
plugin->select_denomination_info = &postgres_select_denomination_info;
plugin->insert_denomination_info = &postgres_insert_denomination_info;

View File

@ -142,7 +142,8 @@ deposit_transaction (void *cls,
qs = TEH_plugin->have_deposit (TEH_plugin->cls,
session,
deposit);
deposit,
GNUNET_YES /* check refund deadline */);
if (qs < 0)
{
if (GNUNET_DB_STATUS_HARD_ERROR == qs)

View File

@ -2730,6 +2730,7 @@ postgres_get_reserve_history (void *cls,
* @param cls the `struct PostgresClosure` with the plugin-specific state
* @param session database connection
* @param deposit deposit to search for
* @param check_extras wether to check extra fields match or not
* @return 1 if we know this operation,
* 0 if this exact deposit is unknown to us,
* otherwise transaction error status
@ -2737,7 +2738,8 @@ postgres_get_reserve_history (void *cls,
static enum GNUNET_DB_QueryStatus
postgres_have_deposit (void *cls,
struct TALER_EXCHANGEDB_Session *session,
const struct TALER_EXCHANGEDB_Deposit *deposit)
const struct TALER_EXCHANGEDB_Deposit *deposit,
int check_extras)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (&deposit->coin.coin_pub),
@ -2755,8 +2757,6 @@ postgres_have_deposit (void *cls,
&deposit2.refund_deadline),
TALER_PQ_result_spec_absolute_time ("wire_deadline",
&deposit2.wire_deadline),
GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
&deposit2.h_contract_terms),
GNUNET_PQ_result_spec_auto_from_type ("h_wire",
&deposit2.h_wire),
GNUNET_PQ_result_spec_end
@ -2774,18 +2774,16 @@ postgres_have_deposit (void *cls,
return qs;
/* Now we check that the other information in @a deposit
also matches, and if not report inconsistencies. */
if ( (0 != TALER_amount_cmp (&deposit->amount_with_fee,
&deposit2.amount_with_fee)) ||
(deposit->timestamp.abs_value_us !=
deposit2.timestamp.abs_value_us) ||
if ( ( (check_extras) &&
( (0 != TALER_amount_cmp (&deposit->amount_with_fee,
&deposit2.amount_with_fee)) ||
(deposit->timestamp.abs_value_us !=
deposit2.timestamp.abs_value_us) ) ) ||
(deposit->refund_deadline.abs_value_us !=
deposit2.refund_deadline.abs_value_us) ||
(0 != memcmp (&deposit->h_contract_terms,
&deposit2.h_contract_terms,
sizeof (struct GNUNET_HashCode))) ||
deposit2.refund_deadline.abs_value_us) ||
(0 != memcmp (&deposit->h_wire,
&deposit2.h_wire,
sizeof (struct GNUNET_HashCode))) )
&deposit2.h_wire,
sizeof (struct GNUNET_HashCode)) ) )
{
/* Inconsistencies detected! Does not match! (We might want to
expand the API with a 'get_deposit' function to return the

View File

@ -345,6 +345,20 @@ struct TALER_AUDITORDB_DepositConfirmation
};
/**
* Function called with deposit confirmations stored in
* the auditor's database.
*
* @param cls closure
* @param serial_id location of the @a dc in the database
* @param dc the deposit confirmation itself
*/
typedef void
(*TALER_AUDITORDB_DepositConfirmationCallback)(void *cls,
uint64_t serial_id,
const struct TALER_AUDITORDB_DepositConfirmation *dc);
/**
* Handle for one session with the database.
*/
@ -525,6 +539,27 @@ struct TALER_AUDITORDB_Plugin
const struct TALER_AUDITORDB_DepositConfirmation *dc);
/**
* Get information about a deposit confirmations from the database.
*
* @param cls the @e cls of this struct with the plugin-specific state
* @param session connection to the database
* @param master_pub for which exchange do we want to get deposit confirmations
* @param start_id row/serial ID where to start the iteration (0 from
* the start, exclusive, i.e. serial_ids must start from 1)
* @param cb function to call with results
* @param cb_cls closure for @a cb
* @return query result status
*/
enum GNUNET_DB_QueryStatus
(*get_deposit_confirmations) (void *cls,
struct TALER_AUDITORDB_Session *session,
const struct TALER_MasterPublicKeyP *master_public_key,
uint64_t start_id,
TALER_AUDITORDB_DepositConfirmationCallback cb,
void *cb_cls);
/**
* Insert information about a denomination key and in particular
* the properties (value, fees, expiration times) the coins signed

View File

@ -1431,6 +1431,7 @@ struct TALER_EXCHANGEDB_Plugin
* @param cls the @e cls of this struct with the plugin-specific state
* @param session database connection
* @param deposit deposit to search for
* @param check_extras wether to check extra fields or not
* @return 1 if we know this operation,
* 0 if this exact deposit is unknown to us,
* otherwise transaction error status
@ -1438,7 +1439,8 @@ struct TALER_EXCHANGEDB_Plugin
enum GNUNET_DB_QueryStatus
(*have_deposit) (void *cls,
struct TALER_EXCHANGEDB_Session *session,
const struct TALER_EXCHANGEDB_Deposit *deposit);
const struct TALER_EXCHANGEDB_Deposit *deposit,
int check_extras);
/**