/* 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 */ /** * @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 #include #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) { TALER_MINT_DB_free_reserve_history (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 newcoin_index index of the coin to generate * @param denom_pub denomination of the coin 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 newcoin_index, const struct GNUNET_CRYPTO_rsa_PublicKey *denom_pub) { // FIXME: check logic uint16_t newcoin_index_nbo = htons (newcoin_index); char *buf; size_t buf_size; PGresult *result; buf_size = GNUNET_CRYPTO_rsa_public_key_encode (denom_pub, &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 @a newcoin_index coin 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 index of the coin to generate * @param denom_pub denomination of the coin to create * @return NULL on error (not found or internal error) */ static struct GNUNET_CRYPTO_rsa_PublicKey * postgres_get_refresh_order (void *cls, struct TALER_MINTDB_Session *session, const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, uint16_t newcoin_index) { // FIXME: check logic char *buf; size_t buf_size; struct GNUNET_CRYPTO_rsa_PublicKey *denom_pub; uint16_t newcoin_index_nbo = htons (newcoin_index); 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 NULL; } if (0 == PQntuples (result)) { PQclear (result); /* FIXME: may want to distinguish between different error cases! */ return NULL; } 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 NULL; } PQclear (result); denom_pub = GNUNET_CRYPTO_rsa_public_key_decode (buf, buf_size); GNUNET_free (buf); return denom_pub; } /** * 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 j coin index (2nd dimension), corresponds to refreshed (new) coins * @param commit_coin coin commitment to store * @return #GNUNET_OK on success * #GNUNET_SYSERR on error */ static int postgres_insert_refresh_commit_coin (void *cls, struct TALER_MINTDB_Session *session, const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub, unsigned int i, unsigned int j, const struct RefreshCommitCoin *commit_coin) { // FIXME: check logic! uint16_t cnc_index_nbo = htons (i); uint16_t newcoin_index_nbo = htons (j); struct TALER_DB_QueryParam params[] = { TALER_DB_QUERY_PARAM_PTR(refresh_session_pub), TALER_DB_QUERY_PARAM_PTR_SIZED(commit_coin->coin_ev, commit_coin->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_coin->refresh_link->coin_priv_enc, commit_coin->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_coin (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_link (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 j coin index (2nd dimension), corresponds to melted (old) coins * @param cc[OUT] 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_link (void *cls, struct TALER_MINTDB_Session *session, const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub, unsigned int cnc_index, unsigned int oldcoin_index, struct RefreshCommitLink *cc) { // FIXME: check logic! uint16_t cnc_index_nbo = htons (cnc_index); uint16_t oldcoin_index_nbo = htons (oldcoin_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(&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", &cc->transfer_pub), TALER_DB_RESULT_SPEC("link_secret_enc", &cc->shared_secret_enc), TALER_DB_RESULT_SPEC_END }; if (GNUNET_YES != TALER_DB_extract_result (result, rs, 0)) { PQclear (result); GNUNET_free (cc); 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 (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); TALER_db_link_data_list_free (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); TALER_db_link_data_list_free (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); TALER_db_link_data_list_free (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 * #TALER_db_get_link() 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->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_coin = &postgres_insert_refresh_commit_coin; plugin->get_refresh_commit_coin = &postgres_get_refresh_commit_coin; plugin->insert_refresh_commit_link = &postgres_insert_refresh_commit_link; plugin->get_refresh_commit_link = &postgres_get_refresh_commit_link; plugin->insert_refresh_collectable = &postgres_insert_refresh_collectable; plugin->get_link = &postgres_get_link; 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; 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 */