check coin history consistency
This commit is contained in:
parent
9445343ec5
commit
62b8ca0bd3
@ -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,
|
||||||
|
Loading…
Reference in New Issue
Block a user