towards fixing #3717 and #3633

This commit is contained in:
Christian Grothoff 2015-05-13 15:57:35 +02:00
parent dc4847220a
commit d5960b50af
4 changed files with 152 additions and 266 deletions

View File

@ -682,28 +682,28 @@ struct TALER_MINTDB_Plugin
struct TALER_MINTDB_Session *db, struct TALER_MINTDB_Session *db,
struct TALER_MINTDB_Reserve *reserve); struct TALER_MINTDB_Reserve *reserve);
/* FIXME: add functions to add bank transfers to our DB
(and to test if we already did add one) (#3633/#3717) */
/** /**
* Insert a incoming transaction into reserves. New reserves are also created * Insert a incoming transaction into reserves. New reserves are
* through this function. * also created through this function.
* *
* @param cls the @e cls of this struct with the plugin-specific state * @param cls the @e cls of this struct with the plugin-specific state
* @param db the database connection handle * @param db the database connection handle
* @param reserve the reserve structure. The public key of the reserve should * @param reserve_pub public key of the reserve
* be set here. Upon successful execution of this function, the
* balance and expiration of the reserve will be updated.
* @param balance the amount that has to be added to the reserve * @param balance the amount that has to be added to the reserve
* @param details bank transaction details justifying the increment,
* must be unique for each incoming transaction
* @param expiry the new expiration time for the reserve * @param expiry the new expiration time for the reserve
* @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failures * @return #GNUNET_OK upon success; #GNUNET_NO if the given
* @a details are already known for this @a reserve_pub,
* #GNUNET_SYSERR upon failures (DB error, incompatible currency)
*/ */
int int
(*reserves_in_insert) (void *cls, (*reserves_in_insert) (void *cls,
struct TALER_MINTDB_Session *db, struct TALER_MINTDB_Session *db,
struct TALER_MINTDB_Reserve *reserve, const struct TALER_ReservePublicKeyP *reserve_pub,
const struct TALER_Amount *balance, const struct TALER_Amount *balance,
const char *details,
const struct GNUNET_TIME_Absolute expiry); const struct GNUNET_TIME_Absolute expiry);

View File

@ -41,7 +41,6 @@ taler_mint_reservemod_LDADD = \
$(top_builddir)/src/util/libtalerutil.la \ $(top_builddir)/src/util/libtalerutil.la \
$(top_builddir)/src/pq/libtalerpq.la \ $(top_builddir)/src/pq/libtalerpq.la \
$(top_builddir)/src/mintdb/libtalermintdb.la \ $(top_builddir)/src/mintdb/libtalermintdb.la \
-lpq \
-lgnunetutil $(XLIB) -lgnunetutil $(XLIB)
taler_mint_reservemod_LDFLAGS = \ taler_mint_reservemod_LDFLAGS = \
$(POSTGRESQL_LDFLAGS) $(POSTGRESQL_LDFLAGS)
@ -57,7 +56,6 @@ taler_mint_dbinit_LDADD = \
$(top_builddir)/src/util/libtalerutil.la \ $(top_builddir)/src/util/libtalerutil.la \
$(top_builddir)/src/pq/libtalerpq.la \ $(top_builddir)/src/pq/libtalerpq.la \
$(top_builddir)/src/mintdb/libtalermintdb.la \ $(top_builddir)/src/mintdb/libtalermintdb.la \
-lpq \
-lgnunetutil $(XLIB) -lgnunetutil $(XLIB)
taler_mint_dbinit_LDFLAGS = \ taler_mint_dbinit_LDFLAGS = \
$(POSTGRESQL_LDFLAGS) $(POSTGRESQL_LDFLAGS)

View File

@ -28,181 +28,25 @@
#include "taler_mintdb_plugin.h" #include "taler_mintdb_plugin.h"
#include "taler_mintdb_lib.h" #include "taler_mintdb_lib.h"
/**
* After what time to inactive reserves expire?
*/
#define RESERVE_EXPIRATION GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_YEARS, 5)
/** /**
* Director of the mint, containing the keys. * Director of the mint, containing the keys.
*/ */
static char *mint_directory; static char *mint_directory;
/**
* Public key of the reserve to manipulate.
*/
static struct GNUNET_CRYPTO_EddsaPublicKey *reserve_pub;
/** /**
* Handle to the mint's configuration * Handle to the mint's configuration
*/ */
static struct GNUNET_CONFIGURATION_Handle *cfg; static struct GNUNET_CONFIGURATION_Handle *cfg;
/** /**
* Database connection handle. * Our DB plugin.
*/ */
static PGconn *db_conn; static struct TALER_MINTDB_Plugin *plugin;
/**
* Create a new or add to existing reserve. Fails if currencies do
* not match.
*
* @param denom denomination to add
* @return #GNUNET_OK on success,
* #GNUNET_SYSERR on error
*/
// FIXME: this should use the DB abstraction layer. (#3717)
// FIXME: this should be done by adding an inbound transaction
// to the table with the transactions for this reserve,
// not by modifying some 'total' value for the reserve!
// (we should in fact probably never modify, always just append!) (#3633)
static int
reservemod_add (struct TALER_Amount denom)
{
PGresult *result;
const void *param_values[] = {
reserve_pub
};
int param_lengths[] = {
sizeof(struct GNUNET_CRYPTO_EddsaPublicKey)
};
int param_formats[] = {
1
};
struct TALER_Amount old_denom;
struct TALER_Amount new_denom;
struct TALER_AmountNBO new_denom_nbo;
result = PQexecParams (db_conn,
"SELECT balance_value, balance_fraction, balance_currency"
" FROM reserves"
" WHERE reserve_pub=$1"
" LIMIT 1;",
1,
NULL,
(const char * const *) param_values,
param_lengths,
param_formats,
1);
if (PGRES_TUPLES_OK != PQresultStatus (result))
{
fprintf (stderr,
"Select failed: %s\n",
PQresultErrorMessage (result));
return GNUNET_SYSERR;
}
if (0 == PQntuples (result))
{
struct GNUNET_TIME_AbsoluteNBO exnbo;
uint32_t value = htonl (denom.value);
uint32_t fraction = htonl (denom.fraction);
const void *param_values[] = {
reserve_pub,
&value,
&fraction,
denom.currency,
&exnbo
};
int param_lengths[] = {
sizeof (struct GNUNET_CRYPTO_EddsaPublicKey),
sizeof (uint32_t),
sizeof (uint32_t),
strlen (denom.currency),
sizeof (struct GNUNET_TIME_AbsoluteNBO)
};
int param_formats[] = {
1, 1, 1, 1, 1
};
exnbo = GNUNET_TIME_absolute_hton (GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_YEARS));
result = PQexecParams (db_conn,
"INSERT INTO reserves (reserve_pub, balance_value, balance_fraction, balance_currency, expiration_date)"
" VALUES ($1,$2,$3,$4,$5);",
5,
NULL,
(const char **) param_values,
param_lengths,
param_formats,
1);
if (PGRES_COMMAND_OK != PQresultStatus (result))
{
fprintf (stderr,
"Insert failed: %s\n",
PQresultErrorMessage (result));
return GNUNET_SYSERR;
}
}
else
{
const void *param_values[] = {
&new_denom_nbo.value,
&new_denom_nbo.fraction,
reserve_pub
};
int param_lengths[] = {
sizeof (new_denom_nbo.value),
sizeof (new_denom_nbo.fraction),
sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)
};
int param_formats[] = {
1, 1, 1
};
GNUNET_assert (GNUNET_OK ==
TALER_PQ_extract_amount (result, 0,
"balance_value",
"balance_fraction",
"balance_currency",
&old_denom));
if (GNUNET_OK !=
TALER_amount_add (&new_denom,
&old_denom,
&denom))
{
fprintf (stderr,
"Integer overflow when computing new balance!\n");
return GNUNET_SYSERR;
}
TALER_amount_hton (&new_denom_nbo,
&new_denom);
result = PQexecParams (db_conn,
"UPDATE reserves"
" SET balance_value = $1, balance_fraction = $2, status_sig = NULL, status_sign_pub = NULL"
" WHERE reserve_pub = $3;",
3,
NULL,
(const char **) param_values,
param_lengths,
param_formats,
1);
if (PGRES_COMMAND_OK != PQresultStatus (result))
{
fprintf (stderr,
"Update failed: %s\n",
PQresultErrorMessage (result));
return GNUNET_SYSERR;
}
/* Yes, for historic reasons libpq returns a 'const char *'... */
if (0 != strcmp ("1",
PQcmdTuples (result)))
{
fprintf (stderr,
"Update failed (updated `%s' tupes instead of '1')\n",
PQcmdTuples (result));
return GNUNET_SYSERR;
}
}
return GNUNET_OK;
}
/** /**
@ -215,15 +59,23 @@ reservemod_add (struct TALER_Amount denom)
int int
main (int argc, char *const *argv) main (int argc, char *const *argv)
{ {
static char *reserve_pub_str; char *reserve_pub_str = NULL;
static char *add_str; char *add_str = NULL;
static const struct GNUNET_GETOPT_CommandLineOption options[] = { struct TALER_Amount add_value;
char *details = NULL;
struct TALER_ReservePublicKeyP reserve_pub;
struct GNUNET_TIME_Absolute expiration;
struct TALER_MINTDB_Session *session;
const struct GNUNET_GETOPT_CommandLineOption options[] = {
{'a', "add", "DENOM", {'a', "add", "DENOM",
"value to add", 1, "value to add", 1,
&GNUNET_GETOPT_set_string, &add_str}, &GNUNET_GETOPT_set_string, &add_str},
{'d', "mint-dir", "DIR", {'d', "mint-dir", "DIR",
"mint directory with keys to update", 1, "mint directory with keys to update", 1,
&GNUNET_GETOPT_set_filename, &mint_directory}, &GNUNET_GETOPT_set_filename, &mint_directory},
{'D', "details", "JSON",
"details about the bank transaction which justify why we add this amount", 1,
&GNUNET_GETOPT_set_string, &details},
TALER_GETOPT_OPTION_HELP ("Deposit funds into a Taler reserve"), TALER_GETOPT_OPTION_HELP ("Deposit funds into a Taler reserve"),
{'R', "reserve", "KEY", {'R', "reserve", "KEY",
"reserve (public key) to modify", 1, "reserve (public key) to modify", 1,
@ -231,7 +83,7 @@ main (int argc, char *const *argv)
GNUNET_GETOPT_OPTION_VERSION (VERSION "-" VCS_VERSION), GNUNET_GETOPT_OPTION_VERSION (VERSION "-" VCS_VERSION),
GNUNET_GETOPT_OPTION_END GNUNET_GETOPT_OPTION_END
}; };
char *connection_cfg_str; int ret;
GNUNET_assert (GNUNET_OK == GNUNET_assert (GNUNET_OK ==
GNUNET_log_setup ("taler-mint-reservemod", GNUNET_log_setup ("taler-mint-reservemod",
@ -248,19 +100,35 @@ main (int argc, char *const *argv)
"Mint directory not given\n"); "Mint directory not given\n");
return 1; return 1;
} }
reserve_pub = GNUNET_new (struct GNUNET_CRYPTO_EddsaPublicKey);
if ((NULL == reserve_pub_str) || if ((NULL == reserve_pub_str) ||
(GNUNET_OK != (GNUNET_OK !=
GNUNET_STRINGS_string_to_data (reserve_pub_str, GNUNET_STRINGS_string_to_data (reserve_pub_str,
strlen (reserve_pub_str), strlen (reserve_pub_str),
reserve_pub, &reserve_pub,
sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)))) sizeof (struct TALER_ReservePublicKeyP))))
{ {
fprintf (stderr, fprintf (stderr,
"Parsing reserve key invalid\n"); "Parsing reserve key invalid\n");
return 1; return 1;
} }
if ( (NULL == add_str) ||
(GNUNET_OK !=
TALER_string_to_amount (add_str,
&add_value)) )
{
fprintf (stderr,
"Failed to parse currency amount `%s'\n",
add_str);
return 1;
}
if (NULL == details)
{
fprintf (stderr,
"No wiring details given (justification required)\n");
return 1;
}
cfg = TALER_config_load (mint_directory); cfg = TALER_config_load (mint_directory);
if (NULL == cfg) if (NULL == cfg)
{ {
@ -268,46 +136,59 @@ main (int argc, char *const *argv)
"Failed to load mint configuration\n"); "Failed to load mint configuration\n");
return 1; return 1;
} }
if (GNUNET_OK != ret = 1;
GNUNET_CONFIGURATION_get_value_string (cfg, if (NULL ==
"mint", (plugin = TALER_MINTDB_plugin_load (cfg)))
"db",
&connection_cfg_str))
{ {
fprintf (stderr, fprintf (stderr,
"Database configuration string not found\n"); "Failed to initialize database plugin.\n");
return 1; goto cleanup;
} }
db_conn = PQconnectdb (connection_cfg_str);
if (CONNECTION_OK != PQstatus (db_conn))
{
fprintf (stderr,
"Database connection failed: %s\n",
PQerrorMessage (db_conn));
return 1;
}
if (NULL != add_str)
{
struct TALER_Amount add_value;
if (GNUNET_OK != session = plugin->get_session (plugin->cls,
TALER_string_to_amount (add_str, GNUNET_NO);
&add_value)) if (NULL == session)
{ {
fprintf (stderr, fprintf (stderr,
"Failed to parse currency amount `%s'\n", "Failed to initialize DB session\n");
add_str); goto cleanup;
return 1;
} }
if (GNUNET_OK != if (GNUNET_OK !=
reservemod_add (add_value)) plugin->start (plugin->cls,
session))
{
fprintf (stderr,
"Failed to start transaction\n");
goto cleanup;
}
expiration = GNUNET_TIME_relative_to_absolute (RESERVE_EXPIRATION);
if (GNUNET_OK !=
plugin->reserves_in_insert (plugin->cls,
session,
&reserve_pub,
&add_value,
details,
expiration))
{ {
fprintf (stderr, fprintf (stderr,
"Failed to update reserve.\n"); "Failed to update reserve.\n");
return 1; goto cleanup;
} }
if (GNUNET_OK !=
plugin->commit (plugin->cls,
session))
{
fprintf (stderr,
"Failed to commit transaction\n");
goto cleanup;
} }
return 0; ret = 0;
cleanup:
if (NULL != plugin)
TALER_MINTDB_plugin_unload (plugin);
if (NULL != cfg)
GNUNET_CONFIGURATION_destroy (cfg);
return ret;
} }
/* end taler-mint-reservemod.c */ /* end taler-mint-reservemod.c */

View File

@ -214,11 +214,17 @@ postgres_create_tables (void *cls,
",balance_val INT8 NOT NULL" ",balance_val INT8 NOT NULL"
",balance_frac INT4 NOT NULL" ",balance_frac INT4 NOT NULL"
",balance_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL" ",balance_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL"
",details VARCHAR NOT NULL "
",expiration_date INT8 NOT NULL" ",expiration_date INT8 NOT NULL"
" CONSTRAINT unique_details PRIMARY KEY (reserve_pub,details)"
");"); ");");
/* Create an index on the foreign key as it is not created automatically by PSQL */ /* Create indices on reserves_in */
SQLEXEC ("CREATE INDEX reserves_in_reserve_pub_index" SQLEXEC ("CREATE INDEX reserves_in_reserve_pub_index"
" ON reserves_in (reserve_pub);"); " ON reserves_in (reserve_pub);");
SQLEXEC ("CREATE INDEX reserves_in_reserve_pub_details_index"
" ON reserves_in (reserve_pub,details);");
SQLEXEC ("CREATE INDEX expiration_index"
" ON reserves_in (expiration_date);");
/* Table with the withdraw operations that have been performed on a reserve. /* Table with the withdraw operations that have been performed on a reserve.
TODO: maybe rename to "reserves_out"? TODO: maybe rename to "reserves_out"?
TODO: is blind_ev really a _primary key_? Is this constraint useful? */ TODO: is blind_ev really a _primary key_? Is this constraint useful? */
@ -426,8 +432,9 @@ postgres_prepare (PGconn *db_conn)
" balance_val," " balance_val,"
" balance_frac," " balance_frac,"
" balance_curr," " balance_curr,"
" details,"
" expiration_date) VALUES (" " expiration_date) VALUES ("
" $1, $2, $3, $4, $5);", " $1, $2, $3, $4, $5, $6);",
5, NULL); 5, NULL);
PREPARE ("get_reserves_in_transactions", PREPARE ("get_reserves_in_transactions",
"SELECT" "SELECT"
@ -435,6 +442,7 @@ postgres_prepare (PGconn *db_conn)
",balance_frac" ",balance_frac"
",balance_curr" ",balance_curr"
",expiration_date" ",expiration_date"
",details"
" FROM reserves_in WHERE reserve_pub=$1", " FROM reserves_in WHERE reserve_pub=$1",
1, NULL); 1, NULL);
PREPARE ("insert_collectable_blindcoin", PREPARE ("insert_collectable_blindcoin",
@ -676,6 +684,7 @@ postgres_prepare (PGconn *db_conn)
" FROM deposits WHERE " " FROM deposits WHERE "
" coin_pub = $1", " coin_pub = $1",
1, NULL); 1, NULL);
return GNUNET_OK; return GNUNET_OK;
#undef PREPARE #undef PREPARE
} }
@ -905,7 +914,7 @@ postgres_reserve_get (void *cls,
if (PGRES_TUPLES_OK != PQresultStatus (result)) if (PGRES_TUPLES_OK != PQresultStatus (result))
{ {
QUERY_ERR (result); QUERY_ERR (result);
PQclear (result); PQclear (resultE);
return GNUNET_SYSERR; return GNUNET_SYSERR;
} }
if (0 == PQntuples (result)) if (0 == PQntuples (result))
@ -974,38 +983,39 @@ postgres_reserves_update (void *cls,
* *
* @param cls the `struct PostgresClosure` with the plugin-specific state * @param cls the `struct PostgresClosure` with the plugin-specific state
* @param session the database connection handle * @param session the database connection handle
* @param reserve the reserve structure. The public key of the reserve should * @param reserve_pub public key of the reserve
* be set here. Upon successful execution of this function, the
* balance and expiration of the reserve will be updated.
* @param balance the amount that has to be added to the reserve * @param balance the amount that has to be added to the reserve
* @param details bank transaction details justifying the increment,
* must be unique for each incoming transaction
* @param expiry the new expiration time for the reserve * @param expiry the new expiration time for the reserve
* @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failures * @return #GNUNET_OK upon success; #GNUNET_NO if the given
* @a details are already known for this @a reserve_pub,
* #GNUNET_SYSERR upon failures (DB error, incompatible currency)
*/ */
static int static int
postgres_reserves_in_insert (void *cls, postgres_reserves_in_insert (void *cls,
struct TALER_MINTDB_Session *session, struct TALER_MINTDB_Session *session,
struct TALER_MINTDB_Reserve *reserve, struct TALER_ReservePublicKeyP *reserve_pub,
const struct TALER_Amount *balance, const struct TALER_Amount *balance,
const char *details,
const struct GNUNET_TIME_Absolute expiry) const struct GNUNET_TIME_Absolute expiry)
{ {
PGresult *result; PGresult *result;
int reserve_exists; int reserve_exists;
struct TALER_MINTDB_Reserve reserve;
struct TALER_MINTDB_Reserve updated_reserve;
result = NULL; result = NULL;
if (NULL == reserve)
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
if (GNUNET_OK != postgres_start (cls, if (GNUNET_OK != postgres_start (cls,
session)) session))
{ {
GNUNET_break (0); GNUNET_break (0);
return GNUNET_SYSERR; return GNUNET_SYSERR;
} }
reserve.pub = *reserve_pub;
reserve_exists = postgres_reserve_get (cls, reserve_exists = postgres_reserve_get (cls,
session, session,
reserve); &reserve);
if (GNUNET_SYSERR == reserve_exists) if (GNUNET_SYSERR == reserve_exists)
{ {
postgres_rollback (cls, postgres_rollback (cls,
@ -1017,7 +1027,7 @@ postgres_reserves_in_insert (void *cls,
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Reserve does not exist; creating a new one\n"); "Reserve does not exist; creating a new one\n");
struct TALER_PQ_QueryParam params[] = { struct TALER_PQ_QueryParam params[] = {
TALER_PQ_QUERY_PARAM_PTR (&reserve->pub), TALER_PQ_QUERY_PARAM_PTR (reserve_pub),
TALER_PQ_QUERY_PARAM_AMOUNT (balance), TALER_PQ_QUERY_PARAM_AMOUNT (balance),
TALER_PQ_QUERY_PARAM_ABSOLUTE_TIME (expiry), TALER_PQ_QUERY_PARAM_ABSOLUTE_TIME (expiry),
TALER_PQ_QUERY_PARAM_END TALER_PQ_QUERY_PARAM_END
@ -1031,13 +1041,34 @@ postgres_reserves_in_insert (void *cls,
goto rollback; goto rollback;
} }
} }
else
{
/* Update reserve */
updated_reserve.pub = reserve->pub;
if (GNUNET_OK !=
TALER_amount_add (&updated_reserve.balance,
&reserve->balance,
balance))
{
/* currency overflow or incompatible currency */
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Attempt to deposit incompatible amount into reserve\n");
goto rollback;
}
updated_reserve.expiry = GNUNET_TIME_absolute_max (expiry,
reserve->expiry);
}
if (NULL != result) if (NULL != result)
PQclear (result); PQclear (result);
result = NULL; result = NULL;
/* create new incoming transaction */ /* create new incoming transaction, SQL "primary key" logic
is used to guard against duplicates! */
struct TALER_PQ_QueryParam params[] = { struct TALER_PQ_QueryParam params[] = {
TALER_PQ_QUERY_PARAM_PTR (&reserve->pub), TALER_PQ_QUERY_PARAM_PTR (&reserve->pub),
TALER_PQ_QUERY_PARAM_AMOUNT (balance), TALER_PQ_QUERY_PARAM_AMOUNT (balance),
TALER_PQ_QUERY_PARAM_PTR_SIZED (details, strlen (details)),
TALER_PQ_QUERY_PARAM_ABSOLUTE_TIME (expiry), TALER_PQ_QUERY_PARAM_ABSOLUTE_TIME (expiry),
TALER_PQ_QUERY_PARAM_END TALER_PQ_QUERY_PARAM_END
}; };
@ -1051,40 +1082,17 @@ postgres_reserves_in_insert (void *cls,
} }
PQclear (result); PQclear (result);
result = NULL; result = NULL;
if (GNUNET_NO == reserve_exists) if ( (GNUNET_YES == reserve_exists) &&
{ (GNUNET_OK != postgres_reserves_update (cls,
if (GNUNET_OK != postgres_commit (cls,
session))
return GNUNET_SYSERR;
reserve->balance = *balance;
reserve->expiry = expiry;
return GNUNET_OK;
}
/* Update reserve */
struct TALER_MINTDB_Reserve updated_reserve;
updated_reserve.pub = reserve->pub;
if (GNUNET_OK !=
TALER_amount_add (&updated_reserve.balance,
&reserve->balance,
balance))
{
return GNUNET_SYSERR;
}
updated_reserve.expiry = GNUNET_TIME_absolute_max (expiry,
reserve->expiry);
if (GNUNET_OK != postgres_reserves_update (cls,
session, session,
&updated_reserve)) &updated_reserve)) )
goto rollback; goto rollback;
if (GNUNET_OK != postgres_commit (cls, if (GNUNET_OK != postgres_commit (cls,
session)) session))
return GNUNET_SYSERR; return GNUNET_SYSERR;
reserve->balance = updated_reserve.balance;
reserve->expiry = updated_reserve.expiry;
return GNUNET_OK; return GNUNET_OK;
rollback: rollback:
if (NULL != result)
PQclear (result); PQclear (result);
postgres_rollback (cls, postgres_rollback (cls,
session); session);
@ -2426,7 +2434,6 @@ postgres_get_coin_transactions (void *cls,
} }
/** /**
* Initialize Postgres database subsystem. * Initialize Postgres database subsystem.
* *