simplify DB logic in auditor-httpd

This commit is contained in:
Christian Grothoff 2020-01-17 13:05:29 +01:00
parent 540b22ce1c
commit 11a9dc2b4f
No known key found for this signature in database
GPG Key ID: 939E6BE1E29FC3CC
5 changed files with 68 additions and 377 deletions

View File

@ -49,7 +49,6 @@ taler_auditor_LDADD = \
taler_auditor_httpd_SOURCES = \ taler_auditor_httpd_SOURCES = \
taler-auditor-httpd.c taler-auditor-httpd.h \ taler-auditor-httpd.c taler-auditor-httpd.h \
taler-auditor-httpd_db.c taler-auditor-httpd_db.h \
taler-auditor-httpd_deposit-confirmation.c taler-auditor-httpd_deposit-confirmation.h \ taler-auditor-httpd_deposit-confirmation.c taler-auditor-httpd_deposit-confirmation.h \
taler-auditor-httpd_exchanges.c taler-auditor-httpd_exchanges.h \ taler-auditor-httpd_exchanges.c taler-auditor-httpd_exchanges.h \
taler-auditor-httpd_mhd.c taler-auditor-httpd_mhd.h taler-auditor-httpd_mhd.c taler-auditor-httpd_mhd.h

View File

@ -1,129 +0,0 @@
/*
This file is part of TALER
Copyright (C) 2014-2018 GNUnet e.V.
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
Foundation; either version 3, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
* @file taler-auditor-httpd_db.c
* @brief Generic database operations for the auditor.
* @author Christian Grothoff
*/
#include "platform.h"
#include <pthread.h>
#include <jansson.h>
#include <gnunet/gnunet_json_lib.h>
#include "taler_json_lib.h"
#include "taler_mhd_lib.h"
#include "taler-auditor-httpd_db.h"
#include "taler-auditor-httpd.h"
/**
* How often should we retry a transaction before giving up
* (for transactions resulting in serialization/dead locks only).
*/
#define MAX_TRANSACTION_COMMIT_RETRIES 100
/**
* Run a database transaction for @a connection.
* Starts a transaction and calls @a cb. Upon success,
* attempts to commit the transaction. Upon soft failures,
* retries @a cb a few times. Upon hard or persistent soft
* errors, generates an error message for @a connection.
*
* @param connection MHD connection to run @a cb for
* @param name name of the transaction (for debugging)
* @param[out] set to MHD response code, if transaction failed
* @param cb callback implementing transaction logic
* @param cb_cls closure for @a cb, must be read-only!
* @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
*/
int
TAH_DB_run_transaction (struct MHD_Connection *connection,
const char *name,
int *mhd_ret,
TAH_DB_TransactionCallback cb,
void *cb_cls)
{
struct TALER_AUDITORDB_Session *session;
if (NULL != mhd_ret)
*mhd_ret = -1; /* invalid value */
if (NULL == (session = TAH_plugin->get_session (TAH_plugin->cls)))
{
GNUNET_break (0);
if (NULL != mhd_ret)
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_DB_SETUP_FAILED,
"failed to establish session with database");
return GNUNET_SYSERR;
}
for (unsigned int retries = 0; retries < MAX_TRANSACTION_COMMIT_RETRIES;
retries++)
{
enum GNUNET_DB_QueryStatus qs;
if (GNUNET_OK !=
TAH_plugin->start (TAH_plugin->cls,
session))
{
GNUNET_break (0);
if (NULL != mhd_ret)
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_DB_START_FAILED,
"failed to begin transaction");
return GNUNET_SYSERR;
}
qs = cb (cb_cls,
connection,
session,
mhd_ret);
if (0 > qs)
TAH_plugin->rollback (TAH_plugin->cls,
session);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
return GNUNET_SYSERR;
if (0 <= qs)
qs = TAH_plugin->commit (TAH_plugin->cls,
session);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
if (NULL != mhd_ret)
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_DB_COMMIT_FAILED_HARD,
"failed to commit transaction");
return GNUNET_SYSERR;
}
/* make sure callback did not violate invariants! */
GNUNET_assert ( (NULL == mhd_ret) ||
(-1 == *mhd_ret) );
if (0 <= qs)
return GNUNET_OK;
}
TALER_LOG_ERROR ("Transaction `%s' commit failed %u times\n",
name,
MAX_TRANSACTION_COMMIT_RETRIES);
if (NULL != mhd_ret)
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_DB_COMMIT_FAILED_ON_RETRY,
"transaction repeatedly failed to serialize");
return GNUNET_SYSERR;
}
/* end of taler-auditor-httpd_db.c */

View File

@ -1,72 +0,0 @@
/*
This file is part of TALER
Copyright (C) 2014-2018 GNUnet e.V.
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
Foundation; either version 3, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
* @file auditor/taler-auditor-httpd_db.h
* @brief High-level (transactional-layer) database operations for the auditor
* @author Chrisitan Grothoff
*/
#ifndef TALER_AUDITOR_HTTPD_DB_H
#define TALER_AUDITOR_HTTPD_DB_H
#include <microhttpd.h>
#include "taler_auditordb_plugin.h"
/**
* Function implementing a database transaction. Runs the transaction
* logic; IF it returns a non-error code, the transaction logic MUST
* NOT queue a MHD response. IF it returns an hard error, the
* transaction logic MUST queue a MHD response and set @a mhd_ret. IF
* it returns the soft error code, the function MAY be called again to
* retry and MUST not queue a MHD response.
*
* @param cls closure
* @param connection MHD request which triggered the transaction
* @param session database session to use
* @param[out] mhd_ret set to MHD response status for @a connection,
* if transaction failed (!)
* @return transaction status
*/
typedef enum GNUNET_DB_QueryStatus
(*TAH_DB_TransactionCallback)(void *cls,
struct MHD_Connection *connection,
struct TALER_AUDITORDB_Session *session,
int *mhd_ret);
/**
* Run a database transaction for @a connection.
* Starts a transaction and calls @a cb. Upon success,
* attempts to commit the transaction. Upon soft failures,
* retries @a cb a few times. Upon hard or persistent soft
* errors, generates an error message for @a connection.
*
* @param connection MHD connection to run @a cb for
* @param name name of the transaction (for debugging)
* @param[out] set to MHD response code, if transaction failed
* @param cb callback implementing transaction logic
* @param cb_cls closure for @a cb, must be read-only!
* @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
*/
int
TAH_DB_run_transaction (struct MHD_Connection *connection,
const char *name,
int *mhd_ret,
TAH_DB_TransactionCallback cb,
void *cb_cls);
#endif
/* TALER_AUDITOR_HTTPD_DB_H */

View File

@ -1,6 +1,6 @@
/* /*
This file is part of TALER This file is part of TALER
Copyright (C) 2014-2018 Inria and GNUnet e.V. Copyright (C) 2014-2020 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the 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 terms of the GNU Affero General Public License as published by the Free Software
@ -29,98 +29,9 @@
#include "taler_json_lib.h" #include "taler_json_lib.h"
#include "taler_mhd_lib.h" #include "taler_mhd_lib.h"
#include "taler-auditor-httpd.h" #include "taler-auditor-httpd.h"
#include "taler-auditor-httpd_db.h"
#include "taler-auditor-httpd_deposit-confirmation.h" #include "taler-auditor-httpd_deposit-confirmation.h"
/**
* Send confirmation of deposit-confirmation success to client.
*
* @param connection connection to the client
* @return MHD result code
*/
static int
reply_deposit_confirmation_success (struct MHD_Connection *connection)
{
return TALER_MHD_reply_json_pack (connection,
MHD_HTTP_OK,
"{s:s}",
"status", "DEPOSIT_CONFIRMATION_OK");
}
/**
* Store exchange's signing key information in the database.
*
* @param cls a `struct TALER_AUDITORDB_ExchangeSigningKey *`
* @param connection MHD request context
* @param session database session and transaction to use
* @param[out] mhd_ret set to MHD status on error
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
store_exchange_signing_key_transaction (void *cls,
struct MHD_Connection *connection,
struct TALER_AUDITORDB_Session *session,
int *mhd_ret)
{
const struct TALER_AUDITORDB_ExchangeSigningKey *es = cls;
enum GNUNET_DB_QueryStatus qs;
qs = TAH_plugin->insert_exchange_signkey (TAH_plugin->cls,
session,
es);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
TALER_LOG_WARNING ("Failed to store exchange signing key in database\n");
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_AUDITOR_EXCHANGE_STORE_DB_ERROR,
"failed to persist exchange signing key");
}
return qs;
}
/**
* Execute database transaction for /deposit-confirmation. Runs the
* transaction logic; IF it returns a non-error code, the transaction
* logic MUST NOT queue a MHD response. IF it returns an hard error,
* the transaction logic MUST queue a MHD response and set @a mhd_ret.
* IF it returns the soft error code, the function MAY be called again
* to retry and MUST not queue a MHD response.
*
* @param cls a `struct DepositConfirmation *`
* @param connection MHD request context
* @param session database session and transaction to use -- FIXME: needed?
* @param[out] mhd_ret set to MHD status on error
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
deposit_confirmation_transaction (void *cls,
struct MHD_Connection *connection,
struct TALER_AUDITORDB_Session *session,
int *mhd_ret)
{
const struct TALER_AUDITORDB_DepositConfirmation *dc = cls;
enum GNUNET_DB_QueryStatus qs;
qs = TAH_plugin->insert_deposit_confirmation (TAH_plugin->cls,
session,
dc);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
TALER_LOG_WARNING (
"Failed to store /deposit-confirmation information in database\n");
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_DEPOSIT_CONFIRMATION_STORE_DB_ERROR,
"failed to persist deposit-confirmation data");
}
return qs;
}
/** /**
* We have parsed the JSON information about the deposit, do some * We have parsed the JSON information about the deposit, do some
* basic sanity checks (especially that the signature on the coin is * basic sanity checks (especially that the signature on the coin is
@ -141,7 +52,8 @@ verify_and_execute_deposit_confirmation (struct MHD_Connection *connection,
{ {
struct TALER_ExchangeSigningKeyValidityPS skv; struct TALER_ExchangeSigningKeyValidityPS skv;
struct TALER_DepositConfirmationPS dcs; struct TALER_DepositConfirmationPS dcs;
int mhd_ret; struct TALER_AUDITORDB_Session *session;
enum GNUNET_DB_QueryStatus qs;
/* check exchange signing key signature */ /* check exchange signing key signature */
skv.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY); skv.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY);
@ -164,14 +76,27 @@ verify_and_execute_deposit_confirmation (struct MHD_Connection *connection,
"master_sig"); "master_sig");
} }
session = TAH_plugin->get_session (TAH_plugin->cls);
if (NULL == session)
{
GNUNET_break (0);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_DB_SETUP_FAILED,
"failed to establish session with database");
}
/* execute transaction */ /* execute transaction */
if (GNUNET_OK != qs = TAH_plugin->insert_exchange_signkey (TAH_plugin->cls,
TAH_DB_run_transaction (connection, session,
"persist exchange signing key", es);
&mhd_ret, if (GNUNET_DB_STATUS_HARD_ERROR == qs)
&store_exchange_signing_key_transaction, {
(void *) es)) TALER_LOG_WARNING ("Failed to store exchange signing key in database\n");
return mhd_ret; return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_AUDITOR_EXCHANGE_STORE_DB_ERROR,
"failed to persist exchange signing key");
}
/* check deposit confirmation signature */ /* check deposit confirmation signature */
dcs.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT); dcs.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT);
@ -198,14 +123,21 @@ verify_and_execute_deposit_confirmation (struct MHD_Connection *connection,
} }
/* execute transaction */ /* execute transaction */
if (GNUNET_OK != qs = TAH_plugin->insert_deposit_confirmation (TAH_plugin->cls,
TAH_DB_run_transaction (connection, session,
"store deposit confirmation", dc);
&mhd_ret, if (GNUNET_DB_STATUS_HARD_ERROR == qs)
&deposit_confirmation_transaction, {
(void *) dc)) TALER_LOG_WARNING ("Failed to store /deposit-confirmation in database\n");
return mhd_ret; return TALER_MHD_reply_with_error (connection,
return reply_deposit_confirmation_success (connection); MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_DEPOSIT_CONFIRMATION_STORE_DB_ERROR,
"failed to persist deposit-confirmation data");
}
return TALER_MHD_reply_json_pack (connection,
MHD_HTTP_OK,
"{s:s}",
"status", "DEPOSIT_CONFIRMATION_OK");
} }

View File

@ -1,6 +1,6 @@
/* /*
This file is part of TALER This file is part of TALER
Copyright (C) 2014-2018 Inria and GNUnet e.V. Copyright (C) 2014-2020 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the 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 terms of the GNU Affero General Public License as published by the Free Software
@ -27,28 +27,9 @@
#include "taler_json_lib.h" #include "taler_json_lib.h"
#include "taler_mhd_lib.h" #include "taler_mhd_lib.h"
#include "taler-auditor-httpd.h" #include "taler-auditor-httpd.h"
#include "taler-auditor-httpd_db.h"
#include "taler-auditor-httpd_exchanges.h" #include "taler-auditor-httpd_exchanges.h"
/**
* Send confirmation of deposit-confirmation success to client.
*
* @param connection connection to the client
* @param ja array with information about exchanges
* @return MHD result code
*/
static int
reply_exchanges_success (struct MHD_Connection *connection,
json_t *ja)
{
return TALER_MHD_reply_json_pack (connection,
MHD_HTTP_OK,
"{s:o}",
"exchanges", ja);
}
/** /**
* Add exchange information to the list. * Add exchange information to the list.
* *
@ -77,45 +58,6 @@ add_exchange (void *cls,
} }
/**
* Execute database transaction for /exchanges. Obtains the list. IF
* it returns a non-error code, the transaction logic MUST NOT queue a
* MHD response. IF it returns an hard error, the transaction logic
* MUST queue a MHD response and set @a mhd_ret. IF it returns the
* soft error code, the function MAY be called again to retry and MUST
* not queue a MHD response.
*
* @param cls[in,out] a `json_t *` with an array of exchanges to be created
* @param connection MHD request context
* @param session database session and transaction to use
* @param[out] mhd_ret set to MHD status on error
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
list_exchanges (void *cls,
struct MHD_Connection *connection,
struct TALER_AUDITORDB_Session *session,
int *mhd_ret)
{
json_t *list = cls;
enum GNUNET_DB_QueryStatus qs;
qs = TAH_plugin->list_exchanges (TAH_plugin->cls,
session,
&add_exchange,
list);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
TALER_LOG_WARNING ("Failed to handle /exchanges in database\n");
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_LIST_EXCHANGES_DB_ERROR,
"Could not fetch exchange list from database");
}
return qs;
}
/** /**
* Handle a "/exchanges" request. * Handle a "/exchanges" request.
* *
@ -133,19 +75,38 @@ TAH_EXCHANGES_handler (struct TAH_RequestHandler *rh,
const char *upload_data, const char *upload_data,
size_t *upload_data_size) size_t *upload_data_size)
{ {
int mhd_ret;
json_t *ja; json_t *ja;
struct TALER_AUDITORDB_Session *session;
enum GNUNET_DB_QueryStatus qs;
session = TAH_plugin->get_session (TAH_plugin->cls);
if (NULL == session)
{
GNUNET_break (0);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_DB_SETUP_FAILED,
"failed to establish session with database");
}
ja = json_array (); ja = json_array ();
if (GNUNET_OK != qs = TAH_plugin->list_exchanges (TAH_plugin->cls,
TAH_DB_run_transaction (connection, session,
"list exchanges", &add_exchange,
&mhd_ret, ja);
&list_exchanges, if (0 > qs)
(void *) ja)) {
return mhd_ret; GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
return reply_exchanges_success (connection, json_decref (ja);
ja); TALER_LOG_WARNING ("Failed to handle /exchanges in database\n");
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_LIST_EXCHANGES_DB_ERROR,
"Could not fetch exchange list from database");
}
return TALER_MHD_reply_json_pack (connection,
MHD_HTTP_OK,
"{s:o}",
"exchanges", ja);
} }