2360 lines
75 KiB
C
2360 lines
75 KiB
C
/*
|
|
This file is part of TALER
|
|
Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors)
|
|
|
|
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, If not, see <http://www.gnu.org/licenses/>
|
|
*/
|
|
|
|
/**
|
|
* @file plugin_mintdb_postgres.c
|
|
* @brief Low-level (statement-level) Postgres database access for the mint
|
|
* @author Florian Dold
|
|
* @author Christian Grothoff
|
|
* @author Sree Harsha Totakura
|
|
*/
|
|
#include "platform.h"
|
|
#include "db_pq.h"
|
|
#include "taler_signatures.h"
|
|
#include "taler_mintdb_plugin.h"
|
|
#include <pthread.h>
|
|
#include <libpq-fe.h>
|
|
|
|
#include "plugin_mintdb_common.c"
|
|
|
|
#define TALER_TEMP_SCHEMA_NAME "taler_temporary"
|
|
|
|
#define QUERY_ERR(result) \
|
|
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Query failed at %s:%u: %s\n", __FILE__, __LINE__, PQresultErrorMessage (result))
|
|
|
|
|
|
#define BREAK_DB_ERR(result) do { \
|
|
GNUNET_break(0); \
|
|
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Database failure: %s\n", PQresultErrorMessage (result)); \
|
|
} while (0)
|
|
|
|
/**
|
|
* Shorthand for exit jumps.
|
|
*/
|
|
#define EXITIF(cond) \
|
|
do { \
|
|
if (cond) { GNUNET_break (0); goto EXITIF_exit; } \
|
|
} while (0)
|
|
|
|
|
|
#define SQLEXEC_(conn, sql, result) \
|
|
do { \
|
|
result = PQexec (conn, sql); \
|
|
if (PGRES_COMMAND_OK != PQresultStatus (result)) \
|
|
{ \
|
|
BREAK_DB_ERR (result); \
|
|
PQclear (result); result = NULL; \
|
|
goto SQLEXEC_fail; \
|
|
} \
|
|
PQclear (result); result = NULL; \
|
|
} while (0)
|
|
|
|
/**
|
|
* This the length of the currency strings (without 0-termination) we use. Note
|
|
* that we need to use this at the DB layer instead of TALER_CURRENCY_LEN as the
|
|
* DB only needs to store 3 bytes instead of 8 bytes.
|
|
*/
|
|
#define TALER_DB_CURRENCY_LEN 3
|
|
|
|
|
|
/**
|
|
* Handle for a database session (per-thread, for transactions).
|
|
*/
|
|
struct TALER_MINTDB_Session
|
|
{
|
|
/**
|
|
* Postgres connection handle.
|
|
*/
|
|
PGconn *conn;
|
|
};
|
|
|
|
|
|
/**
|
|
* Type of the "cls" argument given to each of the functions in
|
|
* our API.
|
|
*/
|
|
struct PostgresClosure
|
|
{
|
|
|
|
/**
|
|
* Thread-local database connection.
|
|
* Contains a pointer to PGconn or NULL.
|
|
*/
|
|
pthread_key_t db_conn_threadlocal;
|
|
|
|
/**
|
|
* Database connection string, as read from
|
|
* the configuration.
|
|
*/
|
|
char *TALER_MINT_db_connection_cfg_str;
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
* Set the given connection to use a temporary schema
|
|
*
|
|
* @param db the database connection
|
|
* @return #GNUNET_OK upon success; #GNUNET_SYSERR upon error
|
|
*/
|
|
static int
|
|
set_temporary_schema (PGconn *db)
|
|
{
|
|
PGresult *result;
|
|
|
|
SQLEXEC_(db,
|
|
"CREATE SCHEMA IF NOT EXISTS " TALER_TEMP_SCHEMA_NAME ";"
|
|
"SET search_path to " TALER_TEMP_SCHEMA_NAME ";",
|
|
result);
|
|
return GNUNET_OK;
|
|
SQLEXEC_fail:
|
|
return GNUNET_SYSERR;
|
|
}
|
|
|
|
|
|
/**
|
|
* Drop the temporary taler schema. This is only useful for testcases
|
|
*
|
|
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
|
* @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
|
|
*/
|
|
static int
|
|
postgres_drop_temporary (void *cls,
|
|
struct TALER_MINTDB_Session *session)
|
|
{
|
|
PGresult *result;
|
|
|
|
SQLEXEC_ (session->conn,
|
|
"DROP SCHEMA " TALER_TEMP_SCHEMA_NAME " CASCADE;",
|
|
result);
|
|
return GNUNET_OK;
|
|
SQLEXEC_fail:
|
|
return GNUNET_SYSERR;
|
|
}
|
|
|
|
|
|
/**
|
|
* Create the necessary tables if they are not present
|
|
*
|
|
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
|
* @param temporary should we use a temporary schema
|
|
* @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
|
|
*/
|
|
static int
|
|
postgres_create_tables (void *cls,
|
|
int temporary)
|
|
{
|
|
struct PostgresClosure *pc = cls;
|
|
PGresult *result;
|
|
PGconn *conn;
|
|
|
|
result = NULL;
|
|
conn = PQconnectdb (pc->TALER_MINT_db_connection_cfg_str);
|
|
if (CONNECTION_OK != PQstatus (conn))
|
|
{
|
|
LOG_ERROR ("Database connection failed: %s\n",
|
|
PQerrorMessage (conn));
|
|
GNUNET_break (0);
|
|
return GNUNET_SYSERR;
|
|
}
|
|
if ( (GNUNET_YES == temporary) &&
|
|
(GNUNET_SYSERR == set_temporary_schema (conn)))
|
|
{
|
|
PQfinish (conn);
|
|
return GNUNET_SYSERR;
|
|
}
|
|
#define SQLEXEC(sql) SQLEXEC_(conn, sql, result);
|
|
/* reserves table is for summarization of a reserve. It is updated when new
|
|
funds are added and existing funds are withdrawn */
|
|
SQLEXEC ("CREATE TABLE IF NOT EXISTS reserves"
|
|
"("
|
|
" reserve_pub BYTEA PRIMARY KEY"
|
|
",current_balance_value INT4 NOT NULL"
|
|
",current_balance_fraction INT4 NOT NULL"
|
|
",balance_currency VARCHAR(4) NOT NULL"
|
|
",expiration_date INT8 NOT NULL"
|
|
")");
|
|
/* reserves_in table collects the transactions which transfer funds into the
|
|
reserve. The amount and expiration date for the corresponding reserve are
|
|
updated when new transfer funds are added. The rows of this table
|
|
correspond to each incoming transaction. */
|
|
SQLEXEC("CREATE TABLE IF NOT EXISTS reserves_in"
|
|
"("
|
|
" reserve_pub BYTEA REFERENCES reserves (reserve_pub) ON DELETE CASCADE"
|
|
",balance_value INT4 NOT NULL"
|
|
",balance_fraction INT4 NOT NULL"
|
|
",balance_currency VARCHAR(4) NOT NULL"
|
|
",expiration_date INT8 NOT NULL"
|
|
");");
|
|
/* Create an index on the foreign key as it is not created automatically by PSQL */
|
|
SQLEXEC ("CREATE INDEX reserves_in_reserve_pub_index"
|
|
" ON reserves_in (reserve_pub);");
|
|
SQLEXEC ("CREATE TABLE IF NOT EXISTS collectable_blindcoins"
|
|
"("
|
|
"blind_ev BYTEA PRIMARY KEY"
|
|
",denom_pub BYTEA NOT NULL" /* FIXME: Make this a foreign key? */
|
|
",denom_sig BYTEA NOT NULL"
|
|
",reserve_pub BYTEA REFERENCES reserves (reserve_pub) ON DELETE CASCADE"
|
|
",reserve_sig BYTEA NOT NULL"
|
|
");");
|
|
SQLEXEC ("CREATE INDEX collectable_blindcoins_reserve_pub_index ON"
|
|
" collectable_blindcoins (reserve_pub)");
|
|
SQLEXEC("CREATE TABLE IF NOT EXISTS known_coins "
|
|
"("
|
|
" coin_pub BYTEA NOT NULL PRIMARY KEY"
|
|
",denom_pub BYTEA NOT NULL"
|
|
",denom_sig BYTEA NOT NULL"
|
|
",expended_value INT4 NOT NULL"
|
|
",expended_fraction INT4 NOT NULL"
|
|
",expended_currency VARCHAR(4) NOT NULL"
|
|
",refresh_session_pub BYTEA"
|
|
")");
|
|
SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_sessions "
|
|
"("
|
|
" session_pub BYTEA PRIMARY KEY CHECK (length(session_pub) = 32)"
|
|
",session_melt_sig BYTEA"
|
|
",session_commit_sig BYTEA"
|
|
",noreveal_index INT2 NOT NULL"
|
|
// non-zero if all reveals were ok
|
|
// and the new coin signatures are ready
|
|
",reveal_ok BOOLEAN NOT NULL DEFAULT false"
|
|
") ");
|
|
SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_order "
|
|
"( "
|
|
" session_pub BYTEA NOT NULL REFERENCES refresh_sessions (session_pub)"
|
|
",newcoin_index INT2 NOT NULL "
|
|
",denom_pub BYTEA NOT NULL "
|
|
",PRIMARY KEY (session_pub, newcoin_index)"
|
|
") ");
|
|
SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_commit_link"
|
|
"("
|
|
" session_pub BYTEA NOT NULL REFERENCES refresh_sessions (session_pub)"
|
|
",transfer_pub BYTEA NOT NULL"
|
|
",link_secret_enc BYTEA NOT NULL"
|
|
// index of the old coin in the customer's request
|
|
",oldcoin_index INT2 NOT NULL"
|
|
// index for cut and choose,
|
|
// ranges from 0 to kappa-1
|
|
",cnc_index INT2 NOT NULL"
|
|
")");
|
|
SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_commit_coin"
|
|
"("
|
|
" session_pub BYTEA NOT NULL REFERENCES refresh_sessions (session_pub) "
|
|
",link_vector_enc BYTEA NOT NULL"
|
|
// index of the new coin in the customer's request
|
|
",newcoin_index INT2 NOT NULL"
|
|
// index for cut and choose,
|
|
",cnc_index INT2 NOT NULL"
|
|
",coin_ev BYTEA NOT NULL"
|
|
")");
|
|
SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_melt"
|
|
"("
|
|
" session_pub BYTEA NOT NULL REFERENCES refresh_sessions (session_pub) "
|
|
",coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) "
|
|
",denom_pub BYTEA NOT NULL "
|
|
",oldcoin_index INT2 NOT NULL"
|
|
")");
|
|
SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_collectable"
|
|
"("
|
|
" session_pub BYTEA NOT NULL REFERENCES refresh_sessions (session_pub) "
|
|
",ev_sig BYTEA NOT NULL"
|
|
",newcoin_index INT2 NOT NULL"
|
|
")");
|
|
SQLEXEC("CREATE TABLE IF NOT EXISTS deposits "
|
|
"( "
|
|
" coin_pub BYTEA NOT NULL PRIMARY KEY CHECK (length(coin_pub)=32)"
|
|
",denom_pub BYTEA NOT NULL" /* FIXME: Link this as a foreign key? */
|
|
",denom_sig BYTEA NOT NULL"
|
|
",transaction_id INT8 NOT NULL"
|
|
",amount_currency VARCHAR(4) NOT NULL"
|
|
",amount_value INT4 NOT NULL"
|
|
",amount_fraction INT4 NOT NULL"
|
|
",merchant_pub BYTEA NOT NULL CHECK (length(merchant_pub)=32)"
|
|
",h_contract BYTEA NOT NULL CHECK (length(h_contract)=64)"
|
|
",h_wire BYTEA NOT NULL CHECK (length(h_wire)=64)"
|
|
",coin_sig BYTEA NOT NULL CHECK (length(coin_sig)=64)"
|
|
",wire TEXT NOT NULL"
|
|
")");
|
|
#undef SQLEXEC
|
|
|
|
PQfinish (conn);
|
|
return GNUNET_OK;
|
|
|
|
SQLEXEC_fail:
|
|
PQfinish (conn);
|
|
return GNUNET_SYSERR;
|
|
}
|
|
|
|
|
|
/**
|
|
* Setup prepared statements.
|
|
*
|
|
* @param db_conn connection handle to initialize
|
|
* @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
|
|
*/
|
|
static int
|
|
postgres_prepare (PGconn *db_conn)
|
|
{
|
|
PGresult *result;
|
|
|
|
#define PREPARE(name, sql, ...) \
|
|
do { \
|
|
result = PQprepare (db_conn, name, sql, __VA_ARGS__); \
|
|
if (PGRES_COMMAND_OK != PQresultStatus (result)) \
|
|
{ \
|
|
BREAK_DB_ERR (result); \
|
|
PQclear (result); result = NULL; \
|
|
return GNUNET_SYSERR; \
|
|
} \
|
|
PQclear (result); result = NULL; \
|
|
} while (0);
|
|
|
|
PREPARE ("get_reserve",
|
|
"SELECT "
|
|
"current_balance_value"
|
|
",current_balance_fraction"
|
|
",balance_currency "
|
|
",expiration_date "
|
|
"FROM reserves "
|
|
"WHERE reserve_pub=$1 "
|
|
"LIMIT 1; ",
|
|
1, NULL);
|
|
PREPARE ("create_reserve",
|
|
"INSERT INTO reserves ("
|
|
" reserve_pub,"
|
|
" current_balance_value,"
|
|
" current_balance_fraction,"
|
|
" balance_currency,"
|
|
" expiration_date) VALUES ("
|
|
"$1, $2, $3, $4, $5);",
|
|
5, NULL);
|
|
PREPARE ("update_reserve",
|
|
"UPDATE reserves "
|
|
"SET"
|
|
" current_balance_value=$2 "
|
|
",current_balance_fraction=$3 "
|
|
",expiration_date=$4 "
|
|
"WHERE reserve_pub=$1 ",
|
|
4, NULL);
|
|
PREPARE ("create_reserves_in_transaction",
|
|
"INSERT INTO reserves_in ("
|
|
" reserve_pub,"
|
|
" balance_value,"
|
|
" balance_fraction,"
|
|
" balance_currency,"
|
|
" expiration_date) VALUES ("
|
|
" $1, $2, $3, $4, $5);",
|
|
5, NULL);
|
|
PREPARE ("get_reserves_in_transactions",
|
|
"SELECT"
|
|
" balance_value"
|
|
",balance_fraction"
|
|
",balance_currency"
|
|
",expiration_date"
|
|
" FROM reserves_in WHERE reserve_pub=$1",
|
|
1, NULL);
|
|
PREPARE ("insert_collectable_blindcoin",
|
|
"INSERT INTO collectable_blindcoins ( "
|
|
" blind_ev"
|
|
",denom_pub, denom_sig"
|
|
",reserve_pub, reserve_sig) "
|
|
"VALUES ($1, $2, $3, $4, $5)",
|
|
5, NULL);
|
|
PREPARE ("get_collectable_blindcoin",
|
|
"SELECT "
|
|
" denom_pub, denom_sig"
|
|
",reserve_sig, reserve_pub "
|
|
"FROM collectable_blindcoins "
|
|
"WHERE blind_ev = $1",
|
|
1, NULL);
|
|
PREPARE ("get_reserves_blindcoins",
|
|
"select"
|
|
" blind_ev"
|
|
",denom_pub, denom_sig"
|
|
",reserve_sig"
|
|
" FROM collectable_blindcoins"
|
|
" WHERE reserve_pub=$1;",
|
|
1, NULL);
|
|
|
|
/* FIXME: does it make sense to store these computed values in the DB? */
|
|
#if 0
|
|
PREPARE ("get_refresh_session",
|
|
"SELECT "
|
|
" (SELECT count(*) FROM refresh_melt WHERE session_pub = $1)::INT2 as num_oldcoins "
|
|
",(SELECT count(*) FROM refresh_blind_session_keys "
|
|
" WHERE session_pub = $1 and cnc_index = 0)::INT2 as num_newcoins "
|
|
",(SELECT count(*) FROM refresh_blind_session_keys "
|
|
" WHERE session_pub = $1 and newcoin_index = 0)::INT2 as kappa "
|
|
",noreveal_index"
|
|
",session_commit_sig "
|
|
",reveal_ok "
|
|
"FROM refresh_sessions "
|
|
"WHERE session_pub = $1",
|
|
1, NULL);
|
|
#endif
|
|
|
|
PREPARE ("get_known_coin",
|
|
"SELECT "
|
|
" coin_pub, denom_pub, denom_sig "
|
|
",expended_value, expended_fraction, expended_currency "
|
|
",refresh_session_pub "
|
|
"FROM known_coins "
|
|
"WHERE coin_pub = $1",
|
|
1, NULL);
|
|
PREPARE ("update_known_coin",
|
|
"UPDATE known_coins "
|
|
"SET "
|
|
" denom_pub = $2 "
|
|
",denom_sig = $3 "
|
|
",expended_value = $4 "
|
|
",expended_fraction = $5 "
|
|
",expended_currency = $6 "
|
|
",refresh_session_pub = $7 "
|
|
"WHERE "
|
|
" coin_pub = $1 ",
|
|
7, NULL);
|
|
PREPARE ("insert_known_coin",
|
|
"INSERT INTO known_coins ("
|
|
" coin_pub"
|
|
",denom_pub"
|
|
",denom_sig"
|
|
",expended_value"
|
|
",expended_fraction"
|
|
",expended_currency"
|
|
",refresh_session_pub"
|
|
")"
|
|
"VALUES ($1,$2,$3,$4,$5,$6,$7)",
|
|
7, NULL);
|
|
PREPARE ("get_refresh_commit_link",
|
|
"SELECT "
|
|
" transfer_pub "
|
|
",link_secret_enc "
|
|
"FROM refresh_commit_link "
|
|
"WHERE session_pub = $1 AND cnc_index = $2 AND oldcoin_index = $3",
|
|
3, NULL);
|
|
PREPARE ("get_refresh_commit_coin",
|
|
"SELECT "
|
|
" link_vector_enc "
|
|
",coin_ev "
|
|
"FROM refresh_commit_coin "
|
|
"WHERE session_pub = $1 AND cnc_index = $2 AND newcoin_index = $3",
|
|
3, NULL);
|
|
PREPARE ("insert_refresh_order",
|
|
"INSERT INTO refresh_order ( "
|
|
" newcoin_index "
|
|
",session_pub "
|
|
",denom_pub "
|
|
") "
|
|
"VALUES ($1, $2, $3) ",
|
|
3, NULL);
|
|
PREPARE ("insert_refresh_melt",
|
|
"INSERT INTO refresh_melt ( "
|
|
" session_pub "
|
|
",oldcoin_index "
|
|
",coin_pub "
|
|
",denom_pub "
|
|
") "
|
|
"VALUES ($1, $2, $3, $4) ",
|
|
3, NULL);
|
|
PREPARE ("get_refresh_order",
|
|
"SELECT denom_pub "
|
|
"FROM refresh_order "
|
|
"WHERE session_pub = $1 AND newcoin_index = $2",
|
|
2, NULL);
|
|
PREPARE ("get_refresh_collectable",
|
|
"SELECT ev_sig "
|
|
"FROM refresh_collectable "
|
|
"WHERE session_pub = $1 AND newcoin_index = $2",
|
|
2, NULL);
|
|
PREPARE ("get_refresh_melt",
|
|
"SELECT coin_pub "
|
|
"FROM refresh_melt "
|
|
"WHERE session_pub = $1 AND oldcoin_index = $2",
|
|
2, NULL);
|
|
PREPARE ("insert_refresh_session",
|
|
"INSERT INTO refresh_sessions ( "
|
|
" session_pub "
|
|
",noreveal_index "
|
|
") "
|
|
"VALUES ($1, $2) ",
|
|
2, NULL);
|
|
PREPARE ("insert_refresh_commit_link",
|
|
"INSERT INTO refresh_commit_link ( "
|
|
" session_pub "
|
|
",transfer_pub "
|
|
",cnc_index "
|
|
",oldcoin_index "
|
|
",link_secret_enc "
|
|
") "
|
|
"VALUES ($1, $2, $3, $4, $5) ",
|
|
5, NULL);
|
|
PREPARE ("insert_refresh_commit_coin",
|
|
"INSERT INTO refresh_commit_coin ( "
|
|
" session_pub "
|
|
",coin_ev "
|
|
",cnc_index "
|
|
",newcoin_index "
|
|
",link_vector_enc "
|
|
") "
|
|
"VALUES ($1, $2, $3, $4, $5) ",
|
|
5, NULL);
|
|
PREPARE ("insert_refresh_collectable",
|
|
"INSERT INTO refresh_collectable ( "
|
|
" session_pub "
|
|
",newcoin_index "
|
|
",ev_sig "
|
|
") "
|
|
"VALUES ($1, $2, $3) ",
|
|
3, NULL);
|
|
PREPARE ("set_reveal_ok",
|
|
"UPDATE refresh_sessions "
|
|
"SET reveal_ok = TRUE "
|
|
"WHERE session_pub = $1 ",
|
|
1, NULL);
|
|
PREPARE ("get_link",
|
|
"SELECT link_vector_enc, ro.denom_pub, ev_sig "
|
|
"FROM refresh_melt rm "
|
|
" JOIN refresh_order ro USING (session_pub) "
|
|
" JOIN refresh_commit_coin rcc USING (session_pub) "
|
|
" JOIN refresh_sessions rs USING (session_pub) "
|
|
" JOIN refresh_collectable rc USING (session_pub) "
|
|
"WHERE rm.coin_pub = $1 "
|
|
"AND ro.newcoin_index = rcc.newcoin_index "
|
|
"AND ro.newcoin_index = rc.newcoin_index "
|
|
"AND rcc.cnc_index = rs.noreveal_index % ( "
|
|
" SELECT count(*) FROM refresh_commit_coin rcc2 "
|
|
" WHERE rcc2.newcoin_index = 0 AND rcc2.session_pub = rs.session_pub "
|
|
" ) ",
|
|
1, NULL);
|
|
PREPARE ("get_transfer",
|
|
"SELECT transfer_pub, link_secret_enc "
|
|
"FROM refresh_melt rm "
|
|
" JOIN refresh_commit_link rcl USING (session_pub) "
|
|
" JOIN refresh_sessions rs USING (session_pub) "
|
|
"WHERE rm.coin_pub = $1 "
|
|
"AND rm.oldcoin_index = rcl.oldcoin_index "
|
|
"AND rcl.cnc_index = rs.noreveal_index % ( "
|
|
" SELECT count(*) FROM refresh_commit_coin rcc2 "
|
|
" WHERE newcoin_index = 0 AND rcc2.session_pub = rm.session_pub "
|
|
" ) ",
|
|
1, NULL);
|
|
PREPARE ("insert_deposit",
|
|
"INSERT INTO deposits ("
|
|
"coin_pub,"
|
|
"denom_pub,"
|
|
"denom_sig,"
|
|
"transaction_id,"
|
|
"amount_value,"
|
|
"amount_fraction,"
|
|
"amount_currency,"
|
|
"merchant_pub,"
|
|
"h_contract,"
|
|
"h_wire,"
|
|
"coin_sig,"
|
|
"wire"
|
|
") VALUES ("
|
|
"$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12"
|
|
")",
|
|
12, NULL);
|
|
PREPARE ("get_deposit",
|
|
"SELECT "
|
|
"coin_pub,"
|
|
"denom_pub,"
|
|
"transaction_id,"
|
|
"amount_value,"
|
|
"amount_fraction,"
|
|
"amount_currency,"
|
|
"merchant_pub,"
|
|
"h_contract,"
|
|
"h_wire,"
|
|
"coin_sig"
|
|
" FROM deposits WHERE ("
|
|
"(coin_pub = $1) AND"
|
|
"(transaction_id = $2) AND"
|
|
"(merchant_pub = $3)"
|
|
")",
|
|
3, NULL);
|
|
return GNUNET_OK;
|
|
#undef PREPARE
|
|
}
|
|
|
|
|
|
/**
|
|
* Close thread-local database connection when a thread is destroyed.
|
|
*
|
|
* @param closure we get from pthreads (the db handle)
|
|
*/
|
|
static void
|
|
db_conn_destroy (void *cls)
|
|
{
|
|
PGconn *db_conn = cls;
|
|
|
|
if (NULL != db_conn)
|
|
PQfinish (db_conn);
|
|
}
|
|
|
|
|
|
/**
|
|
* Get the thread-local database-handle.
|
|
* Connect to the db if the connection does not exist yet.
|
|
*
|
|
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
|
* @param temporary #GNUNET_YES to use a temporary schema; #GNUNET_NO to use the
|
|
* database default one
|
|
* @return the database connection, or NULL on error
|
|
*/
|
|
static struct TALER_MINTDB_Session *
|
|
postgres_get_session (void *cls,
|
|
int temporary)
|
|
{
|
|
struct PostgresClosure *pc = cls;
|
|
PGconn *db_conn;
|
|
struct TALER_MINTDB_Session *session;
|
|
|
|
if (NULL != (session = pthread_getspecific (pc->db_conn_threadlocal)))
|
|
return session;
|
|
db_conn = PQconnectdb (pc->TALER_MINT_db_connection_cfg_str);
|
|
if (CONNECTION_OK !=
|
|
PQstatus (db_conn))
|
|
{
|
|
LOG_ERROR ("Database connection failed: %s\n",
|
|
PQerrorMessage (db_conn));
|
|
GNUNET_break (0);
|
|
return NULL;
|
|
}
|
|
if ((GNUNET_YES == temporary)
|
|
&& (GNUNET_SYSERR == set_temporary_schema(db_conn)))
|
|
{
|
|
GNUNET_break (0);
|
|
return NULL;
|
|
}
|
|
if (GNUNET_OK !=
|
|
postgres_prepare (db_conn))
|
|
{
|
|
GNUNET_break (0);
|
|
return NULL;
|
|
}
|
|
session = GNUNET_new (struct TALER_MINTDB_Session);
|
|
session->conn = db_conn;
|
|
if (0 != pthread_setspecific (pc->db_conn_threadlocal,
|
|
session))
|
|
{
|
|
GNUNET_break (0);
|
|
// FIXME: close db_conn!
|
|
GNUNET_free (session);
|
|
return NULL;
|
|
}
|
|
return session;
|
|
}
|
|
|
|
|
|
/**
|
|
* Start a transaction.
|
|
*
|
|
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
|
* @param session the database connection
|
|
* @return #GNUNET_OK on success
|
|
*/
|
|
static int
|
|
postgres_start (void *cls,
|
|
struct TALER_MINTDB_Session *session)
|
|
{
|
|
PGresult *result;
|
|
|
|
result = PQexec (session->conn,
|
|
"BEGIN");
|
|
if (PGRES_COMMAND_OK !=
|
|
PQresultStatus (result))
|
|
{
|
|
LOG_ERROR ("Failed to start transaction: %s\n",
|
|
PQresultErrorMessage (result));
|
|
GNUNET_break (0);
|
|
PQclear (result);
|
|
return GNUNET_SYSERR;
|
|
}
|
|
|
|
PQclear (result);
|
|
return GNUNET_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* Roll back the current transaction of a database connection.
|
|
*
|
|
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
|
* @param session the database connection
|
|
* @return #GNUNET_OK on success
|
|
*/
|
|
static void
|
|
postgres_rollback (void *cls,
|
|
struct TALER_MINTDB_Session *session)
|
|
{
|
|
PGresult *result;
|
|
|
|
result = PQexec (session->conn,
|
|
"ROLLBACK");
|
|
GNUNET_break (PGRES_COMMAND_OK ==
|
|
PQresultStatus (result));
|
|
PQclear (result);
|
|
}
|
|
|
|
|
|
/**
|
|
* Commit the current transaction of a database connection.
|
|
*
|
|
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
|
* @param session the database connection
|
|
* @return #GNUNET_OK on success
|
|
*/
|
|
static int
|
|
postgres_commit (void *cls,
|
|
struct TALER_MINTDB_Session *session)
|
|
{
|
|
PGresult *result;
|
|
|
|
result = PQexec (session->conn,
|
|
"COMMIT");
|
|
if (PGRES_COMMAND_OK !=
|
|
PQresultStatus (result))
|
|
{
|
|
GNUNET_break (0);
|
|
PQclear (result);
|
|
return GNUNET_SYSERR;
|
|
}
|
|
|
|
PQclear (result);
|
|
return GNUNET_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* Get the summary of a reserve.
|
|
*
|
|
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
|
* @param session the database connection handle
|
|
* @param reserve the reserve data. The public key of the reserve should be set
|
|
* in this structure; it is used to query the database. The balance
|
|
* and expiration are then filled accordingly.
|
|
* @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
|
|
*/
|
|
static int
|
|
postgres_reserve_get (void *cls,
|
|
struct TALER_MINTDB_Session *session,
|
|
struct Reserve *reserve)
|
|
{
|
|
PGresult *result;
|
|
uint64_t expiration_date_nbo;
|
|
struct TALER_DB_QueryParam params[] = {
|
|
TALER_DB_QUERY_PARAM_PTR(reserve->pub),
|
|
TALER_DB_QUERY_PARAM_END
|
|
};
|
|
|
|
if (NULL == reserve->pub)
|
|
{
|
|
GNUNET_break (0);
|
|
return GNUNET_SYSERR;
|
|
}
|
|
result = TALER_DB_exec_prepared (session->conn,
|
|
"get_reserve",
|
|
params);
|
|
if (PGRES_TUPLES_OK != PQresultStatus (result))
|
|
{
|
|
QUERY_ERR (result);
|
|
PQclear (result);
|
|
return GNUNET_SYSERR;
|
|
}
|
|
if (0 == PQntuples (result))
|
|
{
|
|
PQclear (result);
|
|
return GNUNET_NO;
|
|
}
|
|
struct TALER_DB_ResultSpec rs[] = {
|
|
TALER_DB_RESULT_SPEC("expiration_date", &expiration_date_nbo),
|
|
TALER_DB_RESULT_SPEC_END
|
|
};
|
|
EXITIF (GNUNET_OK != TALER_DB_extract_result (result, rs, 0));
|
|
EXITIF (GNUNET_OK !=
|
|
TALER_DB_extract_amount (result, 0,
|
|
"current_balance_value",
|
|
"current_balance_fraction",
|
|
"balance_currency",
|
|
&reserve->balance));
|
|
reserve->expiry.abs_value_us = GNUNET_ntohll (expiration_date_nbo);
|
|
PQclear (result);
|
|
return GNUNET_OK;
|
|
|
|
EXITIF_exit:
|
|
PQclear (result);
|
|
return GNUNET_SYSERR;
|
|
}
|
|
|
|
|
|
/**
|
|
* Updates a reserve with the data from the given reserve structure.
|
|
*
|
|
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
|
* @param session the database connection
|
|
* @param reserve the reserve structure whose data will be used to update the
|
|
* corresponding record in the database.
|
|
* @return #GNUNET_OK upon successful update; #GNUNET_SYSERR upon any error
|
|
*/
|
|
static int
|
|
postgres_reserves_update (void *cls,
|
|
struct TALER_MINTDB_Session *session,
|
|
struct Reserve *reserve)
|
|
{
|
|
PGresult *result;
|
|
struct TALER_AmountNBO balance_nbo;
|
|
struct GNUNET_TIME_AbsoluteNBO expiry_nbo;
|
|
int ret;
|
|
|
|
if ((NULL == reserve) || (NULL == reserve->pub))
|
|
return GNUNET_SYSERR;
|
|
ret = GNUNET_OK;
|
|
struct TALER_DB_QueryParam params[] = {
|
|
TALER_DB_QUERY_PARAM_PTR (reserve->pub),
|
|
TALER_DB_QUERY_PARAM_PTR (&balance_nbo.value),
|
|
TALER_DB_QUERY_PARAM_PTR (&balance_nbo.fraction),
|
|
TALER_DB_QUERY_PARAM_PTR (&expiry_nbo),
|
|
TALER_DB_QUERY_PARAM_END
|
|
};
|
|
TALER_amount_hton (&balance_nbo,
|
|
&reserve->balance);
|
|
expiry_nbo = GNUNET_TIME_absolute_hton (reserve->expiry);
|
|
result = TALER_DB_exec_prepared (session->conn,
|
|
"update_reserve",
|
|
params);
|
|
if (PGRES_COMMAND_OK != PQresultStatus(result))
|
|
{
|
|
QUERY_ERR (result);
|
|
ret = GNUNET_SYSERR;
|
|
}
|
|
PQclear (result);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* Insert a incoming transaction into reserves. New reserves are also created
|
|
* through this function.
|
|
*
|
|
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
|
* @param session the database connection handle
|
|
* @param reserve the reserve structure. The public key of the reserve should
|
|
* 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 expiry the new expiration time for the reserve
|
|
* @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failures
|
|
*/
|
|
static int
|
|
postgres_reserves_in_insert (void *cls,
|
|
struct TALER_MINTDB_Session *session,
|
|
struct Reserve *reserve,
|
|
const struct TALER_Amount *balance,
|
|
const struct GNUNET_TIME_Absolute expiry)
|
|
{
|
|
struct TALER_AmountNBO balance_nbo;
|
|
struct GNUNET_TIME_AbsoluteNBO expiry_nbo;
|
|
PGresult *result;
|
|
int reserve_exists;
|
|
|
|
result = NULL;
|
|
if (NULL == reserve)
|
|
{
|
|
GNUNET_break (0);
|
|
return GNUNET_SYSERR;
|
|
}
|
|
if (GNUNET_OK != postgres_start (cls,
|
|
session))
|
|
{
|
|
GNUNET_break (0);
|
|
return GNUNET_SYSERR;
|
|
}
|
|
reserve_exists = postgres_reserve_get (cls,
|
|
session,
|
|
reserve);
|
|
if (GNUNET_SYSERR == reserve_exists)
|
|
{
|
|
postgres_rollback (cls,
|
|
session);
|
|
return GNUNET_SYSERR;
|
|
}
|
|
TALER_amount_hton (&balance_nbo,
|
|
balance);
|
|
expiry_nbo = GNUNET_TIME_absolute_hton (expiry);
|
|
if (GNUNET_NO == reserve_exists)
|
|
{
|
|
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
|
|
"Reserve does not exist; creating a new one\n");
|
|
struct TALER_DB_QueryParam params[] = {
|
|
TALER_DB_QUERY_PARAM_PTR (reserve->pub),
|
|
TALER_DB_QUERY_PARAM_PTR (&balance_nbo.value),
|
|
TALER_DB_QUERY_PARAM_PTR (&balance_nbo.fraction),
|
|
TALER_DB_QUERY_PARAM_PTR_SIZED (balance_nbo.currency,
|
|
TALER_DB_CURRENCY_LEN),
|
|
TALER_DB_QUERY_PARAM_PTR (&expiry_nbo),
|
|
TALER_DB_QUERY_PARAM_END
|
|
};
|
|
result = TALER_DB_exec_prepared (session->conn,
|
|
"create_reserve",
|
|
params);
|
|
if (PGRES_COMMAND_OK != PQresultStatus(result))
|
|
{
|
|
QUERY_ERR (result);
|
|
goto rollback;
|
|
}
|
|
}
|
|
if (NULL != result)
|
|
PQclear (result);
|
|
result = NULL;
|
|
/* create new incoming transaction */
|
|
struct TALER_DB_QueryParam params[] = {
|
|
TALER_DB_QUERY_PARAM_PTR (reserve->pub),
|
|
TALER_DB_QUERY_PARAM_PTR (&balance_nbo.value),
|
|
TALER_DB_QUERY_PARAM_PTR (&balance_nbo.fraction),
|
|
TALER_DB_QUERY_PARAM_PTR_SIZED (&balance_nbo.currency,
|
|
TALER_DB_CURRENCY_LEN),
|
|
TALER_DB_QUERY_PARAM_PTR (&expiry_nbo),
|
|
TALER_DB_QUERY_PARAM_END
|
|
};
|
|
result = TALER_DB_exec_prepared (session->conn,
|
|
"create_reserves_in_transaction",
|
|
params);
|
|
if (PGRES_COMMAND_OK != PQresultStatus(result))
|
|
{
|
|
QUERY_ERR (result);
|
|
goto rollback;
|
|
}
|
|
PQclear (result);
|
|
result = NULL;
|
|
if (GNUNET_NO == reserve_exists)
|
|
{
|
|
if (GNUNET_OK != postgres_commit (cls,
|
|
session))
|
|
return GNUNET_SYSERR;
|
|
reserve->balance = *balance;
|
|
reserve->expiry = expiry;
|
|
return GNUNET_OK;
|
|
}
|
|
/* Update reserve */
|
|
struct 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,
|
|
&updated_reserve))
|
|
goto rollback;
|
|
if (GNUNET_OK != postgres_commit (cls,
|
|
session))
|
|
return GNUNET_SYSERR;
|
|
reserve->balance = updated_reserve.balance;
|
|
reserve->expiry = updated_reserve.expiry;
|
|
return GNUNET_OK;
|
|
|
|
rollback:
|
|
PQclear (result);
|
|
postgres_rollback (cls,
|
|
session);
|
|
return GNUNET_SYSERR;
|
|
}
|
|
|
|
|
|
/**
|
|
* Locate the response for a /withdraw request under the
|
|
* key of the hash of the blinded message.
|
|
*
|
|
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
|
* @param session database connection to use
|
|
* @param h_blind hash of the blinded message
|
|
* @param collectable corresponding collectable coin (blind signature)
|
|
* if a coin is found
|
|
* @return #GNUNET_SYSERR on internal error
|
|
* #GNUNET_NO if the collectable was not found
|
|
* #GNUNET_YES on success
|
|
*/
|
|
static int
|
|
postgres_get_collectable_blindcoin (void *cls,
|
|
struct TALER_MINTDB_Session *session,
|
|
const struct GNUNET_HashCode *h_blind,
|
|
struct CollectableBlindcoin *collectable)
|
|
{
|
|
PGresult *result;
|
|
struct TALER_DB_QueryParam params[] = {
|
|
TALER_DB_QUERY_PARAM_PTR (h_blind),
|
|
TALER_DB_QUERY_PARAM_END
|
|
};
|
|
struct GNUNET_CRYPTO_rsa_PublicKey *denom_pub;
|
|
struct GNUNET_CRYPTO_rsa_Signature *denom_sig;
|
|
char *denom_pub_enc;
|
|
char *denom_sig_enc;
|
|
size_t denom_pub_enc_size;
|
|
size_t denom_sig_enc_size;
|
|
int ret;
|
|
|
|
ret = GNUNET_SYSERR;
|
|
denom_pub = NULL;
|
|
denom_pub_enc = NULL;
|
|
denom_sig_enc = NULL;
|
|
result = TALER_DB_exec_prepared (session->conn,
|
|
"get_collectable_blindcoin",
|
|
params);
|
|
|
|
if (PGRES_TUPLES_OK != PQresultStatus (result))
|
|
{
|
|
QUERY_ERR (result);
|
|
goto cleanup;
|
|
}
|
|
if (0 == PQntuples (result))
|
|
{
|
|
ret = GNUNET_NO;
|
|
goto cleanup;
|
|
}
|
|
struct TALER_DB_ResultSpec rs[] = {
|
|
TALER_DB_RESULT_SPEC_VAR("denom_pub", &denom_pub_enc, &denom_pub_enc_size),
|
|
TALER_DB_RESULT_SPEC_VAR("denom_sig", &denom_sig_enc, &denom_sig_enc_size),
|
|
TALER_DB_RESULT_SPEC("reserve_sig", &collectable->reserve_sig),
|
|
TALER_DB_RESULT_SPEC("reserve_pub", &collectable->reserve_pub),
|
|
TALER_DB_RESULT_SPEC_END
|
|
};
|
|
|
|
if (GNUNET_OK != TALER_DB_extract_result (result, rs, 0))
|
|
{
|
|
GNUNET_break (0);
|
|
goto cleanup;
|
|
}
|
|
denom_pub = GNUNET_CRYPTO_rsa_public_key_decode (denom_pub_enc,
|
|
denom_pub_enc_size);
|
|
denom_sig = GNUNET_CRYPTO_rsa_signature_decode (denom_sig_enc,
|
|
denom_sig_enc_size);
|
|
if ((NULL == denom_pub) || (NULL == denom_sig))
|
|
{
|
|
GNUNET_break (0);
|
|
goto cleanup;
|
|
}
|
|
collectable->denom_pub = denom_pub;
|
|
collectable->sig = denom_sig;
|
|
ret = GNUNET_YES;
|
|
|
|
cleanup:
|
|
PQclear (result);
|
|
GNUNET_free_non_null (denom_pub_enc);
|
|
GNUNET_free_non_null (denom_sig_enc);
|
|
if (GNUNET_YES != ret)
|
|
{ if (NULL != denom_pub)
|
|
GNUNET_CRYPTO_rsa_public_key_free (denom_pub);
|
|
if (NULL != denom_sig)
|
|
GNUNET_CRYPTO_rsa_signature_free (denom_sig);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* Store collectable bit coin under the corresponding
|
|
* hash of the blinded message.
|
|
*
|
|
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
|
* @param session database connection to use
|
|
* @param h_blind hash of the blinded message
|
|
* @param withdraw amount by which the reserve will be withdrawn with this
|
|
* transaction
|
|
* @param collectable corresponding collectable coin (blind signature)
|
|
* if a coin is found
|
|
* @return #GNUNET_SYSERR on internal error
|
|
* #GNUNET_NO if the collectable was not found
|
|
* #GNUNET_YES on success
|
|
*/
|
|
static int
|
|
postgres_insert_collectable_blindcoin (void *cls,
|
|
struct TALER_MINTDB_Session *session,
|
|
const struct GNUNET_HashCode *h_blind,
|
|
struct TALER_Amount withdraw,
|
|
const struct CollectableBlindcoin *collectable)
|
|
{
|
|
PGresult *result;
|
|
struct Reserve reserve;
|
|
char *denom_pub_enc = NULL;
|
|
char *denom_sig_enc = NULL;
|
|
size_t denom_pub_enc_size;
|
|
size_t denom_sig_enc_size;
|
|
int ret;
|
|
|
|
ret = GNUNET_SYSERR;
|
|
denom_pub_enc_size =
|
|
GNUNET_CRYPTO_rsa_public_key_encode (collectable->denom_pub,
|
|
&denom_pub_enc);
|
|
denom_sig_enc_size =
|
|
GNUNET_CRYPTO_rsa_signature_encode (collectable->sig,
|
|
&denom_sig_enc);
|
|
struct TALER_DB_QueryParam params[] = {
|
|
TALER_DB_QUERY_PARAM_PTR (h_blind),
|
|
TALER_DB_QUERY_PARAM_PTR_SIZED (denom_pub_enc, denom_pub_enc_size - 1),
|
|
TALER_DB_QUERY_PARAM_PTR_SIZED (denom_sig_enc, denom_sig_enc_size - 1), /* DB doesn't like the trailing \0 */
|
|
TALER_DB_QUERY_PARAM_PTR (&collectable->reserve_pub),
|
|
TALER_DB_QUERY_PARAM_PTR (&collectable->reserve_sig),
|
|
TALER_DB_QUERY_PARAM_END
|
|
};
|
|
if (GNUNET_OK != postgres_start (cls,
|
|
session))
|
|
goto cleanup;
|
|
result = TALER_DB_exec_prepared (session->conn,
|
|
"insert_collectable_blindcoin",
|
|
params);
|
|
if (PGRES_COMMAND_OK != PQresultStatus (result))
|
|
{
|
|
QUERY_ERR (result);
|
|
goto rollback;
|
|
}
|
|
reserve.pub = (struct GNUNET_CRYPTO_EddsaPublicKey *)
|
|
&collectable->reserve_pub;
|
|
if (GNUNET_OK != postgres_reserve_get (cls,
|
|
session,
|
|
&reserve))
|
|
goto rollback;
|
|
if (GNUNET_SYSERR ==
|
|
TALER_amount_subtract (&reserve.balance,
|
|
&reserve.balance,
|
|
&withdraw))
|
|
goto rollback;
|
|
if (GNUNET_OK != postgres_reserves_update (cls,
|
|
session,
|
|
&reserve))
|
|
goto rollback;
|
|
if (GNUNET_OK == postgres_commit (cls,
|
|
session))
|
|
{
|
|
ret = GNUNET_OK;
|
|
goto cleanup;
|
|
}
|
|
|
|
rollback:
|
|
postgres_rollback (cls,
|
|
session);
|
|
cleanup:
|
|
PQclear (result);
|
|
GNUNET_free_non_null (denom_pub_enc);
|
|
GNUNET_free_non_null (denom_sig_enc);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* Get all of the transaction history associated with the specified
|
|
* reserve.
|
|
*
|
|
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
|
* @param session connection to use
|
|
* @param reserve_pub public key of the reserve
|
|
* @return known transaction history (NULL if reserve is unknown)
|
|
*/
|
|
static struct ReserveHistory *
|
|
postgres_get_reserve_history (void *cls,
|
|
struct TALER_MINTDB_Session *session,
|
|
const struct GNUNET_CRYPTO_EddsaPublicKey *reserve_pub)
|
|
{
|
|
PGresult *result;
|
|
struct ReserveHistory *rh;
|
|
struct ReserveHistory *rh_head;
|
|
int rows;
|
|
int ret;
|
|
|
|
result = NULL;
|
|
rh = NULL;
|
|
rh_head = NULL;
|
|
ret = GNUNET_SYSERR;
|
|
{
|
|
struct BankTransfer *bt;
|
|
struct TALER_DB_QueryParam params[] = {
|
|
TALER_DB_QUERY_PARAM_PTR (reserve_pub),
|
|
TALER_DB_QUERY_PARAM_END
|
|
};
|
|
|
|
result = TALER_DB_exec_prepared (session->conn,
|
|
"get_reserves_in_transactions",
|
|
params);
|
|
if (PGRES_TUPLES_OK != PQresultStatus (result))
|
|
{
|
|
QUERY_ERR (result);
|
|
goto cleanup;
|
|
}
|
|
if (0 == (rows = PQntuples (result)))
|
|
{
|
|
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
|
|
"Asked to fetch history for an unknown reserve.\n");
|
|
goto cleanup;
|
|
}
|
|
while (0 < rows)
|
|
{
|
|
bt = GNUNET_new (struct BankTransfer);
|
|
if (GNUNET_OK != TALER_DB_extract_amount (result,
|
|
--rows,
|
|
"balance_value",
|
|
"balance_fraction",
|
|
"balance_currency",
|
|
&bt->amount))
|
|
{
|
|
GNUNET_free (bt);
|
|
GNUNET_break (0);
|
|
goto cleanup;
|
|
}
|
|
(void) memcpy (&bt->reserve_pub, reserve_pub, sizeof (bt->reserve_pub));
|
|
if (NULL != rh_head)
|
|
{
|
|
rh_head->next = GNUNET_new (struct ReserveHistory);
|
|
rh_head = rh_head->next;
|
|
}
|
|
else
|
|
{
|
|
rh_head = GNUNET_new (struct ReserveHistory);
|
|
rh = rh_head;
|
|
}
|
|
rh_head->type = TALER_MINT_DB_RO_BANK_TO_MINT;
|
|
rh_head->details.bank = bt;
|
|
}
|
|
}
|
|
PQclear (result);
|
|
result = NULL;
|
|
{
|
|
struct GNUNET_HashCode blind_ev;
|
|
struct GNUNET_CRYPTO_EddsaSignature reserve_sig;
|
|
struct CollectableBlindcoin *cbc;
|
|
char *denom_pub_enc;
|
|
char *denom_sig_enc;
|
|
size_t denom_pub_enc_size;
|
|
size_t denom_sig_enc_size;
|
|
|
|
struct TALER_DB_QueryParam params[] = {
|
|
TALER_DB_QUERY_PARAM_PTR (reserve_pub),
|
|
TALER_DB_QUERY_PARAM_END
|
|
};
|
|
result = TALER_DB_exec_prepared (session->conn,
|
|
"get_reserves_blindcoins",
|
|
params);
|
|
if (PGRES_TUPLES_OK != PQresultStatus (result))
|
|
{
|
|
QUERY_ERR (result);
|
|
goto cleanup;
|
|
}
|
|
if (0 == (rows = PQntuples (result)))
|
|
{
|
|
ret = GNUNET_OK; /* Its OK if there are no withdrawls yet */
|
|
goto cleanup;
|
|
}
|
|
struct TALER_DB_ResultSpec rs[] = {
|
|
TALER_DB_RESULT_SPEC ("blind_ev", &blind_ev),
|
|
TALER_DB_RESULT_SPEC_VAR ("denom_pub", &denom_pub_enc, &denom_pub_enc_size),
|
|
TALER_DB_RESULT_SPEC_VAR ("denom_sig", &denom_sig_enc, &denom_sig_enc_size),
|
|
TALER_DB_RESULT_SPEC ("reserve_sig", &reserve_sig),
|
|
TALER_DB_RESULT_SPEC_END
|
|
};
|
|
GNUNET_assert (NULL != rh);
|
|
GNUNET_assert (NULL != rh_head);
|
|
GNUNET_assert (NULL == rh_head->next);
|
|
while (0 < rows)
|
|
{
|
|
if (GNUNET_YES != TALER_DB_extract_result (result, rs, --rows))
|
|
{
|
|
GNUNET_break (0);
|
|
goto cleanup;
|
|
}
|
|
cbc = GNUNET_new (struct CollectableBlindcoin);
|
|
cbc->sig = GNUNET_CRYPTO_rsa_signature_decode (denom_sig_enc,
|
|
denom_sig_enc_size);
|
|
GNUNET_free (denom_sig_enc);
|
|
denom_sig_enc = NULL;
|
|
cbc->denom_pub = GNUNET_CRYPTO_rsa_public_key_decode (denom_pub_enc,
|
|
denom_pub_enc_size);
|
|
GNUNET_free (denom_pub_enc);
|
|
denom_pub_enc = NULL;
|
|
if ((NULL == cbc->sig) || (NULL == cbc->denom_pub))
|
|
{
|
|
if (NULL != cbc->sig)
|
|
GNUNET_CRYPTO_rsa_signature_free (cbc->sig);
|
|
if (NULL != cbc->denom_pub)
|
|
GNUNET_CRYPTO_rsa_public_key_free (cbc->denom_pub);
|
|
GNUNET_free (cbc);
|
|
GNUNET_break (0);
|
|
goto cleanup;
|
|
}
|
|
(void) memcpy (&cbc->h_coin_envelope, &blind_ev, sizeof (blind_ev));
|
|
(void) memcpy (&cbc->reserve_pub, reserve_pub, sizeof (cbc->reserve_pub));
|
|
(void) memcpy (&cbc->reserve_sig, &reserve_sig, sizeof (cbc->reserve_sig));
|
|
rh_head->next = GNUNET_new (struct ReserveHistory);
|
|
rh_head = rh_head->next;
|
|
rh_head->type = TALER_MINT_DB_RO_WITHDRAW_COIN;
|
|
rh_head->details.withdraw = cbc;
|
|
}
|
|
}
|
|
ret = GNUNET_OK;
|
|
|
|
cleanup:
|
|
if (NULL != result)
|
|
PQclear (result);
|
|
if (GNUNET_SYSERR == ret)
|
|
{
|
|
common_free_reserve_history (cls,
|
|
rh);
|
|
rh = NULL;
|
|
}
|
|
return rh;
|
|
}
|
|
|
|
|
|
/**
|
|
* Check if we have the specified deposit already in the database.
|
|
*
|
|
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
|
* @param session database connection
|
|
* @param deposit deposit to search for
|
|
* @return #GNUNET_YES if we know this operation,
|
|
* #GNUNET_NO if this deposit is unknown to us
|
|
*/
|
|
static int
|
|
postgres_have_deposit (void *cls,
|
|
struct TALER_MINTDB_Session *session,
|
|
const struct Deposit *deposit)
|
|
{
|
|
struct TALER_DB_QueryParam params[] = {
|
|
TALER_DB_QUERY_PARAM_PTR (&deposit->coin.coin_pub),
|
|
TALER_DB_QUERY_PARAM_PTR (&deposit->transaction_id),
|
|
TALER_DB_QUERY_PARAM_PTR (&deposit->merchant_pub),
|
|
TALER_DB_QUERY_PARAM_END
|
|
};
|
|
PGresult *result;
|
|
int ret;
|
|
|
|
ret = GNUNET_SYSERR;
|
|
result = TALER_DB_exec_prepared (session->conn,
|
|
"get_deposit",
|
|
params);
|
|
if (PGRES_TUPLES_OK !=
|
|
PQresultStatus (result))
|
|
{
|
|
BREAK_DB_ERR (result);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (0 == PQntuples (result))
|
|
{
|
|
ret = GNUNET_NO;
|
|
goto cleanup;
|
|
}
|
|
ret = GNUNET_YES;
|
|
|
|
cleanup:
|
|
PQclear (result);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* Insert information about deposited coin into the
|
|
* database.
|
|
*
|
|
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
|
* @param session connection to the database
|
|
* @param deposit deposit information to store
|
|
* @return #GNUNET_OK on success, #GNUNET_SYSERR on error
|
|
*/
|
|
static int
|
|
postgres_insert_deposit (void *cls,
|
|
struct TALER_MINTDB_Session *session,
|
|
const struct Deposit *deposit)
|
|
{
|
|
char *denom_pub_enc;
|
|
char *denom_sig_enc;
|
|
char *json_wire_enc;
|
|
PGresult *result;
|
|
struct TALER_AmountNBO amount_nbo;
|
|
size_t denom_pub_enc_size;
|
|
size_t denom_sig_enc_size;
|
|
int ret;
|
|
|
|
ret = GNUNET_SYSERR;
|
|
denom_pub_enc_size =
|
|
GNUNET_CRYPTO_rsa_public_key_encode (deposit->coin.denom_pub,
|
|
&denom_pub_enc);
|
|
denom_sig_enc_size =
|
|
GNUNET_CRYPTO_rsa_signature_encode (deposit->coin.denom_sig,
|
|
&denom_sig_enc);
|
|
json_wire_enc = json_dumps (deposit->wire, JSON_COMPACT);
|
|
TALER_amount_hton (&amount_nbo,
|
|
&deposit->amount);
|
|
struct TALER_DB_QueryParam params[]= {
|
|
TALER_DB_QUERY_PARAM_PTR (&deposit->coin.coin_pub),
|
|
TALER_DB_QUERY_PARAM_PTR_SIZED (denom_pub_enc, denom_pub_enc_size),
|
|
TALER_DB_QUERY_PARAM_PTR_SIZED (denom_sig_enc, denom_sig_enc_size),
|
|
TALER_DB_QUERY_PARAM_PTR (&deposit->transaction_id),
|
|
TALER_DB_QUERY_PARAM_PTR (&amount_nbo.value),
|
|
TALER_DB_QUERY_PARAM_PTR (&amount_nbo.fraction),
|
|
TALER_DB_QUERY_PARAM_PTR_SIZED (amount_nbo.currency,
|
|
TALER_CURRENCY_LEN - 1),
|
|
TALER_DB_QUERY_PARAM_PTR (&deposit->merchant_pub),
|
|
TALER_DB_QUERY_PARAM_PTR (&deposit->h_contract),
|
|
TALER_DB_QUERY_PARAM_PTR (&deposit->h_wire),
|
|
TALER_DB_QUERY_PARAM_PTR (&deposit->csig),
|
|
TALER_DB_QUERY_PARAM_PTR_SIZED (json_wire_enc,
|
|
strlen (json_wire_enc)),
|
|
TALER_DB_QUERY_PARAM_END
|
|
};
|
|
result = TALER_DB_exec_prepared (session->conn, "insert_deposit", params);
|
|
if (PGRES_COMMAND_OK != PQresultStatus (result))
|
|
{
|
|
BREAK_DB_ERR (result);
|
|
goto cleanup;
|
|
}
|
|
ret = GNUNET_OK;
|
|
|
|
cleanup:
|
|
PQclear (result);
|
|
GNUNET_free_non_null (denom_pub_enc);
|
|
GNUNET_free_non_null (denom_sig_enc);
|
|
GNUNET_free_non_null (json_wire_enc);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* Lookup refresh session data under the given public key.
|
|
*
|
|
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
|
* @param session database handle to use
|
|
* @param refresh_session_pub public key to use for the lookup
|
|
* @param refresh_session[OUT] where to store the result
|
|
* @return #GNUNET_YES on success,
|
|
* #GNUNET_NO if not found,
|
|
* #GNUNET_SYSERR on DB failure
|
|
*/
|
|
static int
|
|
postgres_get_refresh_session (void *cls,
|
|
struct TALER_MINTDB_Session *session,
|
|
const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub,
|
|
struct RefreshSession *refresh_session)
|
|
{
|
|
// FIXME: check logic!
|
|
int res;
|
|
struct TALER_DB_QueryParam params[] = {
|
|
TALER_DB_QUERY_PARAM_PTR(refresh_session_pub),
|
|
TALER_DB_QUERY_PARAM_END
|
|
};
|
|
|
|
PGresult *result = TALER_DB_exec_prepared (session->conn,
|
|
"get_refresh_session",
|
|
params);
|
|
|
|
if (PGRES_TUPLES_OK != PQresultStatus (result))
|
|
{
|
|
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
|
"Query failed: %s\n",
|
|
PQresultErrorMessage (result));
|
|
PQclear (result);
|
|
return GNUNET_SYSERR;
|
|
}
|
|
|
|
if (0 == PQntuples (result))
|
|
return GNUNET_NO;
|
|
|
|
GNUNET_assert (1 == PQntuples (result));
|
|
|
|
/* We're done if the caller is only interested in
|
|
* whether the session exists or not */
|
|
|
|
if (NULL == refresh_session)
|
|
return GNUNET_YES;
|
|
|
|
memset (session, 0, sizeof (struct RefreshSession));
|
|
|
|
struct TALER_DB_ResultSpec rs[] = {
|
|
TALER_DB_RESULT_SPEC("num_oldcoins", &refresh_session->num_oldcoins),
|
|
TALER_DB_RESULT_SPEC("num_newcoins", &refresh_session->num_newcoins),
|
|
TALER_DB_RESULT_SPEC("kappa", &refresh_session->kappa),
|
|
TALER_DB_RESULT_SPEC("noreveal_index", &refresh_session->noreveal_index),
|
|
TALER_DB_RESULT_SPEC_END
|
|
};
|
|
|
|
res = TALER_DB_extract_result (result, rs, 0);
|
|
|
|
if (GNUNET_OK != res)
|
|
{
|
|
GNUNET_break (0);
|
|
PQclear (result);
|
|
return GNUNET_SYSERR;
|
|
}
|
|
|
|
refresh_session->num_oldcoins = ntohs (refresh_session->num_oldcoins);
|
|
refresh_session->num_newcoins = ntohs (refresh_session->num_newcoins);
|
|
refresh_session->kappa = ntohs (refresh_session->kappa);
|
|
refresh_session->noreveal_index = ntohs (refresh_session->noreveal_index);
|
|
|
|
PQclear (result);
|
|
return GNUNET_YES;
|
|
}
|
|
|
|
|
|
/**
|
|
* Store new refresh session data under the given public key.
|
|
*
|
|
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
|
* @param session database handle to use
|
|
* @param refresh_session_pub public key to use to locate the session
|
|
* @param refresh_session session data to store
|
|
* @return #GNUNET_YES on success,
|
|
* #GNUNET_SYSERR on DB failure
|
|
*/
|
|
static int
|
|
postgres_create_refresh_session (void *cls,
|
|
struct TALER_MINTDB_Session *session,
|
|
const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub,
|
|
const struct RefreshSession *refresh_session)
|
|
{
|
|
// FIXME: actually store session data!
|
|
uint16_t noreveal_index;
|
|
struct TALER_DB_QueryParam params[] = {
|
|
TALER_DB_QUERY_PARAM_PTR(session_pub),
|
|
TALER_DB_QUERY_PARAM_PTR(&noreveal_index),
|
|
TALER_DB_QUERY_PARAM_END
|
|
};
|
|
|
|
noreveal_index = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, 1<<15);
|
|
noreveal_index = htonl (noreveal_index);
|
|
|
|
PGresult *result = TALER_DB_exec_prepared (session->conn,
|
|
"insert_refresh_session",
|
|
params);
|
|
|
|
if (PGRES_COMMAND_OK != PQresultStatus (result))
|
|
{
|
|
BREAK_DB_ERR (result);
|
|
PQclear (result);
|
|
return GNUNET_SYSERR;
|
|
}
|
|
|
|
PQclear (result);
|
|
return GNUNET_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* Store the given /refresh/melt request in the database.
|
|
*
|
|
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
|
* @param session database connection
|
|
* @param refresh_session session key of the melt operation
|
|
* @param oldcoin_index index of the coin to store
|
|
* @param melt melt operation
|
|
* @return #GNUNET_OK on success
|
|
* #GNUNET_SYSERR on internal error
|
|
*/
|
|
static int
|
|
postgres_insert_refresh_melt (void *cls,
|
|
struct TALER_MINTDB_Session *session,
|
|
const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session,
|
|
uint16_t oldcoin_index,
|
|
const struct RefreshMelt *melt)
|
|
{
|
|
// FIXME: check logic!
|
|
uint16_t oldcoin_index_nbo = htons (oldcoin_index);
|
|
char *buf;
|
|
size_t buf_size;
|
|
PGresult *result;
|
|
|
|
buf_size = GNUNET_CRYPTO_rsa_public_key_encode (melt->coin.denom_pub,
|
|
&buf);
|
|
{
|
|
struct TALER_DB_QueryParam params[] = {
|
|
TALER_DB_QUERY_PARAM_PTR(refresh_session),
|
|
TALER_DB_QUERY_PARAM_PTR(&oldcoin_index_nbo),
|
|
TALER_DB_QUERY_PARAM_PTR(&melt->coin.coin_pub),
|
|
TALER_DB_QUERY_PARAM_PTR_SIZED(buf, buf_size),
|
|
TALER_DB_QUERY_PARAM_END
|
|
};
|
|
result = TALER_DB_exec_prepared (session->conn,
|
|
"insert_refresh_melt",
|
|
params);
|
|
}
|
|
GNUNET_free (buf);
|
|
if (PGRES_COMMAND_OK != PQresultStatus (result))
|
|
{
|
|
BREAK_DB_ERR (result);
|
|
PQclear (result);
|
|
return GNUNET_SYSERR;
|
|
}
|
|
PQclear (result);
|
|
return GNUNET_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* Get information about melted coin details from the database.
|
|
*
|
|
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
|
* @param session database connection
|
|
* @param refresh_session session key of the melt operation
|
|
* @param oldcoin_index index of the coin to retrieve
|
|
* @param melt melt data to fill in
|
|
* @return #GNUNET_OK on success
|
|
* #GNUNET_SYSERR on internal error
|
|
*/
|
|
static int
|
|
postgres_get_refresh_melt (void *cls,
|
|
struct TALER_MINTDB_Session *session,
|
|
const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session,
|
|
uint16_t oldcoin_index,
|
|
struct RefreshMelt *melt)
|
|
{
|
|
// FIXME: check logic!
|
|
GNUNET_break (0);
|
|
return GNUNET_SYSERR;
|
|
}
|
|
|
|
|
|
/**
|
|
* Store in the database which coin(s) we want to create
|
|
* in a given refresh operation.
|
|
*
|
|
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
|
* @param session database connection
|
|
* @param session_pub refresh session key
|
|
* @param num_newcoins number of coins to generate, size of the @a denom_pubs array
|
|
* @param denom_pubs array denominations of the coins to create
|
|
* @return #GNUNET_OK on success
|
|
* #GNUNET_SYSERR on internal error
|
|
*/
|
|
static int
|
|
postgres_insert_refresh_order (void *cls,
|
|
struct TALER_MINTDB_Session *session,
|
|
const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub,
|
|
uint16_t num_newcoins,
|
|
struct GNUNET_CRYPTO_rsa_PublicKey *const*denom_pubs)
|
|
{
|
|
// FIXME: check logic: was written for just one COIN!
|
|
uint16_t newcoin_index_nbo = htons (num_newcoins);
|
|
char *buf;
|
|
size_t buf_size;
|
|
PGresult *result;
|
|
|
|
buf_size = GNUNET_CRYPTO_rsa_public_key_encode (*denom_pubs,
|
|
&buf);
|
|
|
|
{
|
|
struct TALER_DB_QueryParam params[] = {
|
|
TALER_DB_QUERY_PARAM_PTR (&newcoin_index_nbo),
|
|
TALER_DB_QUERY_PARAM_PTR (session_pub),
|
|
TALER_DB_QUERY_PARAM_PTR_SIZED (buf, buf_size),
|
|
TALER_DB_QUERY_PARAM_END
|
|
};
|
|
result = TALER_DB_exec_prepared (session->conn,
|
|
"insert_refresh_order",
|
|
params);
|
|
}
|
|
GNUNET_free (buf);
|
|
if (PGRES_COMMAND_OK != PQresultStatus (result))
|
|
{
|
|
BREAK_DB_ERR (result);
|
|
PQclear (result);
|
|
return GNUNET_SYSERR;
|
|
}
|
|
if (0 != strcmp ("1", PQcmdTuples (result)))
|
|
{
|
|
GNUNET_break (0);
|
|
return GNUNET_SYSERR;
|
|
}
|
|
PQclear (result);
|
|
return GNUNET_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* Lookup in the database the coins that we want to
|
|
* create in the given refresh operation.
|
|
*
|
|
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
|
* @param session database connection
|
|
* @param session_pub refresh session key
|
|
* @param newcoin_index array of the @a denom_pubs array
|
|
* @param denom_pubs where to store the deomination keys
|
|
* @return #GNUNET_OK on success
|
|
* #GNUNET_SYSERR on internal error
|
|
*/
|
|
static int
|
|
postgres_get_refresh_order (void *cls,
|
|
struct TALER_MINTDB_Session *session,
|
|
const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub,
|
|
uint16_t num_newcoins,
|
|
struct GNUNET_CRYPTO_rsa_PublicKey **denom_pubs)
|
|
{
|
|
// FIXME: check logic -- was written for just one coin!
|
|
char *buf;
|
|
size_t buf_size;
|
|
uint16_t newcoin_index_nbo = htons (num_newcoins);
|
|
|
|
struct TALER_DB_QueryParam params[] = {
|
|
TALER_DB_QUERY_PARAM_PTR(session_pub),
|
|
TALER_DB_QUERY_PARAM_PTR(&newcoin_index_nbo),
|
|
TALER_DB_QUERY_PARAM_END
|
|
};
|
|
|
|
PGresult *result = TALER_DB_exec_prepared (session->conn,
|
|
"get_refresh_order", params);
|
|
|
|
if (PGRES_TUPLES_OK != PQresultStatus (result))
|
|
{
|
|
BREAK_DB_ERR (result);
|
|
PQclear (result);
|
|
return GNUNET_SYSERR;
|
|
}
|
|
|
|
if (0 == PQntuples (result))
|
|
{
|
|
PQclear (result);
|
|
/* FIXME: may want to distinguish between different error cases! */
|
|
return GNUNET_SYSERR;
|
|
}
|
|
GNUNET_assert (1 == PQntuples (result));
|
|
struct TALER_DB_ResultSpec rs[] = {
|
|
TALER_DB_RESULT_SPEC_VAR ("denom_pub", &buf, &buf_size),
|
|
TALER_DB_RESULT_SPEC_END
|
|
};
|
|
if (GNUNET_OK != TALER_DB_extract_result (result, rs, 0))
|
|
{
|
|
PQclear (result);
|
|
GNUNET_break (0);
|
|
return GNUNET_SYSERR;
|
|
}
|
|
PQclear (result);
|
|
denom_pubs[0] = GNUNET_CRYPTO_rsa_public_key_decode (buf, buf_size);
|
|
GNUNET_free (buf);
|
|
return GNUNET_OK;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Store information about the commitment of the
|
|
* given coin for the given refresh session in the database.
|
|
*
|
|
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
|
* @param session database connection to use
|
|
* @param refresh_session_pub refresh session this commitment belongs to
|
|
* @param i set index (1st dimension)
|
|
* @param num_newcoins coin index size of the @a commit_coins array
|
|
* @param commit_coins array of coin commitments to store
|
|
* @return #GNUNET_OK on success
|
|
* #GNUNET_SYSERR on error
|
|
*/
|
|
static int
|
|
postgres_insert_refresh_commit_coins (void *cls,
|
|
struct TALER_MINTDB_Session *session,
|
|
const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub,
|
|
unsigned int i,
|
|
unsigned int num_newcoins,
|
|
const struct RefreshCommitCoin *commit_coins)
|
|
{
|
|
// FIXME: check logic! -- was written for single commit_coin!
|
|
uint16_t cnc_index_nbo = htons (i);
|
|
uint16_t newcoin_index_nbo = htons (num_newcoins);
|
|
struct TALER_DB_QueryParam params[] = {
|
|
TALER_DB_QUERY_PARAM_PTR(refresh_session_pub),
|
|
TALER_DB_QUERY_PARAM_PTR_SIZED(commit_coins->coin_ev, commit_coins->coin_ev_size),
|
|
TALER_DB_QUERY_PARAM_PTR(&cnc_index_nbo),
|
|
TALER_DB_QUERY_PARAM_PTR(&newcoin_index_nbo),
|
|
TALER_DB_QUERY_PARAM_PTR_SIZED(commit_coins->refresh_link->coin_priv_enc,
|
|
commit_coins->refresh_link->blinding_key_enc_size +
|
|
sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey)),
|
|
TALER_DB_QUERY_PARAM_END
|
|
};
|
|
|
|
PGresult *result = TALER_DB_exec_prepared (session->conn,
|
|
"insert_refresh_commit_coin",
|
|
params);
|
|
|
|
if (PGRES_COMMAND_OK != PQresultStatus (result))
|
|
{
|
|
BREAK_DB_ERR (result);
|
|
PQclear (result);
|
|
return GNUNET_SYSERR;
|
|
}
|
|
|
|
if (0 != strcmp ("1", PQcmdTuples (result)))
|
|
{
|
|
GNUNET_break (0);
|
|
return GNUNET_SYSERR;
|
|
}
|
|
|
|
PQclear (result);
|
|
return GNUNET_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* Obtain information about the commitment of the
|
|
* given coin of the given refresh session from the database.
|
|
*
|
|
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
|
* @param session database connection to use
|
|
* @param refresh_session_pub refresh session the commitment belongs to
|
|
* @param i set index (1st dimension)
|
|
* @param j coin index (2nd dimension), corresponds to refreshed (new) coins
|
|
* @param commit_coin[OUT] coin commitment to return
|
|
* @return #GNUNET_OK on success
|
|
* #GNUNET_NO if not found
|
|
* #GNUNET_SYSERR on error
|
|
*/
|
|
static int
|
|
postgres_get_refresh_commit_coins (void *cls,
|
|
struct TALER_MINTDB_Session *session,
|
|
const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub,
|
|
unsigned int cnc_index,
|
|
unsigned int newcoin_index,
|
|
struct RefreshCommitCoin *cc)
|
|
{
|
|
// FIXME: check logic!
|
|
uint16_t cnc_index_nbo = htons (cnc_index);
|
|
uint16_t newcoin_index_nbo = htons (newcoin_index);
|
|
struct TALER_DB_QueryParam params[] = {
|
|
TALER_DB_QUERY_PARAM_PTR(refresh_session_pub),
|
|
TALER_DB_QUERY_PARAM_PTR(&cnc_index_nbo),
|
|
TALER_DB_QUERY_PARAM_PTR(&newcoin_index_nbo),
|
|
TALER_DB_QUERY_PARAM_END
|
|
};
|
|
char *c_buf;
|
|
size_t c_buf_size;
|
|
char *rl_buf;
|
|
size_t rl_buf_size;
|
|
struct TALER_RefreshLinkEncrypted *rl;
|
|
|
|
PGresult *result = TALER_DB_exec_prepared (session->conn,
|
|
"get_refresh_commit_coin",
|
|
params);
|
|
|
|
if (PGRES_TUPLES_OK != PQresultStatus (result))
|
|
{
|
|
BREAK_DB_ERR (result);
|
|
PQclear (result);
|
|
return GNUNET_SYSERR;
|
|
}
|
|
|
|
if (0 == PQntuples (result))
|
|
{
|
|
PQclear (result);
|
|
return GNUNET_NO;
|
|
}
|
|
|
|
struct TALER_DB_ResultSpec rs[] = {
|
|
TALER_DB_RESULT_SPEC_VAR("coin_ev", &c_buf, &c_buf_size),
|
|
TALER_DB_RESULT_SPEC_VAR("link_vector_enc", &rl_buf, &rl_buf_size),
|
|
TALER_DB_RESULT_SPEC_END
|
|
};
|
|
if (GNUNET_YES != TALER_DB_extract_result (result, rs, 0))
|
|
{
|
|
PQclear (result);
|
|
return GNUNET_SYSERR;
|
|
}
|
|
PQclear (result);
|
|
if (rl_buf_size < sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey))
|
|
{
|
|
GNUNET_free (c_buf);
|
|
GNUNET_free (rl_buf);
|
|
return GNUNET_SYSERR;
|
|
}
|
|
rl = TALER_refresh_link_encrypted_decode (rl_buf,
|
|
rl_buf_size);
|
|
GNUNET_free (rl_buf);
|
|
cc->refresh_link = rl;
|
|
cc->coin_ev = c_buf;
|
|
cc->coin_ev_size = c_buf_size;
|
|
return GNUNET_YES;
|
|
}
|
|
|
|
|
|
/**
|
|
* Store the commitment to the given (encrypted) refresh link data
|
|
* for the given refresh session.
|
|
*
|
|
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
|
* @param session database connection to use
|
|
* @param refresh_session_pub public key of the refresh session this
|
|
* commitment belongs with
|
|
* @param i set index (1st dimension)
|
|
* @param j coin index (2nd dimension), corresponds to melted (old) coins
|
|
* @param commit_link link information to store
|
|
* @return #GNUNET_SYSERR on internal error, #GNUNET_OK on success
|
|
*/
|
|
static int
|
|
postgres_insert_refresh_commit_links (void *cls,
|
|
struct TALER_MINTDB_Session *session,
|
|
const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub,
|
|
unsigned int i,
|
|
unsigned int j,
|
|
const struct RefreshCommitLink *commit_link)
|
|
{
|
|
// FIXME: check logic!
|
|
uint16_t cnc_index_nbo = htons (i);
|
|
uint16_t oldcoin_index_nbo = htons (j);
|
|
struct TALER_DB_QueryParam params[] = {
|
|
TALER_DB_QUERY_PARAM_PTR(refresh_session_pub),
|
|
TALER_DB_QUERY_PARAM_PTR(&commit_link->transfer_pub),
|
|
TALER_DB_QUERY_PARAM_PTR(&cnc_index_nbo),
|
|
TALER_DB_QUERY_PARAM_PTR(&oldcoin_index_nbo),
|
|
TALER_DB_QUERY_PARAM_PTR(&commit_link->shared_secret_enc),
|
|
TALER_DB_QUERY_PARAM_END
|
|
};
|
|
|
|
PGresult *result = TALER_DB_exec_prepared (session->conn,
|
|
"insert_refresh_commit_link",
|
|
params);
|
|
if (PGRES_COMMAND_OK != PQresultStatus (result))
|
|
{
|
|
BREAK_DB_ERR (result);
|
|
PQclear (result);
|
|
return GNUNET_SYSERR;
|
|
}
|
|
|
|
if (0 != strcmp ("1", PQcmdTuples (result)))
|
|
{
|
|
GNUNET_break (0);
|
|
return GNUNET_SYSERR;
|
|
}
|
|
|
|
PQclear (result);
|
|
return GNUNET_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* Obtain the commited (encrypted) refresh link data
|
|
* for the given refresh session.
|
|
*
|
|
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
|
* @param session database connection to use
|
|
* @param refresh_session_pub public key of the refresh session this
|
|
* commitment belongs with
|
|
* @param i set index (1st dimension)
|
|
* @param num_links size of the @a commit_link array
|
|
* @param links[OUT] array of link information to return
|
|
* @return #GNUNET_SYSERR on internal error,
|
|
* #GNUNET_NO if commitment was not found
|
|
* #GNUNET_OK on success
|
|
*/
|
|
static int
|
|
postgres_get_refresh_commit_links (void *cls,
|
|
struct TALER_MINTDB_Session *session,
|
|
const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub,
|
|
unsigned int i,
|
|
unsigned int num_links,
|
|
struct RefreshCommitLink *links)
|
|
{
|
|
// FIXME: check logic: was written for a single link!
|
|
uint16_t cnc_index_nbo = htons (i);
|
|
uint16_t oldcoin_index_nbo = htons (num_links);
|
|
|
|
struct TALER_DB_QueryParam params[] = {
|
|
TALER_DB_QUERY_PARAM_PTR(refresh_session_pub),
|
|
TALER_DB_QUERY_PARAM_PTR(&cnc_index_nbo),
|
|
TALER_DB_QUERY_PARAM_PTR(&oldcoin_index_nbo),
|
|
TALER_DB_QUERY_PARAM_END
|
|
};
|
|
|
|
PGresult *result = TALER_DB_exec_prepared (session->conn,
|
|
"get_refresh_commit_link",
|
|
params);
|
|
if (PGRES_TUPLES_OK != PQresultStatus (result))
|
|
{
|
|
BREAK_DB_ERR (result);
|
|
PQclear (result);
|
|
return GNUNET_SYSERR;
|
|
}
|
|
|
|
if (0 == PQntuples (result))
|
|
{
|
|
PQclear (result);
|
|
return GNUNET_NO;
|
|
}
|
|
|
|
struct TALER_DB_ResultSpec rs[] = {
|
|
TALER_DB_RESULT_SPEC("transfer_pub", &links->transfer_pub),
|
|
TALER_DB_RESULT_SPEC("link_secret_enc", &links->shared_secret_enc),
|
|
TALER_DB_RESULT_SPEC_END
|
|
};
|
|
|
|
if (GNUNET_YES != TALER_DB_extract_result (result, rs, 0))
|
|
{
|
|
PQclear (result);
|
|
return GNUNET_SYSERR;
|
|
}
|
|
|
|
PQclear (result);
|
|
return GNUNET_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* Insert signature of a new coin generated during refresh into
|
|
* the database indexed by the refresh session and the index
|
|
* of the coin. This data is later used should an old coin
|
|
* be used to try to obtain the private keys during "/refresh/link".
|
|
*
|
|
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
|
* @param session database connection
|
|
* @param session_pub refresh session
|
|
* @param newcoin_index coin index
|
|
* @param ev_sig coin signature
|
|
* @return #GNUNET_OK on success
|
|
*/
|
|
static int
|
|
postgres_insert_refresh_collectable (void *cls,
|
|
struct TALER_MINTDB_Session *session,
|
|
const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub,
|
|
uint16_t newcoin_index,
|
|
const struct GNUNET_CRYPTO_rsa_Signature *ev_sig)
|
|
{
|
|
// FIXME: check logic!
|
|
uint16_t newcoin_index_nbo = htons (newcoin_index);
|
|
char *buf;
|
|
size_t buf_size;
|
|
PGresult *result;
|
|
|
|
buf_size = GNUNET_CRYPTO_rsa_signature_encode (ev_sig,
|
|
&buf);
|
|
{
|
|
struct TALER_DB_QueryParam params[] = {
|
|
TALER_DB_QUERY_PARAM_PTR(session_pub),
|
|
TALER_DB_QUERY_PARAM_PTR(&newcoin_index_nbo),
|
|
TALER_DB_QUERY_PARAM_PTR_SIZED(buf, buf_size),
|
|
TALER_DB_QUERY_PARAM_END
|
|
};
|
|
result = TALER_DB_exec_prepared (session->conn,
|
|
"insert_refresh_collectable",
|
|
params);
|
|
}
|
|
GNUNET_free (buf);
|
|
if (PGRES_COMMAND_OK != PQresultStatus (result))
|
|
{
|
|
BREAK_DB_ERR (result);
|
|
PQclear (result);
|
|
return GNUNET_SYSERR;
|
|
}
|
|
PQclear (result);
|
|
return GNUNET_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* Obtain the link data of a coin, that is the encrypted link
|
|
* information, the denomination keys and the signatures.
|
|
*
|
|
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
|
* @param session database connection
|
|
* @param coin_pub public key to use to retrieve linkage data
|
|
* @return all known link data for the coin
|
|
*/
|
|
static struct LinkDataList *
|
|
postgres_get_link_data_list (void *cls,
|
|
struct TALER_MINTDB_Session *session,
|
|
const struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub)
|
|
{
|
|
// FIXME: check logic!
|
|
struct LinkDataList *ldl;
|
|
struct LinkDataList *pos;
|
|
struct TALER_DB_QueryParam params[] = {
|
|
TALER_DB_QUERY_PARAM_PTR(coin_pub),
|
|
TALER_DB_QUERY_PARAM_END
|
|
};
|
|
PGresult *result = TALER_DB_exec_prepared (session->conn, "get_link", params);
|
|
|
|
ldl = NULL;
|
|
if (PGRES_TUPLES_OK != PQresultStatus (result))
|
|
{
|
|
BREAK_DB_ERR (result);
|
|
PQclear (result);
|
|
return NULL;
|
|
}
|
|
|
|
if (0 == PQntuples (result))
|
|
{
|
|
PQclear (result);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
int i = 0;
|
|
|
|
for (i = 0; i < PQntuples (result); i++)
|
|
{
|
|
struct TALER_RefreshLinkEncrypted *link_enc;
|
|
struct GNUNET_CRYPTO_rsa_PublicKey *denom_pub;
|
|
struct GNUNET_CRYPTO_rsa_Signature *sig;
|
|
char *ld_buf;
|
|
size_t ld_buf_size;
|
|
char *pk_buf;
|
|
size_t pk_buf_size;
|
|
char *sig_buf;
|
|
size_t sig_buf_size;
|
|
struct TALER_DB_ResultSpec rs[] = {
|
|
TALER_DB_RESULT_SPEC_VAR("link_vector_enc", &ld_buf, &ld_buf_size),
|
|
TALER_DB_RESULT_SPEC_VAR("denom_pub", &pk_buf, &pk_buf_size),
|
|
TALER_DB_RESULT_SPEC_VAR("ev_sig", &sig_buf, &sig_buf_size),
|
|
TALER_DB_RESULT_SPEC_END
|
|
};
|
|
|
|
if (GNUNET_OK != TALER_DB_extract_result (result, rs, i))
|
|
{
|
|
PQclear (result);
|
|
GNUNET_break (0);
|
|
common_free_link_data_list (cls,
|
|
ldl);
|
|
return NULL;
|
|
}
|
|
if (ld_buf_size < sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey))
|
|
{
|
|
PQclear (result);
|
|
GNUNET_free (pk_buf);
|
|
GNUNET_free (sig_buf);
|
|
GNUNET_free (ld_buf);
|
|
common_free_link_data_list (cls,
|
|
ldl);
|
|
return NULL;
|
|
}
|
|
// FIXME: use util API for this!
|
|
link_enc = GNUNET_malloc (sizeof (struct TALER_RefreshLinkEncrypted) +
|
|
ld_buf_size - sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey));
|
|
link_enc->blinding_key_enc = (const char *) &link_enc[1];
|
|
link_enc->blinding_key_enc_size = ld_buf_size - sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey);
|
|
memcpy (link_enc->coin_priv_enc,
|
|
ld_buf,
|
|
ld_buf_size);
|
|
|
|
sig = GNUNET_CRYPTO_rsa_signature_decode (sig_buf,
|
|
sig_buf_size);
|
|
denom_pub = GNUNET_CRYPTO_rsa_public_key_decode (pk_buf,
|
|
pk_buf_size);
|
|
GNUNET_free (pk_buf);
|
|
GNUNET_free (sig_buf);
|
|
GNUNET_free (ld_buf);
|
|
if ( (NULL == sig) ||
|
|
(NULL == denom_pub) )
|
|
{
|
|
if (NULL != denom_pub)
|
|
GNUNET_CRYPTO_rsa_public_key_free (denom_pub);
|
|
if (NULL != sig)
|
|
GNUNET_CRYPTO_rsa_signature_free (sig);
|
|
GNUNET_free (link_enc);
|
|
GNUNET_break (0);
|
|
PQclear (result);
|
|
common_free_link_data_list (cls,
|
|
ldl);
|
|
return NULL;
|
|
}
|
|
pos = GNUNET_new (struct LinkDataList);
|
|
pos->next = ldl;
|
|
pos->link_data_enc = link_enc;
|
|
pos->denom_pub = denom_pub;
|
|
pos->ev_sig = sig;
|
|
ldl = pos;
|
|
}
|
|
return ldl;
|
|
}
|
|
|
|
|
|
/**
|
|
* Obtain shared secret and transfer public key from the public key of
|
|
* the coin. This information and the link information returned by
|
|
* #postgres_get_link_data_list() enable the owner of an old coin to
|
|
* determine the private keys of the new coins after the melt.
|
|
*
|
|
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
|
* @param session database connection
|
|
* @param coin_pub public key of the coin
|
|
* @param transfer_pub[OUT] public transfer key
|
|
* @param shared_secret_enc[OUT] set to shared secret
|
|
* @return #GNUNET_OK on success,
|
|
* #GNUNET_NO on failure (not found)
|
|
* #GNUNET_SYSERR on internal failure (database issue)
|
|
*/
|
|
static int
|
|
postgres_get_transfer (void *cls,
|
|
struct TALER_MINTDB_Session *session,
|
|
const struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub,
|
|
struct GNUNET_CRYPTO_EcdsaPublicKey *transfer_pub,
|
|
struct TALER_EncryptedLinkSecret *shared_secret_enc)
|
|
{
|
|
// FIXME: check logic!
|
|
struct TALER_DB_QueryParam params[] = {
|
|
TALER_DB_QUERY_PARAM_PTR(coin_pub),
|
|
TALER_DB_QUERY_PARAM_END
|
|
};
|
|
|
|
PGresult *result = TALER_DB_exec_prepared (session->conn, "get_transfer", params);
|
|
|
|
if (PGRES_TUPLES_OK != PQresultStatus (result))
|
|
{
|
|
BREAK_DB_ERR (result);
|
|
PQclear (result);
|
|
return GNUNET_SYSERR;
|
|
}
|
|
|
|
if (0 == PQntuples (result))
|
|
{
|
|
PQclear (result);
|
|
return GNUNET_NO;
|
|
}
|
|
|
|
if (1 != PQntuples (result))
|
|
{
|
|
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
|
"got %d tuples for get_transfer\n",
|
|
PQntuples (result));
|
|
GNUNET_break (0);
|
|
return GNUNET_SYSERR;
|
|
}
|
|
|
|
struct TALER_DB_ResultSpec rs[] = {
|
|
TALER_DB_RESULT_SPEC("transfer_pub", transfer_pub),
|
|
TALER_DB_RESULT_SPEC("link_secret_enc", shared_secret_enc),
|
|
TALER_DB_RESULT_SPEC_END
|
|
};
|
|
|
|
if (GNUNET_OK != TALER_DB_extract_result (result, rs, 0))
|
|
{
|
|
PQclear (result);
|
|
GNUNET_break (0);
|
|
return GNUNET_SYSERR;
|
|
}
|
|
|
|
PQclear (result);
|
|
return GNUNET_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* Compile a list of all (historic) transactions performed
|
|
* with the given coin (/refresh/melt and /deposit operations).
|
|
*
|
|
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
|
* @param session database connection
|
|
* @param coin_pub coin to investigate
|
|
* @return list of transactions, NULL if coin is fresh
|
|
*/
|
|
static struct TALER_MINT_DB_TransactionList *
|
|
postgres_get_coin_transactions (void *cls,
|
|
struct TALER_MINTDB_Session *session,
|
|
const struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub)
|
|
{
|
|
// FIXME: check logic!
|
|
GNUNET_break (0); // FIXME: implement!
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Initialize Postgres database subsystem.
|
|
*
|
|
* @param cls a configuration instance
|
|
* @return NULL on error, otherwise a `struct TALER_MINTDB_Plugin`
|
|
*/
|
|
void *
|
|
libtaler_plugin_mintdb_postgres_init (void *cls)
|
|
{
|
|
struct GNUNET_CONFIGURATION_Handle *cfg = cls;
|
|
struct PostgresClosure *pg;
|
|
struct TALER_MINTDB_Plugin *plugin;
|
|
|
|
pg = GNUNET_new (struct PostgresClosure);
|
|
|
|
if (0 != pthread_key_create (&pg->db_conn_threadlocal,
|
|
&db_conn_destroy))
|
|
{
|
|
LOG_ERROR ("Cannnot create pthread key.\n");
|
|
return NULL;
|
|
}
|
|
/* FIXME: use configuration section with "postgres" in its name... */
|
|
if (GNUNET_OK !=
|
|
GNUNET_CONFIGURATION_get_value_string (cfg,
|
|
"mint", "db",
|
|
&pg->TALER_MINT_db_connection_cfg_str))
|
|
{
|
|
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
|
|
"mint",
|
|
"db");
|
|
return NULL;
|
|
}
|
|
plugin = GNUNET_new (struct TALER_MINTDB_Plugin);
|
|
plugin->cls = pg;
|
|
plugin->get_session = &postgres_get_session;
|
|
plugin->drop_temporary = &postgres_drop_temporary;
|
|
plugin->create_tables = &postgres_create_tables;
|
|
plugin->start = &postgres_start;
|
|
plugin->commit = &postgres_commit;
|
|
plugin->rollback = &postgres_rollback;
|
|
plugin->reserve_get = &postgres_reserve_get;
|
|
plugin->reserves_in_insert = &postgres_reserves_in_insert;
|
|
plugin->get_collectable_blindcoin = &postgres_get_collectable_blindcoin;
|
|
plugin->insert_collectable_blindcoin = &postgres_insert_collectable_blindcoin;
|
|
plugin->get_reserve_history = &postgres_get_reserve_history;
|
|
plugin->free_reserve_history = &common_free_reserve_history;
|
|
plugin->have_deposit = &postgres_have_deposit;
|
|
plugin->insert_deposit = &postgres_insert_deposit;
|
|
plugin->get_refresh_session = &postgres_get_refresh_session;
|
|
plugin->create_refresh_session = &postgres_create_refresh_session;
|
|
plugin->insert_refresh_melt = &postgres_insert_refresh_melt;
|
|
plugin->get_refresh_melt = &postgres_get_refresh_melt;
|
|
plugin->insert_refresh_order = &postgres_insert_refresh_order;
|
|
plugin->get_refresh_order = &postgres_get_refresh_order;
|
|
plugin->insert_refresh_commit_coins = &postgres_insert_refresh_commit_coins;
|
|
plugin->get_refresh_commit_coins = &postgres_get_refresh_commit_coins;
|
|
plugin->insert_refresh_commit_links = &postgres_insert_refresh_commit_links;
|
|
plugin->get_refresh_commit_links = &postgres_get_refresh_commit_links;
|
|
plugin->insert_refresh_collectable = &postgres_insert_refresh_collectable;
|
|
plugin->get_link_data_list = &postgres_get_link_data_list;
|
|
plugin->free_link_data_list = &common_free_link_data_list;
|
|
plugin->get_transfer = &postgres_get_transfer;
|
|
// plugin->have_lock = &postgres_have_lock;
|
|
// plugin->insert_lock = &postgres_insert_lock;
|
|
plugin->get_coin_transactions = &postgres_get_coin_transactions;
|
|
plugin->free_coin_transaction_list = &common_free_coin_transaction_list;
|
|
return plugin;
|
|
}
|
|
|
|
|
|
/**
|
|
* Shutdown Postgres database subsystem.
|
|
*
|
|
* @param cls a `struct TALER_MINTDB_Plugin`
|
|
* @return NULL (always)
|
|
*/
|
|
void *
|
|
libtaler_plugin_mintdb_postgres_done (void *cls)
|
|
{
|
|
struct TALER_MINTDB_Plugin *plugin = cls;
|
|
struct PostgresClosure *pg = plugin->cls;
|
|
|
|
GNUNET_free (pg);
|
|
GNUNET_free (plugin);
|
|
return NULL;
|
|
}
|
|
|
|
/* end of plugin_mintdb_postgres.c */
|