gnunet-svn
[Top][All Lists]
Advanced

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

[taler-exchange] branch master updated: complete coin history DB logic t


From: gnunet
Subject: [taler-exchange] branch master updated: complete coin history DB logic to properly handle new ETags
Date: Tue, 19 Sep 2023 11:55:14 +0200

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 2afcc8c7 complete coin history DB logic to properly handle new ETags
2afcc8c7 is described below

commit 2afcc8c70202c10b71f98c9e4b9766ae08656459
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Tue Sep 19 11:55:09 2023 +0200

    complete coin history DB logic to properly handle new ETags
---
 src/auditor/taler-helper-auditor-aggregation.c     |   8 +-
 src/auditor/taler-helper-auditor-coins.c           |   6 +-
 src/exchange/taler-exchange-httpd_coins_get.c      | 216 +++++++----
 .../taler-exchange-httpd_reserves_history.c        |   2 +-
 src/exchangedb/0002-purse_decision.sql             |  25 +-
 src/exchangedb/0002-refresh_commitments.sql        |   2 +-
 src/exchangedb/pg_get_coin_transactions.c          | 417 +++++++++++++++------
 src/exchangedb/pg_get_coin_transactions.h          |  26 +-
 src/exchangedb/test_exchangedb.c                   |  11 +-
 src/include/taler_exchangedb_plugin.h              |  24 +-
 10 files changed, 502 insertions(+), 235 deletions(-)

diff --git a/src/auditor/taler-helper-auditor-aggregation.c 
b/src/auditor/taler-helper-auditor-aggregation.c
index 81892c16..8075e100 100644
--- a/src/auditor/taler-helper-auditor-aggregation.c
+++ b/src/auditor/taler-helper-auditor-aggregation.c
@@ -767,7 +767,7 @@ wire_transfer_information_cb (
   struct TALER_CoinPublicInfo coin;
   enum GNUNET_DB_QueryStatus qs;
   struct TALER_PaytoHashP hpt;
-  uint64_t etag = 0;
+  uint64_t etag_out;
 
   TALER_payto_hash (account_pay_uri,
                     &hpt);
@@ -780,12 +780,14 @@ wire_transfer_information_cb (
                               "h-payto does not match payto URI");
   }
   /* Obtain coin's transaction history */
-  /* TODO: could use 'etag' mechanism to only fetch transactions
+  /* TODO: could use 'start' mechanism to only fetch transactions
      we did not yet process, instead of going over them
      again and again.*/
   qs = TALER_ARL_edb->get_coin_transactions (TALER_ARL_edb->cls,
                                              coin_pub,
-                                             &etag,
+                                             0,
+                                             0,
+                                             &etag_out,
                                              &tl);
   if ( (qs < 0) ||
        (NULL == tl) )
diff --git a/src/auditor/taler-helper-auditor-coins.c 
b/src/auditor/taler-helper-auditor-coins.c
index 8c3d66b9..f873fa3c 100644
--- a/src/auditor/taler-helper-auditor-coins.c
+++ b/src/auditor/taler-helper-auditor-coins.c
@@ -435,14 +435,16 @@ check_coin_history (const struct 
TALER_CoinSpendPublicKeyP *coin_pub,
   struct TALER_Amount refunded;
   struct TALER_Amount deposit_fee;
   bool have_refund;
-  uint64_t etag = 0;
+  uint64_t etag_out;
 
   /* TODO: could use 'etag' mechanism to only fetch transactions
      we did not yet process, instead of going over them
      again and again. */
   qs = TALER_ARL_edb->get_coin_transactions (TALER_ARL_edb->cls,
                                              coin_pub,
-                                             &etag,
+                                             0,
+                                             0,
+                                             &etag_out,
                                              &tl);
   if (0 >= qs)
     return qs;
diff --git a/src/exchange/taler-exchange-httpd_coins_get.c 
b/src/exchange/taler-exchange-httpd_coins_get.c
index c5cf6ba5..7553bf19 100644
--- a/src/exchange/taler-exchange-httpd_coins_get.c
+++ b/src/exchange/taler-exchange-httpd_coins_get.c
@@ -15,7 +15,7 @@
 */
 /**
  * @file taler-exchange-httpd_coins_get.c
- * @brief Handle GET /coins/$COIN_PUB requests
+ * @brief Handle GET /coins/$COIN_PUB/history requests
  * @author Christian Grothoff
  */
 #include "platform.h"
@@ -538,102 +538,162 @@ MHD_RESULT
 TEH_handler_coins_get (struct TEH_RequestContext *rc,
                        const struct TALER_CoinSpendPublicKeyP *coin_pub)
 {
-  enum GNUNET_DB_QueryStatus qs;
-  struct TALER_EXCHANGEDB_TransactionList *tl;
-  const char *etags;
-  uint64_t etag = 0;
-
-  etags = MHD_lookup_connection_value (rc->connection,
-                                       MHD_HEADER_KIND,
-                                       MHD_HTTP_HEADER_IF_NONE_MATCH);
-  if (NULL != etags)
+  struct TALER_EXCHANGEDB_TransactionList *tl = NULL;
+  uint64_t start_off = 0;
+  uint64_t etag_in = 0;
+  uint64_t etag_out;
+  char etagp[24];
+  struct MHD_Response *resp;
+  unsigned int http_status;
+
+  TALER_MHD_parse_request_number (rc->connection,
+                                  "start",
+                                  &start_off);
+  /* Check signature */
   {
-    char dummy;
-    unsigned long long ev;
+    struct TALER_CoinSpendSignatureP coin_sig;
+    bool required = true;
 
-    if (1 != sscanf (etags,
-                     "\"%llu\"%c",
-                     &ev,
-                     &dummy))
+    TALER_MHD_parse_request_header_auto (rc->connection,
+                                         TALER_COIN_HISTORY_SIGNATURE_HEADER,
+                                         &coin_sig,
+                                         required);
+    if (GNUNET_OK !=
+        TALER_wallet_coin_history_verify (start_off,
+                                          coin_pub,
+                                          &coin_sig))
     {
-      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                  "Client send malformed `If-None-Match' header `%s'\n",
-                  etags);
+      GNUNET_break_op (0);
+      return TALER_MHD_reply_with_error (rc->connection,
+                                         MHD_HTTP_FORBIDDEN,
+                                         
TALER_EC_EXCHANGE_COIN_HISTORY_BAD_SIGNATURE,
+                                         NULL);
+    }
+  }
+
+  /* Get etag */
+  {
+    const char *etags;
+
+    etags = MHD_lookup_connection_value (rc->connection,
+                                         MHD_HEADER_KIND,
+                                         MHD_HTTP_HEADER_IF_NONE_MATCH);
+    if (NULL != etags)
+    {
+      char dummy;
+      unsigned long long ev;
+
+      if (1 != sscanf (etags,
+                       "\"%llu\"%c",
+                       &ev,
+                       &dummy))
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                    "Client send malformed `If-None-Match' header `%s'\n",
+                    etags);
+      }
+      else
+      {
+        etag_in = (uint64_t) ev;
+      }
     }
     else
     {
-      etag = (uint64_t) ev;
+      etag_in = start_off;
     }
   }
-  qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
-                                          coin_pub,
-                                          &etag,
-                                          &tl);
-  switch (qs)
+
+  /* Get history from DB between etag and now */
   {
-  case GNUNET_DB_STATUS_HARD_ERROR:
-    GNUNET_break (0);
-    return TALER_MHD_reply_with_error (rc->connection,
-                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                       TALER_EC_GENERIC_DB_FETCH_FAILED,
-                                       "get_coin_history");
-  case GNUNET_DB_STATUS_SOFT_ERROR:
-    GNUNET_break (0);   /* single-shot query should never have soft-errors */
-    return TALER_MHD_reply_with_error (rc->connection,
-                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                       TALER_EC_GENERIC_DB_SOFT_FAILURE,
-                                       "get_coin_history");
-  case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
-    if (0 == etag)
+    enum GNUNET_DB_QueryStatus qs;
+
+    qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
+                                            coin_pub,
+                                            start_off,
+                                            etag_in,
+                                            &etag_out,
+                                            &tl);
+    switch (qs)
+    {
+    case GNUNET_DB_STATUS_HARD_ERROR:
+      GNUNET_break (0);
+      return TALER_MHD_reply_with_error (rc->connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                         "get_coin_history");
+    case GNUNET_DB_STATUS_SOFT_ERROR:
+      GNUNET_break (0); /* single-shot query should never have soft-errors */
+      return TALER_MHD_reply_with_error (rc->connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_GENERIC_DB_SOFT_FAILURE,
+                                         "get_coin_history");
+    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
       return TALER_MHD_reply_with_error (rc->connection,
                                          MHD_HTTP_NOT_FOUND,
                                          
TALER_EC_EXCHANGE_GENERIC_COIN_UNKNOWN,
                                          NULL);
+    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+      /* Handled below */
+      break;
+    }
+  }
+
+  GNUNET_snprintf (etagp,
+                   sizeof (etagp),
+                   "\"%llu\"",
+                   (unsigned long long) etag_out);
+  if (etag_in == etag_out)
+  {
     return TEH_RESPONSE_reply_not_modified (rc->connection,
-                                            etags,
+                                            etagp,
                                             &add_response_headers,
                                             NULL);
-  case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+  }
+  if (NULL == tl)
+  {
+    /* 204: empty history */
+    resp = MHD_create_response_from_buffer (0,
+                                            "",
+                                            MHD_RESPMEM_PERSISTENT);
+    http_status = MHD_HTTP_NO_CONTENT;
+  }
+  else
+  {
+    /* 200: regular history */
+    json_t *history;
+
+    history = compile_transaction_history (coin_pub,
+                                           tl);
+    TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
+                                            tl);
+    tl = NULL;
+    if (NULL == history)
     {
-      json_t *history;
-      char etagp[24];
-      MHD_RESULT ret;
-      struct MHD_Response *resp;
-
-      GNUNET_snprintf (etagp,
-                       sizeof (etagp),
-                       "\"%llu\"",
-                       (unsigned long long) etag);
-      history = compile_transaction_history (coin_pub,
-                                             tl);
-      TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
-                                              tl);
-      tl = NULL;
-      if (NULL == history)
-      {
-        GNUNET_break (0);
-        return TALER_MHD_reply_with_error (rc->connection,
-                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                           
TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
-                                           "Failed to compile coin history");
-      }
-      resp = TALER_MHD_MAKE_JSON_PACK (
-        GNUNET_JSON_pack_array_steal ("history",
-                                      history));
-      GNUNET_break (MHD_YES ==
-                    MHD_add_response_header (resp,
-                                             MHD_HTTP_HEADER_ETAG,
-                                             etagp));
-      ret = MHD_queue_response (rc->connection,
-                                MHD_HTTP_OK,
-                                resp);
-      GNUNET_break (MHD_YES == ret);
-      MHD_destroy_response (resp);
-      return ret;
+      GNUNET_break (0);
+      return TALER_MHD_reply_with_error (rc->connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         
TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
+                                         "Failed to compile coin history");
     }
+    resp = TALER_MHD_MAKE_JSON_PACK (
+      GNUNET_JSON_pack_array_steal ("history",
+                                    history));
+    http_status = MHD_HTTP_OK;
+  }
+  GNUNET_break (MHD_YES ==
+                MHD_add_response_header (resp,
+                                         MHD_HTTP_HEADER_ETAG,
+                                         etagp));
+  {
+    MHD_RESULT ret;
+
+    ret = MHD_queue_response (rc->connection,
+                              http_status,
+                              resp);
+    GNUNET_break (MHD_YES == ret);
+    MHD_destroy_response (resp);
+    return ret;
   }
-  GNUNET_break (0);
-  return MHD_NO;
 }
 
 
diff --git a/src/exchange/taler-exchange-httpd_reserves_history.c 
b/src/exchange/taler-exchange-httpd_reserves_history.c
index 0c692c8f..a73b5ab6 100644
--- a/src/exchange/taler-exchange-httpd_reserves_history.c
+++ b/src/exchange/taler-exchange-httpd_reserves_history.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014-2022 Taler Systems SA
+  Copyright (C) 2014-2023 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU Affero General Public License as published by the Free 
Software
diff --git a/src/exchangedb/0002-purse_decision.sql 
b/src/exchangedb/0002-purse_decision.sql
index 3eeeea8a..bd712ad2 100644
--- a/src/exchangedb/0002-purse_decision.sql
+++ b/src/exchangedb/0002-purse_decision.sql
@@ -1,6 +1,6 @@
 --
 -- This file is part of TALER
--- Copyright (C) 2014--2022 Taler Systems SA
+-- Copyright (C) 2014--2023 Taler Systems SA
 --
 -- TALER is free software; you can redistribute it and/or modify it under the
 -- terms of the GNU General Public License as published by the Free Software
@@ -74,16 +74,19 @@ CREATE OR REPLACE FUNCTION purse_decision_insert_trigger()
   LANGUAGE plpgsql
   AS $$
 BEGIN
-  INSERT INTO exchange.coin_history
-    (coin_pub
-    ,table_name
-    ,serial_id)
-  SELECT
-    pd.coin_pub
-   ,'purse_decision'
-   ,NEW.purse_decision_serial_id
-  FROM purse_deposits pd
-  WHERE purse_pub = NEW.purse_pub;
+  IF NEW.refunded
+  THEN
+    INSERT INTO exchange.coin_history
+      (coin_pub
+      ,table_name
+      ,serial_id)
+    SELECT
+      pd.coin_pub
+     ,'purse_decision'
+     ,NEW.purse_decision_serial_id
+    FROM purse_deposits pd
+    WHERE purse_pub = NEW.purse_pub;
+  END IF;
   RETURN NEW;
 END $$;
 COMMENT ON FUNCTION purse_decision_insert_trigger()
diff --git a/src/exchangedb/0002-refresh_commitments.sql 
b/src/exchangedb/0002-refresh_commitments.sql
index 50e298c2..e577f1e1 100644
--- a/src/exchangedb/0002-refresh_commitments.sql
+++ b/src/exchangedb/0002-refresh_commitments.sql
@@ -1,6 +1,6 @@
 --
 -- This file is part of TALER
--- Copyright (C) 2014--2022 Taler Systems SA
+-- Copyright (C) 2014--2023 Taler Systems SA
 --
 -- TALER is free software; you can redistribute it and/or modify it under the
 -- terms of the GNU General Public License as published by the Free Software
diff --git a/src/exchangedb/pg_get_coin_transactions.c 
b/src/exchangedb/pg_get_coin_transactions.c
index 704e7c5c..e5d3b9b0 100644
--- a/src/exchangedb/pg_get_coin_transactions.c
+++ b/src/exchangedb/pg_get_coin_transactions.c
@@ -24,8 +24,17 @@
 #include "taler_pq_lib.h"
 #include "pg_get_coin_transactions.h"
 #include "pg_helper.h"
+#include "pg_start_read_committed.h"
+#include "pg_commit.h"
+#include "pg_rollback.h"
 #include "plugin_exchangedb_common.h"
 
+/**
+ * How often do we re-try when encountering DB serialization issues?
+ * (We are read-only, so can only happen due to concurrent insert,
+ * which should be very rare.)
+ */
+#define RETRIES 3
 
 /**
  * Closure for callbacks called from #postgres_get_coin_transactions()
@@ -42,11 +51,6 @@ struct CoinHistoryContext
    */
   const struct TALER_CoinSpendPublicKeyP *coin_pub;
 
-  /**
-   * Closure for all callbacks of this database plugin.
-   */
-  void *db_cls;
-
   /**
    * Plugin context.
    */
@@ -57,10 +61,6 @@ struct CoinHistoryContext
    */
   bool failed;
 
-  /**
-   * Set to 'true' if we found a deposit or melt (for invariant check).
-   */
-  bool have_deposit_or_melt;
 };
 
 
@@ -86,7 +86,6 @@ add_coin_deposit (void *cls,
     struct TALER_EXCHANGEDB_TransactionList *tl;
     uint64_t serial_id;
 
-    chc->have_deposit_or_melt = true;
     deposit = GNUNET_new (struct TALER_EXCHANGEDB_DepositListEntry);
     {
       struct GNUNET_PQ_ResultSpec rs[] = {
@@ -170,7 +169,6 @@ add_coin_purse_deposit (void *cls,
     struct TALER_EXCHANGEDB_TransactionList *tl;
     uint64_t serial_id;
 
-    chc->have_deposit_or_melt = true;
     deposit = GNUNET_new (struct TALER_EXCHANGEDB_PurseDepositListEntry);
     {
       bool not_finished;
@@ -246,7 +244,6 @@ add_coin_melt (void *cls,
     struct TALER_EXCHANGEDB_TransactionList *tl;
     uint64_t serial_id;
 
-    chc->have_deposit_or_melt = true;
     melt = GNUNET_new (struct TALER_EXCHANGEDB_MeltListEntry);
     {
       struct GNUNET_PQ_ResultSpec rs[] = {
@@ -668,6 +665,11 @@ add_coin_reserve_open (void *cls,
  */
 struct Work
 {
+  /**
+   * Name of the table.
+   */
+  const char *table;
+
   /**
    * SQL prepared statement name.
    */
@@ -680,58 +682,174 @@ struct Work
 };
 
 
-enum GNUNET_DB_QueryStatus
-TEH_PG_get_coin_transactions (
-  void *cls,
-  const struct TALER_CoinSpendPublicKeyP *coin_pub,
-  uint64_t *etag,
-  struct TALER_EXCHANGEDB_TransactionList **tlp)
+/**
+ * We found a coin history entry. Lookup details
+ * from the respective table and store in @a cls.
+ *
+ * @param[in,out] cls a `struct CoinHistoryContext`
+ * @param result a coin history entry result set
+ * @param num_results total number of results in @a results
+ */
+static void
+handle_history_entry (void *cls,
+                      PGresult *result,
+                      unsigned int num_results)
 {
-  struct PostgresClosure *pg = cls;
+  struct CoinHistoryContext *chc = cls;
+  struct PostgresClosure *pg = chc->pg;
   static const struct Work work[] = {
     /** #TALER_EXCHANGEDB_TT_DEPOSIT */
-    { "get_deposit_with_coin_pub",
+    { "coin_deposits",
+      "get_deposit_with_coin_pub",
       &add_coin_deposit },
     /** #TALER_EXCHANGEDB_TT_MELT */
-    { "get_refresh_session_by_coin",
+    { "refresh_commitments",
+      "get_refresh_session_by_coin",
       &add_coin_melt },
     /** #TALER_EXCHANGEDB_TT_PURSE_DEPOSIT */
-    { "get_purse_deposit_by_coin_pub",
+    { "purse_deposits",
+      "get_purse_deposit_by_coin_pub",
       &add_coin_purse_deposit },
     /** #TALER_EXCHANGEDB_TT_PURSE_REFUND */
-    { "get_purse_decision_by_coin_pub",
+    { "purse_decision",
+      "get_purse_decision_by_coin_pub",
       &add_coin_purse_decision },
     /** #TALER_EXCHANGEDB_TT_REFUND */
-    { "get_refunds_by_coin",
+    { "refunds",
+      "get_refunds_by_coin",
       &add_coin_refund },
     /** #TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP */
-    { "recoup_by_old_coin",
+    { "recoup_refresh::OLD",
+      "recoup_by_old_coin",
       &add_old_coin_recoup },
     /** #TALER_EXCHANGEDB_TT_RECOUP */
-    { "recoup_by_coin",
+    { "recoup",
+      "recoup_by_coin",
       &add_coin_recoup },
     /** #TALER_EXCHANGEDB_TT_RECOUP_REFRESH */
-    { "recoup_by_refreshed_coin",
+    { "recoup_refresh::NEW",
+      "recoup_by_refreshed_coin",
       &add_coin_recoup_refresh },
     /** #TALER_EXCHANGEDB_TT_RESERVE_OPEN */
-    { "reserve_open_by_coin",
+    { "reserves_open_deposits",
+      "reserve_open_by_coin",
       &add_coin_reserve_open },
-    { NULL, NULL }
+    { NULL, NULL, NULL }
+  };
+  char *table_name;
+  uint64_t serial_id;
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    GNUNET_PQ_result_spec_string ("table_name",
+                                  &table_name),
+    GNUNET_PQ_result_spec_uint64 ("serial_id",
+                                  &serial_id),
+    GNUNET_PQ_result_spec_end
+  };
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (chc->coin_pub),
+    GNUNET_PQ_query_param_uint64 (&serial_id),
+    GNUNET_PQ_query_param_end
   };
+
+  for (unsigned int i = 0; i<num_results; i++)
+  {
+    enum GNUNET_DB_QueryStatus qs;
+    bool found = false;
+
+    if (GNUNET_OK !=
+        GNUNET_PQ_extract_result (result,
+                                  rs,
+                                  i))
+    {
+      GNUNET_break (0);
+      chc->failed = true;
+      return;
+    }
+
+    for (unsigned int s = 0;
+         NULL != work[s].statement;
+         s++)
+    {
+      if (0 != strcmp (table_name,
+                       work[s].table))
+        continue;
+      found = true;
+      qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+                                                 work[s].statement,
+                                                 params,
+                                                 work[s].cb,
+                                                 chc);
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Coin %s had %d transactions at %llu in table %s\n",
+                  TALER_B2S (chc->coin_pub),
+                  (int) qs,
+                  (unsigned long long) serial_id,
+                  table_name);
+      if (0 >= qs)
+        chc->failed = true;
+      break;
+    }
+    if (! found)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Coin history includes unsupported table `%s`\n",
+                  table_name);
+      chc->failed = true;
+    }
+    GNUNET_PQ_cleanup_result (rs);
+    if (chc->failed)
+      break;
+  }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_coin_transactions (
+  void *cls,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  uint64_t start_off,
+  uint64_t etag_in,
+  uint64_t *etag_out,
+  struct TALER_EXCHANGEDB_TransactionList **tlp)
+{
+  struct PostgresClosure *pg = cls;
   struct GNUNET_PQ_QueryParam params[] = {
     GNUNET_PQ_query_param_auto_from_type (coin_pub),
     GNUNET_PQ_query_param_end
   };
-  enum GNUNET_DB_QueryStatus qs;
+  struct GNUNET_PQ_QueryParam lparams[] = {
+    GNUNET_PQ_query_param_auto_from_type (coin_pub),
+    GNUNET_PQ_query_param_uint64 (&start_off),
+    GNUNET_PQ_query_param_end
+  };
   struct CoinHistoryContext chc = {
     .head = NULL,
     .coin_pub = coin_pub,
-    .pg = pg,
-    .db_cls = cls
+    .pg = pg
   };
 
-  *etag = 0;  // FIXME: etag not yet implemented!
-  PREPARE (pg, // done!
+  *tlp = NULL;
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Getting transactions for coin %s\n",
+              TALER_B2S (coin_pub));
+  PREPARE (pg,
+           "get_coin_history_etag",
+           "SELECT"
+           " coin_history_serial_id"
+           " FROM coin_history"
+           " WHERE coin_pub=$1"
+           " ORDER BY coin_history_serial_id DESC"
+           " LIMIT 1;");
+  PREPARE (pg,
+           "get_coin_history",
+           "SELECT"
+           " table_name"
+           ",serial_id"
+           " FROM coin_history"
+           " WHERE coin_pub=$1"
+           "   AND coin_history_serial_id > $2"
+           " ORDER BY coin_history_serial_id DESC;");
+  PREPARE (pg,
            "get_deposit_with_coin_pub",
            "SELECT"
            " cdep.amount_with_fee"
@@ -758,8 +876,9 @@ TEH_PG_get_coin_transactions (
            "   ON (kc.coin_pub = cdep.coin_pub)"
            " JOIN denominations denoms"
            "   USING (denominations_serial)"
-           " WHERE cdep.coin_pub=$1;");
-  PREPARE (pg, // done!
+           " WHERE cdep.coin_pub=$1"
+           "   AND cdep.coin_deposit_serial_id=$2;");
+  PREPARE (pg,
            "get_refresh_session_by_coin",
            "SELECT"
            " rc"
@@ -774,8 +893,9 @@ TEH_PG_get_coin_transactions (
            "   ON (refresh_commitments.old_coin_pub = kc.coin_pub)"
            " JOIN denominations denoms"
            "   USING (denominations_serial)"
-           " WHERE old_coin_pub=$1;");
-  PREPARE (pg, // done!
+           " WHERE old_coin_pub=$1"
+           "   AND melt_serial_id=$2;");
+  PREPARE (pg,
            "get_purse_deposit_by_coin_pub",
            "SELECT"
            " partner_base_url"
@@ -797,10 +917,26 @@ TEH_PG_get_coin_transactions (
            "   ON (pd.coin_pub = kc.coin_pub)"
            " JOIN denominations denoms"
            "   USING (denominations_serial)"
-           // FIXME: use to-be-created materialized index
-           // on coin_pub (query crosses partitions!)
-           " WHERE pd.coin_pub=$1;");
-  PREPARE (pg, // done!
+           " WHERE pd.coin_pub=$1"
+           "   AND pd.purse_deposit_serial_id=$2;");
+  PREPARE (pg,
+           "get_purse_decision_by_coin_pub",
+           "SELECT"
+           " pdes.purse_pub"
+           ",pd.amount_with_fee"
+           ",denom.fee_refund"
+           ",pdes.purse_decision_serial_id"
+           " FROM purse_decision pdes"
+           " JOIN purse_deposits pd"
+           "   USING (purse_pub)"
+           " JOIN known_coins kc"
+           "   ON (pd.coin_pub = kc.coin_pub)"
+           " JOIN denominations denom"
+           "   USING (denominations_serial)"
+           " WHERE pd.coin_pub=$1"
+           "   AND pdes.purse_decision_serial_id=$2"
+           "   AND pdes.refunded;");
+  PREPARE (pg,
            "get_refunds_by_coin",
            "SELECT"
            " bdep.merchant_pub"
@@ -819,83 +955,66 @@ TEH_PG_get_coin_transactions (
            "   ON (ref.coin_pub = kc.coin_pub)"
            " JOIN denominations denom"
            "   USING (denominations_serial)"
-           " WHERE ref.coin_pub=$1;");
-  PREPARE (pg, // done!
-           "get_purse_decision_by_coin_pub",
-           "SELECT"
-           " pdes.purse_pub"
-           ",pd.amount_with_fee"
-           ",denom.fee_refund"
-           ",pdes.purse_decision_serial_id"
-           " FROM purse_deposits pd"
-           " JOIN purse_decision pdes"
-           "   USING (purse_pub)"
-           " JOIN known_coins kc"
-           "   ON (pd.coin_pub = kc.coin_pub)"
-           " JOIN denominations denom"
-           "   USING (denominations_serial)"
-           " WHERE pd.coin_pub=$1"
-           "   AND pdes.refunded;");
-  PREPARE (pg, // done!
+           " WHERE ref.coin_pub=$1"
+           "   AND ref.refund_serial_id=$2;");
+  PREPARE (pg,
            "recoup_by_old_coin",
            "SELECT"
            " coins.coin_pub"
-           ",coin_sig"
-           ",coin_blind"
-           ",amount"
-           ",recoup_timestamp"
+           ",rr.coin_sig"
+           ",rr.coin_blind"
+           ",rr.amount"
+           ",rr.recoup_timestamp"
            ",denoms.denom_pub_hash"
            ",coins.denom_sig"
-           ",recoup_refresh_uuid"
-           " FROM recoup_refresh"
+           ",rr.recoup_refresh_uuid"
+           " FROM recoup_refresh rr"
            " JOIN known_coins coins"
            "   USING (coin_pub)"
            " JOIN denominations denoms"
            "   USING (denominations_serial)"
-           " WHERE rrc_serial IN"
+           " WHERE recoup_refresh_uuid=$2"
+           "   AND rrc_serial IN"
            "   (SELECT rrc.rrc_serial"
-           "    FROM refresh_commitments"
-           "       JOIN refresh_revealed_coins rrc"
-           "           USING (melt_serial_id)"
-           "    WHERE old_coin_pub=$1);");
-  PREPARE (pg, // done
+           "    FROM refresh_commitments melt"
+           "    JOIN refresh_revealed_coins rrc"
+           "      USING (melt_serial_id)"
+           "    WHERE melt.old_coin_pub=$1);");
+  PREPARE (pg,
            "recoup_by_coin",
            "SELECT"
-           " reserves.reserve_pub"
+           " res.reserve_pub"
            ",denoms.denom_pub_hash"
-           ",coin_sig"
-           ",coin_blind"
-           ",amount"
-           ",recoup_timestamp"
-           ",recoup_uuid"
+           ",rcp.coin_sig"
+           ",rcp.coin_blind"
+           ",rcp.amount"
+           ",rcp.recoup_timestamp"
+           ",rcp.recoup_uuid"
            " FROM recoup rcp"
-           /* NOTE: suboptimal JOIN follows: crosses shards!
-              Could theoretically be improved via a materialized
-              index. But likely not worth it (query is rare and
-              number of reserve shards might be limited) */
            " JOIN reserves_out ro"
            "   USING (reserve_out_serial_id)"
-           " JOIN reserves"
+           " JOIN reserves res"
            "   USING (reserve_uuid)"
            " JOIN known_coins coins"
            "   USING (coin_pub)"
            " JOIN denominations denoms"
            "   ON (denoms.denominations_serial = coins.denominations_serial)"
-           " WHERE coins.coin_pub=$1;");
+           " WHERE rcp.recoup_uuid=$2"
+           "   AND coins.coin_pub=$1;");
   /* Used in #postgres_get_coin_transactions() to obtain recoup transactions
      for a refreshed coin */
-  PREPARE (pg, // done!
+  PREPARE (pg,
            "recoup_by_refreshed_coin",
            "SELECT"
            " old_coins.coin_pub AS old_coin_pub"
-           ",coin_sig"
-           ",coin_blind"
-           ",amount"
-           ",recoup_timestamp"
+           ",rr.coin_sig"
+           ",rr.coin_blind"
+           ",rr.amount"
+           ",rr.recoup_timestamp"
            ",denoms.denom_pub_hash"
            ",coins.denom_sig"
            ",recoup_refresh_uuid"
-           " FROM recoup_refresh"
+           " FROM recoup_refresh rr"
            "    JOIN refresh_revealed_coins rrc"
            "      USING (rrc_serial)"
            "    JOIN refresh_commitments rfc"
@@ -903,11 +1022,12 @@ TEH_PG_get_coin_transactions (
            "    JOIN known_coins old_coins"
            "      ON (rfc.old_coin_pub = old_coins.coin_pub)"
            "    JOIN known_coins coins"
-           "      ON (recoup_refresh.coin_pub = coins.coin_pub)"
+           "      ON (rr.coin_pub = coins.coin_pub)"
            "    JOIN denominations denoms"
            "      ON (denoms.denominations_serial = 
coins.denominations_serial)"
-           " WHERE coins.coin_pub=$1;");
-  PREPARE (pg, // done
+           " WHERE rr.recoup_refresh_uuid=$2"
+           "   AND coins.coin_pub=$1;");
+  PREPARE (pg,
            "reserve_open_by_coin",
            "SELECT"
            " reserve_open_deposit_uuid"
@@ -915,36 +1035,97 @@ TEH_PG_get_coin_transactions (
            ",reserve_sig"
            ",contribution"
            " FROM reserves_open_deposits"
-           " WHERE coin_pub=$1;");
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Getting transactions for coin %s\n",
-              TALER_B2S (coin_pub));
-  for (unsigned int i = 0; NULL != work[i].statement; i++)
+           " WHERE coin_pub=$1"
+           "   AND reserve_open_deposit_uuid=$2;");
+
+  for (unsigned int i = 0; i<RETRIES; i++)
   {
-    qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
-                                               work[i].statement,
-                                               params,
-                                               work[i].cb,
-                                               &chc);
+    enum GNUNET_DB_QueryStatus qs;
+    uint64_t end;
+    struct GNUNET_PQ_ResultSpec rs[] = {
+      GNUNET_PQ_result_spec_uint64 ("coin_history_serial_id",
+                                    &end),
+      GNUNET_PQ_result_spec_end
+    };
+
+    if (GNUNET_OK !=
+        TEH_PG_start_read_committed (pg,
+                                     "get-coin-transactions"))
+    {
+      GNUNET_break (0);
+      return GNUNET_DB_STATUS_HARD_ERROR;
+    }
+    /* First only check the last item, to see if
+       we even need to iterate */
+    qs = GNUNET_PQ_eval_prepared_singleton_select (
+      pg->conn,
+      "get_coin_history_etag",
+      params,
+      rs);
+    switch (qs)
+    {
+    case GNUNET_DB_STATUS_HARD_ERROR:
+      TEH_PG_rollback (pg);
+      return qs;
+    case GNUNET_DB_STATUS_SOFT_ERROR:
+      TEH_PG_rollback (pg);
+      continue;
+    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+      TEH_PG_rollback (pg);
+      return qs;
+    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+      *etag_out = end;
+      if (end == etag_in)
+        return qs;
+    }
+    /* We indeed need to iterate over the history */
     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Coin %s yielded %d transactions of type %s\n",
+                "Current ETag for coin %s is %llu\n",
                 TALER_B2S (coin_pub),
-                qs,
-                work[i].statement);
-    if ( (0 > qs) ||
-         (chc.failed) )
+                (unsigned long long) end);
+
+    qs = GNUNET_PQ_eval_prepared_multi_select (
+      pg->conn,
+      "get_coin_history",
+      lparams,
+      &handle_history_entry,
+      &chc);
+    switch (qs)
+    {
+    case GNUNET_DB_STATUS_HARD_ERROR:
+      TEH_PG_rollback (pg);
+      return qs;
+    case GNUNET_DB_STATUS_SOFT_ERROR:
+      TEH_PG_rollback (pg);
+      continue;
+    default:
+      break;
+    }
+    if (chc.failed)
+    {
+      TEH_PG_rollback (pg);
+      TEH_COMMON_free_coin_transaction_list (pg,
+                                             chc.head);
+      return GNUNET_DB_STATUS_SOFT_ERROR;
+    }
+    qs = TEH_PG_commit (pg);
+    switch (qs)
     {
-      if (NULL != chc.head)
-        TEH_COMMON_free_coin_transaction_list (cls,
-                                               chc.head);
-      *tlp = NULL;
-      if (chc.failed)
-        qs = GNUNET_DB_STATUS_HARD_ERROR;
+    case GNUNET_DB_STATUS_HARD_ERROR:
+      TEH_COMMON_free_coin_transaction_list (pg,
+                                             chc.head);
+      chc.head = NULL;
       return qs;
+    case GNUNET_DB_STATUS_SOFT_ERROR:
+      TEH_COMMON_free_coin_transaction_list (pg,
+                                             chc.head);
+      chc.head = NULL;
+      continue;
+    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+      *tlp = chc.head;
+      return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
     }
   }
-  *tlp = chc.head;
-  if (NULL == chc.head)
-    return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
-  return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+  return GNUNET_DB_STATUS_SOFT_ERROR;
 }
diff --git a/src/exchangedb/pg_get_coin_transactions.h 
b/src/exchangedb/pg_get_coin_transactions.h
index d49b97bc..c19df387 100644
--- a/src/exchangedb/pg_get_coin_transactions.h
+++ b/src/exchangedb/pg_get_coin_transactions.h
@@ -27,23 +27,31 @@
 
 
 /**
- * Compile a list of all (historic) transactions performed with the given coin
- * (/refresh/melt, /deposit, /refund and /recoup operations).
- * Should return 0 if @a etag is already current, otherwise
- * return the full history and update @a etag. @a etag
- * should be set to the last row ID of the given coin
- * in the coin history table.
+ * Compile a list of (historic) transactions performed with the given coin
+ * (melt, refund, recoup and deposit operations).  Should return 0 if the @a
+ * coin_pub is unknown, otherwise determine @a etag_out and if it is past @a
+ * etag_in return the history after @a start_off. @a etag_out should be set
+ * to the last row ID of the given @a coin_pub in the coin history table.
  *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param cls the @e cls of this struct with the plugin-specific state
  * @param coin_pub coin to investigate
- * @param[in,out] etag known etag, updated to current etag * @param[out] tlp 
set to list of transactions, NULL if coin is fresh
+ * @param start_off starting offset from which on to return entries
+ * @param etag_in up to this offset the client already has a response, do not
+ *                   return anything unless @a etag_out will be larger
+ * @param[out] etag_out set to the latest history offset known for this @a 
coin_pub
+ * @param[out] tlp set to list of transactions, set to NULL if coin has no
+ *             transaction history past @a start_off or if @a etag_in is equal
+ *             to the value written to @a etag_out.
  * @return database transaction status
  */
 enum GNUNET_DB_QueryStatus
 TEH_PG_get_coin_transactions (
   void *cls,
   const struct TALER_CoinSpendPublicKeyP *coin_pub,
-  uint64_t *etag,
+  uint64_t start_off,
+  uint64_t etag_in,
+  uint64_t *etag_out,
   struct TALER_EXCHANGEDB_TransactionList **tlp);
 
+
 #endif
diff --git a/src/exchangedb/test_exchangedb.c b/src/exchangedb/test_exchangedb.c
index 9a30a189..f2df1f38 100644
--- a/src/exchangedb/test_exchangedb.c
+++ b/src/exchangedb/test_exchangedb.c
@@ -1723,13 +1723,16 @@ run (void *cls)
       /* Just to test fetching a coin with melt history */
       struct TALER_EXCHANGEDB_TransactionList *tl;
       enum GNUNET_DB_QueryStatus qs;
-      uint64_t etag = 0;
+      uint64_t etag;
 
       qs = plugin->get_coin_transactions (plugin->cls,
                                           &refresh.coin.coin_pub,
+                                          0,
+                                          0,
                                           &etag,
                                           &tl);
-      FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs);
+      FAILIF (0 >= qs);
+      FAILIF (NULL == tl);
       plugin->free_coin_transaction_list (plugin->cls,
                                           tl);
     }
@@ -1980,6 +1983,8 @@ run (void *cls)
 
     qs = plugin->get_coin_transactions (plugin->cls,
                                         &refund.coin.coin_pub,
+                                        0,
+                                        0,
                                         &etag,
                                         &tl);
   }
@@ -2437,7 +2442,7 @@ main (int argc,
     return -1;
   }
   GNUNET_log_setup (argv[0],
-                    "WARNING",
+                    "INFO",
                     NULL);
   plugin_name++;
   (void) GNUNET_asprintf (&testname,
diff --git a/src/include/taler_exchangedb_plugin.h 
b/src/include/taler_exchangedb_plugin.h
index cc71f777..fc11a292 100644
--- a/src/include/taler_exchangedb_plugin.h
+++ b/src/include/taler_exchangedb_plugin.h
@@ -4652,23 +4652,29 @@ struct TALER_EXCHANGEDB_Plugin
 
 
   /**
-   * Compile a list of all (historic) transactions performed
-   * with the given coin (melt, refund, recoup and deposit operations).
-   * Should return 0 if @a etag is already current, otherwise
-   * return the full history and update @a etag. @a etag
-   * should be set to the last row ID of the given coin
-   * in the coin history table.
+   * Compile a list of (historic) transactions performed with the given coin
+   * (melt, refund, recoup and deposit operations).  Should return 0 if the @a
+   * coin_pub is unknown, otherwise determine @a etag_out and if it is past @a
+   * etag_in return the history after @a start_off. @a etag_out should be set
+   * to the last row ID of the given @a coin_pub in the coin history table.
    *
    * @param cls the @e cls of this struct with the plugin-specific state
    * @param coin_pub coin to investigate
-   * @param[in,out] etag known etag, updated to current etag
-   * @param[out] tlp set to list of transactions, NULL if coin is fresh
+   * @param start_off starting offset from which on to return entries
+   * @param etag_in up to this offset the client already has a response, do not
+   *                   return anything unless @a etag_out will be larger
+   * @param[out] etag_out set to the latest history offset known for this @a 
coin_pub
+   * @param[out] tlp set to list of transactions, set to NULL if coin has no
+   *             transaction history past @a start_off or if @a etag_in is 
equal
+   *             to the value written to @a etag_out.
    * @return database transaction status
    */
   enum GNUNET_DB_QueryStatus
   (*get_coin_transactions)(void *cls,
                            const struct TALER_CoinSpendPublicKeyP *coin_pub,
-                           uint64_t *etag,
+                           uint64_t start_off,
+                           uint64_t etag_in,
+                           uint64_t *etag_out,
                            struct TALER_EXCHANGEDB_TransactionList **tlp);
 
 

-- 
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]