gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-exchange] branch master updated: introduce stored procedure for c


From: gnunet
Subject: [taler-exchange] branch master updated: introduce stored procedure for coin balance check
Date: Thu, 09 Dec 2021 23:13:43 +0100

This is an automated email from the git hooks/post-receive script.

grothoff pushed a commit to branch master
in repository exchange.

The following commit(s) were added to refs/heads/master by this push:
     new fba91c63 introduce stored procedure for coin balance check
fba91c63 is described below

commit fba91c63d57d73732249b972127575ca1fd4d5ff
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Thu Dec 9 23:13:39 2021 +0100

    introduce stored procedure for coin balance check
---
 src/exchange/taler-exchange-httpd_db.c      | 110 ++++++++++++++--
 src/exchange/taler-exchange-httpd_db.h      |   3 +
 src/exchangedb/exchange-0001.sql            | 188 ++++++++++++++++++++++++++++
 src/exchangedb/plugin_exchangedb_postgres.c |  59 ++++++++-
 src/include/taler_exchangedb_plugin.h       |  25 ++--
 5 files changed, 367 insertions(+), 18 deletions(-)

diff --git a/src/exchange/taler-exchange-httpd_db.c 
b/src/exchange/taler-exchange-httpd_db.c
index 7ced8b88..388679c3 100644
--- a/src/exchange/taler-exchange-httpd_db.c
+++ b/src/exchange/taler-exchange-httpd_db.c
@@ -164,14 +164,29 @@ TEH_make_coin_known (const struct TALER_CoinPublicInfo 
*coin,
 }
 
 
-enum GNUNET_DB_QueryStatus
-TEH_check_coin_balance (struct MHD_Connection *connection,
-                        const struct TALER_CoinSpendPublicKeyP *coin_pub,
-                        const struct TALER_Amount *coin_value,
-                        const struct TALER_Amount *op_cost,
-                        bool check_recoup,
-                        bool zombie_required,
-                        MHD_RESULT *mhd_ret)
+/**
+ * Called when we actually know that the balance (was) insufficient.
+ * Re-does the check (slowly) to compute the full error message for
+ * the client.
+ *
+ * @param connection HTTP connection to report hard errors on
+ * @param coin_pub coin to analyze
+ * @param coin_value total value of the original coin (by denomination)
+ * @param op_cost cost of the current operation (for error reporting)
+ * @param check_recoup should we include recoup transactions in the check
+ * @param zombie_required additional requirement that the coin must
+ *          be a zombie coin, or also hard failure
+ * @param[out] mhd_ret set to response status code, on hard error only
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+check_coin_balance (struct MHD_Connection *connection,
+                    const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                    const struct TALER_Amount *coin_value,
+                    const struct TALER_Amount *op_cost,
+                    bool check_recoup,
+                    bool zombie_required,
+                    MHD_RESULT *mhd_ret)
 {
   struct TALER_EXCHANGEDB_TransactionList *tl;
   struct TALER_Amount spent;
@@ -273,13 +288,90 @@ TEH_check_coin_balance (struct MHD_Connection *connection,
     return GNUNET_DB_STATUS_HARD_ERROR;
   }
 
-  /* we're good, coin has sufficient funds to be melted */
+  /* This should not happen: The coin has sufficient funds
+     after all!?!? */
   TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
                                           tl);
   return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
 }
 
 
+enum GNUNET_DB_QueryStatus
+TEH_check_coin_balance (struct MHD_Connection *connection,
+                        const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                        const struct TALER_Amount *coin_value,
+                        const struct TALER_Amount *op_cost,
+                        bool check_recoup,
+                        bool zombie_required,
+                        MHD_RESULT *mhd_ret)
+{
+  bool balance_ok = false;
+  bool zombie_ok = false;
+  enum GNUNET_DB_QueryStatus qs;
+
+  qs = TEH_plugin->do_check_coin_balance (TEH_plugin->cls,
+                                          coin_pub,
+                                          coin_value,
+                                          check_recoup,
+                                          zombie_required,
+                                          &balance_ok,
+                                          &zombie_ok);
+  switch (qs)
+  {
+  case GNUNET_DB_STATUS_HARD_ERROR:
+    *mhd_ret = TALER_MHD_reply_with_error (
+      connection,
+      MHD_HTTP_INTERNAL_SERVER_ERROR,
+      TALER_EC_GENERIC_DB_FETCH_FAILED,
+      "check_coin_balance");
+    return qs;
+  case GNUNET_DB_STATUS_SOFT_ERROR:
+    return qs;
+  case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+    GNUNET_break (0);
+    *mhd_ret = TALER_MHD_reply_with_error (
+      connection,
+      MHD_HTTP_INTERNAL_SERVER_ERROR,
+      TALER_EC_GENERIC_DB_FETCH_FAILED,
+      "check_coin_balance");
+    return GNUNET_DB_STATUS_HARD_ERROR;
+  case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+    /* handled below */
+    break;
+  }
+  if (! zombie_ok)
+  {
+    GNUNET_break_op (0);
+    *mhd_ret = TALER_MHD_reply_with_error (
+      connection,
+      MHD_HTTP_BAD_REQUEST,
+      TALER_EC_EXCHANGE_MELT_COIN_EXPIRED_NO_ZOMBIE,
+      NULL);
+    return GNUNET_DB_STATUS_HARD_ERROR;
+  }
+  if (balance_ok)
+    return qs;
+  /* balance is not OK, do expensive call to compute full error message */
+  qs = check_coin_balance (connection,
+                           coin_pub,
+                           coin_value,
+                           op_cost,
+                           check_recoup,
+                           zombie_required,
+                           mhd_ret);
+  if (qs < 0)
+    return qs; /* we expected to fail (same check as before!) */
+  GNUNET_break (0); /* stored procedure and individual statements
+                       disagree, should be impossible! */
+  *mhd_ret = TALER_MHD_reply_with_error (
+    connection,
+    MHD_HTTP_INTERNAL_SERVER_ERROR,
+    TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
+    "stored procedure disagrees with full coin transaction history fetch");
+  return GNUNET_DB_STATUS_HARD_ERROR;
+}
+
+
 enum GNUNET_GenericReturnValue
 TEH_DB_run_transaction (struct MHD_Connection *connection,
                         const char *name,
diff --git a/src/exchange/taler-exchange-httpd_db.h 
b/src/exchange/taler-exchange-httpd_db.h
index 60885dbd..5ee3b41d 100644
--- a/src/exchange/taler-exchange-httpd_db.h
+++ b/src/exchange/taler-exchange-httpd_db.h
@@ -47,6 +47,9 @@ TEH_make_coin_known (const struct TALER_CoinPublicInfo *coin,
  * insufficient for all transactions associated with the
  * coin, return a hard error.
  *
+ * We first do a "fast" check using a stored procedure, and
+ * only obtain the "full" data on failure (for performance).
+ *
  * @param connection HTTP connection to report hard errors on
  * @param coin_pub coin to analyze
  * @param coin_value total value of the original coin (by denomination)
diff --git a/src/exchangedb/exchange-0001.sql b/src/exchangedb/exchange-0001.sql
index dc6b2bba..1725b70e 100644
--- a/src/exchangedb/exchange-0001.sql
+++ b/src/exchangedb/exchange-0001.sql
@@ -899,6 +899,194 @@ COMMENT ON FUNCTION 
exchange_do_withdraw_limit_check(INT8, INT8, INT8, INT4)
 
 
 
+CREATE OR REPLACE FUNCTION exchange_do_check_coin_balance(
+  IN denom_val INT8, -- value of the denomination of the coin
+  IN denom_frac INT4, -- value of the denomination of the coin
+  IN in_coin_pub BYTEA, -- coin public key
+  IN check_recoup BOOLEAN, -- do we need to check the recoup table?
+  IN zombie_required BOOLEAN, -- do we need a zombie coin?
+  OUT balance_ok BOOLEAN, -- balance satisfied?
+  OUT zombie_ok BOOLEAN) -- zombie satisfied?
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  coin_uuid INT8; -- known_coin_id of coin_pub
+DECLARE
+  tmp_val INT8; -- temporary result
+DECLARE
+  tmp_frac INT8; -- temporary result
+DECLARE
+  spent_val INT8; -- how much of coin was spent?
+DECLARE
+  spent_frac INT8; -- how much of coin was spent?
+DECLARE
+  unspent_val INT8; -- how much of coin was refunded?
+DECLARE
+  unspent_frac INT8; -- how much of coin was refunded?
+BEGIN
+
+-- Note: possible future optimization: get the coin_uuid from the previous
+-- 'ensure_coin_known' and pass that here instead of the coin_pub. Might help
+-- a tiny bit with performance.
+SELECT known_coin_id INTO coin_uuid
+  FROM known_coins
+ WHERE coin_pub=in_coin_pub;
+
+IF NOT FOUND
+THEN
+  -- coin unknown, should be impossible!
+  balance_ok=FALSE;
+  zombie_ok=FALSE;
+  ASSERT false, 'coin unknown';
+  RETURN;
+END IF;
+
+
+spent_val = 0;
+spent_frac = 0;
+unspent_val = denom_val;
+unspent_frac = denom_frac;
+
+SELECT
+   SUM(amount_with_fee_val) -- overflow here is not plausible
+  ,SUM(CAST(amount_with_fee_frac AS INT8)) -- compute using 64 bits
+  INTO
+   tmp_val
+  ,tmp_frac
+  FROM deposits
+ WHERE known_coin_id=coin_uuid;
+
+IF tmp_val IS NOT NULL
+THEN
+  spent_val = spent_val + tmp_val;
+  spent_frac = spent_frac + tmp_frac;
+END IF;
+
+SELECT
+   SUM(amount_with_fee_val) -- overflow here is not plausible
+  ,SUM(CAST(amount_with_fee_frac AS INT8)) -- compute using 64 bits
+  INTO
+   tmp_val
+  ,tmp_frac
+  FROM refresh_commitments
+ WHERE old_known_coin_id=coin_uuid;
+
+IF tmp_val IS NOT NULL
+THEN
+  spent_val = spent_val + tmp_val;
+  spent_frac = spent_frac + tmp_frac;
+END IF;
+
+
+SELECT
+   SUM(rf.amount_with_fee_val) -- overflow here is not plausible
+  ,SUM(CAST(rf.amount_with_fee_frac AS INT8)) -- compute using 64 bits
+  INTO
+   tmp_val
+  ,tmp_frac
+  FROM deposits
+  JOIN refunds rf
+    USING (deposit_serial_id)
+  WHERE
+    known_coin_id=coin_uuid;
+IF tmp_val IS NOT NULL
+THEN
+  unspent_val = unspent_val + tmp_val;
+  unspent_frac = unspent_frac + tmp_frac;
+END IF;
+
+-- Note: even if 'check_recoup' is true, the tables below
+-- are in practice likely empty (as they only apply if
+-- the exchange (ever) had to revoke keys).
+IF check_recoup
+THEN
+
+  SELECT
+     SUM(amount_val) -- overflow here is not plausible
+    ,SUM(CAST(amount_frac AS INT8)) -- compute using 64 bits
+    INTO
+     tmp_val
+    ,tmp_frac
+    FROM recoup_refresh
+   WHERE known_coin_id=coin_uuid;
+
+  IF tmp_val IS NOT NULL
+  THEN
+    spent_val = spent_val + tmp_val;
+    spent_frac = spent_frac + tmp_frac;
+  END IF;
+
+  SELECT
+     SUM(amount_val) -- overflow here is not plausible
+    ,SUM(CAST(amount_frac AS INT8)) -- compute using 64 bits
+    INTO
+     tmp_val
+    ,tmp_frac
+    FROM recoup
+   WHERE known_coin_id=coin_uuid;
+
+  IF tmp_val IS NOT NULL
+  THEN
+    spent_val = spent_val + tmp_val;
+    spent_frac = spent_frac + tmp_frac;
+  END IF;
+
+  SELECT
+     SUM(amount_val) -- overflow here is not plausible
+    ,SUM(CAST(amount_frac AS INT8)) -- compute using 64 bits
+    INTO
+     tmp_val
+    ,tmp_frac
+    FROM recoup_refresh
+    JOIN refresh_revealed_coins rrc
+        USING (rrc_serial)
+    JOIN refresh_commitments rfc
+         ON (rrc.melt_serial_id = rfc.melt_serial_id)
+   WHERE rfc.old_known_coin_id=coin_uuid;
+
+  IF tmp_val IS NOT NULL
+  THEN
+    unspent_val = unspent_val + tmp_val;
+    unspent_frac = unspent_frac + tmp_frac;
+  END IF;
+
+  IF ( (0 < tmp_val) OR (0 < tmp_frac) )
+  THEN
+    -- There was a transaction that justifies the zombie
+    -- status, clear the flag
+    zombie_required=FALSE;
+  END IF;
+
+END IF;
+
+
+-- normalize results
+spent_val = spent_val + spent_frac / 100000000;
+spent_frac = spent_frac % 100000000;
+unspent_val = unspent_val + unspent_frac / 100000000;
+unspent_frac = unspent_frac % 100000000;
+
+-- Actually check if the coin balance is sufficient. Verbosely. ;-)
+IF (unspent_val > spent_val)
+THEN
+  balance_ok=TRUE;
+ELSE
+  IF (unspent_val = spent_val) AND (unspent_frac >= spent_frac)
+  THEN
+    balance_ok=TRUE;
+  ELSE
+    balance_ok=FALSE;
+  END IF;
+END IF;
+
+zombie_ok = NOT zombie_required;
+
+END $$;
+
+COMMENT ON FUNCTION exchange_do_check_coin_balance(INT8, INT4, BYTEA, BOOLEAN, 
BOOLEAN)
+  IS 'Checks whether the coin has sufficient balance for all the operations 
associated with it';
+
+
 
 
 -- Complete transaction
diff --git a/src/exchangedb/plugin_exchangedb_postgres.c 
b/src/exchangedb/plugin_exchangedb_postgres.c
index 73a03e75..8e184a9d 100644
--- a/src/exchangedb/plugin_exchangedb_postgres.c
+++ b/src/exchangedb/plugin_exchangedb_postgres.c
@@ -596,6 +596,16 @@ prepare_statements (struct PostgresClosure *pg)
       "lock_withdraw",
       "LOCK TABLE reserves_out;",
       0),
+    /* Used in #postgres_do_check_coin_balance() to check
+       a coin's balance */
+    GNUNET_PQ_make_prepare (
+      "call_check_coin_balance",
+      "SELECT "
+      " balance_ok"
+      ",zombie_ok"
+      " FROM exchange_do_check_coin_balance"
+      " ($1,$2,$3,$4,$5);",
+      5),
     /* Used in #postgres_do_withdraw() to store
        the signature of a blinded coin with the blinded coin's
        details before returning it during /reserve/withdraw. We store
@@ -4491,6 +4501,53 @@ postgres_get_withdraw_info (
 }
 
 
+/**
+ * Check coin balance is sufficient to satisfy balance
+ * invariants.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param coin_pub coin to check
+ * @param coin_value value of the coin's denomination (avoids internal lookup)
+ * @param check_recoup include recoup and recoup_refresh tables in calculation
+ * @param zombie_required additionally require coin to be a zombie coin
+ * @param[out] balance_ok set to true if the balance was sufficient
+ * @param[out] zombie_ok set to true if the zombie requirement was satisfied
+ * @return query execution status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_do_check_coin_balance (
+  void *cls,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_Amount *coin_value,
+  bool check_recoup,
+  bool zombie_required,
+  bool *balance_ok,
+  bool *zombie_ok)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    TALER_PQ_query_param_amount (coin_value),
+    GNUNET_PQ_query_param_auto_from_type (coin_pub),
+    GNUNET_PQ_query_param_bool (check_recoup),
+    GNUNET_PQ_query_param_bool (zombie_required),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    GNUNET_PQ_result_spec_bool ("balance_ok",
+                                balance_ok),
+    GNUNET_PQ_result_spec_bool ("zombie_ok",
+                                zombie_ok),
+    GNUNET_PQ_result_spec_end
+  };
+
+  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   "call_check_coin_balance",
+                                                   params,
+                                                   rs);
+
+}
+
+
 /**
  * Perform withdraw operation, checking for sufficient balance
  * and possibly persisting the withdrawal details.
@@ -11825,7 +11882,7 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
   plugin->get_latest_reserve_in_reference =
     &postgres_get_latest_reserve_in_reference;
   plugin->get_withdraw_info = &postgres_get_withdraw_info;
-  // plugin->insert_withdraw_info = &postgres_insert_withdraw_info;
+  plugin->do_check_coin_balance = &postgres_do_check_coin_balance;
   plugin->do_withdraw = &postgres_do_withdraw;
   plugin->do_withdraw_limit_check = &postgres_do_withdraw_limit_check;
   plugin->get_reserve_history = &postgres_get_reserve_history;
diff --git a/src/include/taler_exchangedb_plugin.h 
b/src/include/taler_exchangedb_plugin.h
index fd2f3dc4..56a16dd7 100644
--- a/src/include/taler_exchangedb_plugin.h
+++ b/src/include/taler_exchangedb_plugin.h
@@ -2503,18 +2503,27 @@ struct TALER_EXCHANGEDB_Plugin
 
 
   /**
-   * Store collectable coin under the corresponding hash of the blinded
-   * message.
+   * Check coin balance is sufficient to satisfy balance
+   * invariants.
    *
-   * @param cls the @e cls of this struct with the plugin-specific state
-   * @param collectable corresponding collectable coin (blind signature)
-   *                    if a coin is found
-   * @return statement execution status
+   * @param cls the `struct PostgresClosure` with the plugin-specific state
+   * @param coin_pub coin to check
+   * @param coin_value value of the coin's denomination (avoids internal 
lookup)
+   * @param check_recoup include recoup and recoup_refresh tables in 
calculation
+   * @param zombie_required additionally require coin to be a zombie coin
+   * @param[out] balance_ok set to true if the balance was sufficient
+   * @param[out] zombie_ok set to true if the zombie requirement was satisfied
+   * @return query execution status
    */
   enum GNUNET_DB_QueryStatus
-  (*insert_withdraw_infoXX)(
+  (*do_check_coin_balance)(
     void *cls,
-    const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable);
+    const struct TALER_CoinSpendPublicKeyP *coin_pub,
+    const struct TALER_Amount *coin_value,
+    bool check_recoup,
+    bool zombie_required,
+    bool *balance_ok,
+    bool *zombie_ok);
 
 
   /**

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]