aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2020-03-27 09:35:42 +0100
committerChristian Grothoff <christian@grothoff.org>2020-03-27 09:35:42 +0100
commit62b8ca0bd3bfa47a864bf35d1c1d4b346e98d720 (patch)
tree1dc8ea256456b53b8e169d532ddfe5846b2c1fcd /src
parent9445343ec5c35a79b8cd8a2a15e92ca7435bda46 (diff)
check coin history consistency
Diffstat (limited to 'src')
-rw-r--r--src/auditor/taler-helper-auditor-coins.c272
1 files changed, 264 insertions, 8 deletions
diff --git a/src/auditor/taler-helper-auditor-coins.c b/src/auditor/taler-helper-auditor-coins.c
index 9f9f6dc93..5bcf636b7 100644
--- a/src/auditor/taler-helper-auditor-coins.c
+++ b/src/auditor/taler-helper-auditor-coins.c
@@ -38,7 +38,7 @@
* Set to a VERY low value here for testing. Practical values may be
* 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.
@@ -169,6 +169,84 @@ static json_t *report_refreshs_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 **************************** */
@@ -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 ******************** */
/* This logic checks that the exchange did the right thing for each
coin, checking deposits, refunds, refresh* and known_coins
@@ -776,6 +984,7 @@ withdraw_cb (void *cls,
(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;
@@ -930,9 +1139,12 @@ reveal_data_cb (void *cls,
/**
* 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.
*
+ * @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 denom_pub expected denomination of the coin
* @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
*/
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_Amount *loss_potential)
{
struct TALER_CoinPublicInfo ci;
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,
"Checking denomination signature on %s\n",
TALER_B2S (coin_pub));
@@ -966,11 +1200,11 @@ check_known_coin (const struct TALER_CoinSpendPublicKeyP *coin_pub,
{
TALER_ARL_report (report_bad_sig_losses,
json_pack ("{s:s, s:I, s:o, s:o}",
- "operation", "known-coin",
- "row", (json_int_t) -1,
+ "operation", operation,
+ "row", (json_int_t) rowid,
"loss", TALER_JSON_from_amount (
loss_potential),
- "key_pub", GNUNET_JSON_from_data_auto (
+ "coin_pub", GNUNET_JSON_from_data_auto (
coin_pub)));
GNUNET_assert (GNUNET_OK ==
TALER_amount_add (&total_bad_sig_loss,
@@ -1037,7 +1271,10 @@ refresh_session_cb (void *cls,
cc->qs = qs;
return GNUNET_SYSERR;
}
- qs = check_known_coin (coin_pub,
+ qs = check_known_coin ("melt",
+ issue,
+ rowid,
+ coin_pub,
denom_pub,
amount_with_fee);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
@@ -1377,7 +1614,10 @@ deposit_cb (void *cls,
cc->qs = qs;
return GNUNET_SYSERR;
}
- qs = check_known_coin (coin_pub,
+ qs = check_known_coin ("deposit",
+ issue,
+ rowid,
+ coin_pub,
denom_pub,
amount_with_fee);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
@@ -1738,6 +1978,18 @@ check_recoup (struct CoinContext *cc,
cc->qs = qs;
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 = {
.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_RECOUP),
@@ -1789,6 +2041,10 @@ check_recoup (struct CoinContext *cc,
"loss", TALER_JSON_from_amount (amount),
"coin_pub", GNUNET_JSON_from_data_auto (
&coin->coin_pub)));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_add (&total_bad_sig_loss,
+ &total_bad_sig_loss,
+ amount));
}
GNUNET_assert (GNUNET_OK ==
TALER_amount_add (&ds->denom_recoup,