check coin history consistency

This commit is contained in:
Christian Grothoff 2020-03-27 09:35:42 +01:00
parent 9445343ec5
commit 62b8ca0bd3
No known key found for this signature in database
GPG Key ID: 939E6BE1E29FC3CC

View File

@ -38,7 +38,7 @@
* Set to a VERY low value here for testing. Practical values may be * Set to a VERY low value here for testing. Practical values may be
* in the millions. * in the millions.
*/ */
#define MAX_COIN_SUMMARIES 4 #define MAX_COIN_HISTORIES 4
/** /**
* Use a 1 day grace period to deal with clocks not being perfectly synchronized. * Use a 1 day grace period to deal with clocks not being perfectly synchronized.
@ -169,6 +169,84 @@ static json_t *report_refreshs_hanging;
*/ */
static struct TALER_Amount total_refresh_hanging; static struct TALER_Amount total_refresh_hanging;
/**
* Coin and associated transaction history.
*/
struct CoinHistory
{
/**
* Public key of the coin.
*/
struct TALER_CoinSpendPublicKeyP coin_pub;
/**
* The transaction list for the @a coin_pub.
*/
struct TALER_EXCHANGEDB_TransactionList *tl;
};
/**
* Array of transaction histories for coins. The index is based on the coin's
* public key. Entries are replaced whenever we have a collision.
*/
static struct CoinHistory coin_histories[MAX_COIN_HISTORIES];
/**
* Return the index we should use for @a coin_pub in #coin_histories.
*
* @param coin_pub a coin's public key
* @return index for caching this coin's history in #coin_histories
*/
static unsigned int
coin_history_index (const struct TALER_CoinSpendPublicKeyP *coin_pub)
{
uint32_t i;
memcpy (&i,
coin_pub,
sizeof (i));
return i % MAX_COIN_HISTORIES;
}
/**
* Add a coin history to our in-memory cache.
*
* @param coin_pub public key of the coin to cache
* @param tl history to store
*/
static void
cache_history (const struct TALER_CoinSpendPublicKeyP *coin_pub,
struct TALER_EXCHANGEDB_TransactionList *tl)
{
unsigned int i = coin_history_index (coin_pub);
if (NULL != coin_histories[i].tl)
TALER_ARL_edb->free_coin_transaction_list (TALER_ARL_edb->cls,
coin_histories[i].tl);
coin_histories[i].coin_pub = *coin_pub;
coin_histories[i].tl = tl;
}
/**
* Obtain a coin's history from our in-memory cache.
*
* @param coin_pub public key of the coin to cache
* @return NULL if @a coin_pub is not in the cache
*/
static struct TALER_EXCHANGEDB_TransactionList *
get_cached_history (const struct TALER_CoinSpendPublicKeyP *coin_pub)
{
unsigned int i = coin_history_index (coin_pub);
if (GNUNET_memcmp (coin_pub,
&coin_histories[i].coin_pub))
return coin_histories[i].tl;
return NULL;
}
/* ***************************** Report logic **************************** */ /* ***************************** Report logic **************************** */
@ -362,6 +440,136 @@ report_row_inconsistency (const char *table,
} }
/* ************* Analyze history of a coin ******************** */
/**
* Obtain @a coin_pub's history, verify it, report inconsistencies
* and store the result in our cache.
*
* @param coin_pub public key of the coin to check the history of
* @param rowid a row identifying the transaction
* @param operation operation matching @a rowid
* @param value value of the respective coin's denomination
* @return database status code, negative on failures
*/
static enum GNUNET_DB_QueryStatus
check_coin_history (const struct TALER_CoinSpendPublicKeyP *coin_pub,
uint64_t rowid,
const char *operation,
const struct TALER_Amount *value)
{
struct TALER_EXCHANGEDB_TransactionList *tl;
enum GNUNET_DB_QueryStatus qs;
struct TALER_Amount total;
struct TALER_Amount spent;
struct TALER_Amount refunded;
struct TALER_Amount deposit_fee;
int have_refund;
qs = TALER_ARL_edb->get_coin_transactions (TALER_ARL_edb->cls,
TALER_ARL_esession,
coin_pub,
GNUNET_YES,
&tl);
if (0 >= qs)
return qs;
GNUNET_assert (GNUNET_OK ==
TALER_amount_get_zero (value->currency,
&refunded));
GNUNET_assert (GNUNET_OK ==
TALER_amount_get_zero (value->currency,
&spent));
GNUNET_assert (GNUNET_OK ==
TALER_amount_get_zero (value->currency,
&deposit_fee));
for (struct TALER_EXCHANGEDB_TransactionList *pos = tl;
NULL != pos;
pos = pos->next)
{
switch (pos->type)
{
case TALER_EXCHANGEDB_TT_DEPOSIT:
/* spent += pos->amount_with_fee */
GNUNET_assert (GNUNET_OK ==
TALER_amount_add (&spent,
&spent,
&pos->details.deposit->amount_with_fee));
deposit_fee = pos->details.deposit->deposit_fee;
break;
case TALER_EXCHANGEDB_TT_MELT:
/* spent += pos->amount_with_fee */
GNUNET_assert (GNUNET_OK ==
TALER_amount_add (&spent,
&spent,
&pos->details.melt->amount_with_fee));
break;
case TALER_EXCHANGEDB_TT_REFUND:
/* refunded += pos->refund_amount - pos->refund_fee */
GNUNET_assert (GNUNET_OK ==
TALER_amount_add (&refunded,
&refunded,
&pos->details.refund->refund_amount));
GNUNET_assert (GNUNET_OK ==
TALER_amount_add (&spent,
&spent,
&pos->details.refund->refund_fee));
have_refund = GNUNET_YES;
break;
case TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP:
/* refunded += pos->value */
GNUNET_assert (GNUNET_OK ==
TALER_amount_add (&refunded,
&refunded,
&pos->details.old_coin_recoup->value));
case TALER_EXCHANGEDB_TT_RECOUP:
/* spent += pos->value */
GNUNET_assert (GNUNET_OK ==
TALER_amount_add (&spent,
&spent,
&pos->details.recoup->value));
break;
case TALER_EXCHANGEDB_TT_RECOUP_REFRESH:
/* spent += pos->value */
GNUNET_assert (GNUNET_OK ==
TALER_amount_add (&spent,
&spent,
&pos->details.recoup_refresh->value));
break;
}
}
if (have_refund)
{
/* If we gave any refund, also discount ONE deposit fee */
GNUNET_assert (GNUNET_OK ==
TALER_amount_add (&refunded,
&refunded,
&deposit_fee));
}
/* total coin value = original value plus refunds */
GNUNET_assert (GNUNET_OK ==
TALER_amount_add (&total,
&refunded,
value));
if (1 ==
TALER_amount_cmp (&spent,
&total))
{
/* spent > total: bad */
report_amount_arithmetic_inconsistency (operation,
rowid,
&spent,
&total,
-1);
}
cache_history (coin_pub,
tl);
return qs;
}
/* ************************* Analyze coins ******************** */ /* ************************* Analyze coins ******************** */
/* This logic checks that the exchange did the right thing for each /* This logic checks that the exchange did the right thing for each
coin, checking deposits, refunds, refresh* and known_coins coin, checking deposits, refunds, refresh* and known_coins
@ -776,6 +984,7 @@ withdraw_cb (void *cls,
(void) reserve_sig; (void) reserve_sig;
(void) execution_date; (void) execution_date;
(void) amount_with_fee; (void) amount_with_fee;
GNUNET_assert (rowid >= ppc.last_withdraw_serial_id); /* should be monotonically increasing */ GNUNET_assert (rowid >= ppc.last_withdraw_serial_id); /* should be monotonically increasing */
ppc.last_withdraw_serial_id = rowid + 1; ppc.last_withdraw_serial_id = rowid + 1;
@ -930,9 +1139,12 @@ reveal_data_cb (void *cls,
/** /**
* Check that the @a coin_pub is a known coin with a proper * Check that the @a coin_pub is a known coin with a proper
* signature for denominatinon @a denom_pub. If not, TALER_ARL_report * signature for denominatinon @a denom_pub. If not, report
* a loss of @a loss_potential. * a loss of @a loss_potential.
* *
* @param operation which operation is this about
* @param issue denomination key information about the coin
* @param rowid which row is this operation in
* @param coin_pub public key of a coin * @param coin_pub public key of a coin
* @param denom_pub expected denomination of the coin * @param denom_pub expected denomination of the coin
* @param loss_potential how big could the loss be if the coin is * @param loss_potential how big could the loss be if the coin is
@ -941,13 +1153,35 @@ reveal_data_cb (void *cls,
* #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT
*/ */
static enum GNUNET_DB_QueryStatus static enum GNUNET_DB_QueryStatus
check_known_coin (const struct TALER_CoinSpendPublicKeyP *coin_pub, check_known_coin (const char *operation,
const struct TALER_DenominationKeyValidityPS *issue,
uint64_t rowid,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_DenominationPublicKey *denom_pub, const struct TALER_DenominationPublicKey *denom_pub,
const struct TALER_Amount *loss_potential) const struct TALER_Amount *loss_potential)
{ {
struct TALER_CoinPublicInfo ci; struct TALER_CoinPublicInfo ci;
enum GNUNET_DB_QueryStatus qs; enum GNUNET_DB_QueryStatus qs;
if (NULL == get_cached_history (coin_pub))
{
struct TALER_Amount value;
TALER_amount_ntoh (&value,
&issue->value);
qs = check_coin_history (coin_pub,
rowid,
operation,
&value);
if (0 > qs)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
return qs;
}
GNUNET_break (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs);
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Checking denomination signature on %s\n", "Checking denomination signature on %s\n",
TALER_B2S (coin_pub)); TALER_B2S (coin_pub));
@ -966,11 +1200,11 @@ check_known_coin (const struct TALER_CoinSpendPublicKeyP *coin_pub,
{ {
TALER_ARL_report (report_bad_sig_losses, TALER_ARL_report (report_bad_sig_losses,
json_pack ("{s:s, s:I, s:o, s:o}", json_pack ("{s:s, s:I, s:o, s:o}",
"operation", "known-coin", "operation", operation,
"row", (json_int_t) -1, "row", (json_int_t) rowid,
"loss", TALER_JSON_from_amount ( "loss", TALER_JSON_from_amount (
loss_potential), loss_potential),
"key_pub", GNUNET_JSON_from_data_auto ( "coin_pub", GNUNET_JSON_from_data_auto (
coin_pub))); coin_pub)));
GNUNET_assert (GNUNET_OK == GNUNET_assert (GNUNET_OK ==
TALER_amount_add (&total_bad_sig_loss, TALER_amount_add (&total_bad_sig_loss,
@ -1037,7 +1271,10 @@ refresh_session_cb (void *cls,
cc->qs = qs; cc->qs = qs;
return GNUNET_SYSERR; return GNUNET_SYSERR;
} }
qs = check_known_coin (coin_pub, qs = check_known_coin ("melt",
issue,
rowid,
coin_pub,
denom_pub, denom_pub,
amount_with_fee); amount_with_fee);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
@ -1377,7 +1614,10 @@ deposit_cb (void *cls,
cc->qs = qs; cc->qs = qs;
return GNUNET_SYSERR; return GNUNET_SYSERR;
} }
qs = check_known_coin (coin_pub, qs = check_known_coin ("deposit",
issue,
rowid,
coin_pub,
denom_pub, denom_pub,
amount_with_fee); amount_with_fee);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
@ -1738,6 +1978,18 @@ check_recoup (struct CoinContext *cc,
cc->qs = qs; cc->qs = qs;
return GNUNET_SYSERR; return GNUNET_SYSERR;
} }
qs = check_known_coin ("recoup",
issue,
rowid,
&coin->coin_pub,
denom_pub,
amount);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
cc->qs = qs;
return GNUNET_SYSERR;
}
{ {
struct TALER_RecoupRequestPS pr = { struct TALER_RecoupRequestPS pr = {
.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_RECOUP), .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_RECOUP),
@ -1789,6 +2041,10 @@ check_recoup (struct CoinContext *cc,
"loss", TALER_JSON_from_amount (amount), "loss", TALER_JSON_from_amount (amount),
"coin_pub", GNUNET_JSON_from_data_auto ( "coin_pub", GNUNET_JSON_from_data_auto (
&coin->coin_pub))); &coin->coin_pub)));
GNUNET_assert (GNUNET_OK ==
TALER_amount_add (&total_bad_sig_loss,
&total_bad_sig_loss,
amount));
} }
GNUNET_assert (GNUNET_OK == GNUNET_assert (GNUNET_OK ==
TALER_amount_add (&ds->denom_recoup, TALER_amount_add (&ds->denom_recoup,