/*
This file is part of TALER
(C) 2014, 2015, 2016 INRIA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Lesser General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
TALER; see the file COPYING. If not, see
*/
/**
* @file merchant/plugin_merchantdb_postgres.c
* @brief database helper functions for postgres used by the merchant
* @author Sree Harsha Totakura
* @author Christian Grothoff
* @author Marcello Stanisci
*/
#include "platform.h"
#include
#include
#include
#include
#include
#include "taler_merchantdb_plugin.h"
/**
* Type of the "cls" argument given to each of the functions in
* our API.
*/
struct PostgresClosure
{
/**
* Postgres connection handle.
*/
PGconn *conn;
};
/**
* Extract error code.
*
* @param res postgres result object with error details
*/
#define EXTRACT_DB_ERROR(res) \
PQresultErrorField(res, PG_DIAG_SQLSTATE)
/**
* Log error from PostGres.
*
* @param kind log level to use
* @param cmd command that failed
* @param res postgres result object with error details
*/
#define PQSQL_strerror(kind, cmd, res) \
GNUNET_log_from (kind, "merchantdb-postgres", \
"SQL %s failed at %s:%u with error: %s", \
cmd, __FILE__, __LINE__, PQresultErrorMessage (res));
/**
* Log a really unexpected PQ error.
*
* @param result PQ result object of the PQ operation that failed
*/
#define BREAK_DB_ERR(result) do { \
GNUNET_break (0); \
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, \
"Database failure: %s\n", \
PQresultErrorMessage (result)); \
} while (0)
/**
* Drop merchant tables
*
* @param cls closure our `struct Plugin`
* @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
*/
static int
postgres_drop_tables (void *cls)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_ExecuteStatement es[] = {
GNUNET_PQ_make_try_execute ("DROP TABLE merchant_transfers;"),
GNUNET_PQ_make_try_execute ("DROP TABLE merchant_deposits;"),
GNUNET_PQ_make_try_execute ("DROP TABLE merchant_transactions;"),
GNUNET_PQ_make_try_execute ("DROP TABLE merchant_proofs;"),
GNUNET_PQ_make_try_execute ("DROP TABLE merchant_contract_terms;"),
GNUNET_PQ_EXECUTE_STATEMENT_END
};
return GNUNET_PQ_exec_statements (pg->conn,
es);
}
/**
* Initialize merchant tables
*
* @param cls closure our `struct Plugin`
* @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
*/
static int
postgres_initialize (void *cls)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_ExecuteStatement es[] = {
GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS merchant_contract_terms ("
"order_id VARCHAR NOT NULL"
",merchant_pub BYTEA NOT NULL"
",contract_terms BYTEA NOT NULL"
",h_contract_terms BYTEA NOT NULL"
",timestamp INT8 NOT NULL"
",row_id BIGSERIAL"
",PRIMARY KEY (order_id, merchant_pub)"
");"),
GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS merchant_refunds ("
" rtransaction_id INT8 NOT NULL"
",h_contract_terms BYTEA NOT NULL"
",coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)"
",reason VARCHAR NOT NULL"
",refund_amount_val INT8 NOT NULL"
",refund_amount_frac INT8 NOT NULL"
",refund_amount_curr VARCHAR(" TALER_CURRENCY_LEN_STR ") NOT NULL"
");"),
GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS merchant_transactions ("
" h_contract_terms BYTEA NOT NULL"
",exchange_uri VARCHAR NOT NULL"
",merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)"
",h_wire BYTEA NOT NULL CHECK (LENGTH(h_wire)=64)"
",timestamp INT8 NOT NULL"
",refund_deadline INT8 NOT NULL"
",total_amount_val INT8 NOT NULL"
",total_amount_frac INT4 NOT NULL"
",total_amount_curr VARCHAR(" TALER_CURRENCY_LEN_STR ") NOT NULL"
",PRIMARY KEY (h_contract_terms, merchant_pub)"
");"),
GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS merchant_deposits ("
" h_contract_terms BYTEA NOT NULL"
",merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)"
",FOREIGN KEY (h_contract_terms, merchant_pub) REFERENCES merchant_transactions (h_contract_terms, merchant_pub)"
",coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)"
",amount_with_fee_val INT8 NOT NULL"
",amount_with_fee_frac INT4 NOT NULL"
",amount_with_fee_curr VARCHAR(" TALER_CURRENCY_LEN_STR ") NOT NULL"
",deposit_fee_val INT8 NOT NULL"
",deposit_fee_frac INT4 NOT NULL"
",deposit_fee_curr VARCHAR(" TALER_CURRENCY_LEN_STR ") NOT NULL"
",refund_fee_val INT8 NOT NULL"
",refund_fee_frac INT4 NOT NULL"
",refund_fee_curr VARCHAR(" TALER_CURRENCY_LEN_STR ") NOT NULL"
",signkey_pub BYTEA NOT NULL CHECK (LENGTH(signkey_pub)=32)"
",exchange_proof BYTEA NOT NULL"
",PRIMARY KEY (h_contract_terms, coin_pub)"
");"),
GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS merchant_proofs ("
" exchange_uri VARCHAR NOT NULL"
",wtid BYTEA CHECK (LENGTH(wtid)=32)"
",execution_time INT8 NOT NULL"
",signkey_pub BYTEA NOT NULL CHECK (LENGTH(signkey_pub)=32)"
",proof BYTEA NOT NULL"
",PRIMARY KEY (wtid, exchange_uri)"
");"),
/* Note that h_contract_terms + coin_pub may actually be unknown to
us, e.g. someone else deposits something for us at the exchange.
Hence those cannot be foreign keys into deposits/transactions! */
GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS merchant_transfers ("
" h_contract_terms BYTEA NOT NULL"
",coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)"
",wtid BYTEA NOT NULL CHECK (LENGTH(wtid)=32)"
",PRIMARY KEY (h_contract_terms, coin_pub)"
");"),
GNUNET_PQ_make_try_execute ("CREATE INDEX IF NOT EXISTS merchant_transfers_by_coin"
" ON merchant_transfers (h_contract_terms, coin_pub)"),
GNUNET_PQ_make_try_execute ("CREATE INDEX IF NOT EXISTS merchant_transfers_by_wtid"
" ON merchant_transfers (wtid)"),
GNUNET_PQ_EXECUTE_STATEMENT_END
};
struct GNUNET_PQ_PreparedStatement ps[] = {
GNUNET_PQ_make_prepare ("insert_transaction",
"INSERT INTO merchant_transactions"
"(h_contract_terms"
",exchange_uri"
",merchant_pub"
",h_wire"
",timestamp"
",refund_deadline"
",total_amount_val"
",total_amount_frac"
",total_amount_curr"
") VALUES "
"($1, $2, $3, $4, $5, $6, $7, $8, $9)",
9),
GNUNET_PQ_make_prepare ("insert_deposit",
"INSERT INTO merchant_deposits"
"(h_contract_terms"
",merchant_pub"
",coin_pub"
",amount_with_fee_val"
",amount_with_fee_frac"
",amount_with_fee_curr"
",deposit_fee_val"
",deposit_fee_frac"
",deposit_fee_curr"
",refund_fee_val"
",refund_fee_frac"
",refund_fee_curr"
",signkey_pub"
",exchange_proof) VALUES "
"($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)",
14),
GNUNET_PQ_make_prepare ("insert_transfer",
"INSERT INTO merchant_transfers"
"(h_contract_terms"
",coin_pub"
",wtid) VALUES "
"($1, $2, $3)",
3),
GNUNET_PQ_make_prepare ("insert_proof",
"INSERT INTO merchant_proofs"
"(exchange_uri"
",wtid"
",execution_time"
",signkey_pub"
",proof) VALUES "
"($1, $2, $3, $4, $5)",
5),
GNUNET_PQ_make_prepare ("insert_contract_terms",
"INSERT INTO merchant_contract_terms"
"(order_id"
",merchant_pub"
",timestamp"
",contract_terms"
",h_contract_terms)"
" VALUES "
"($1, $2, $3, $4, $5)",
4),
GNUNET_PQ_make_prepare ("find_contract_terms_from_hash",
"SELECT"
" contract_terms"
" FROM merchant_contract_terms"
" WHERE"
" h_contract_terms=$1"
" AND merchant_pub=$2",
2),
GNUNET_PQ_make_prepare ("find_contract_terms",
"SELECT"
" contract_terms"
" FROM merchant_contract_terms"
" WHERE"
" order_id=$1"
" AND merchant_pub=$2",
2),
GNUNET_PQ_make_prepare ("find_contract_terms_by_date",
"SELECT"
" contract_terms"
",order_id"
",row_id"
" FROM merchant_contract_terms"
" WHERE"
" timestamp<$1"
" AND merchant_pub=$2"
" ORDER BY row_id DESC, timestamp DESC"
" LIMIT $3",
3),
GNUNET_PQ_make_prepare ("find_refunds_from_contract_terms_hash",
"SELECT * FROM merchant_refunds"
" WHERE h_contract_terms=$1",
1),
GNUNET_PQ_make_prepare ("find_contract_terms_by_date_and_range",
"SELECT"
" contract_terms"
",order_id"
",row_id"
" FROM merchant_contract_terms"
" WHERE"
" timestamp<$1"
" AND merchant_pub=$2"
" AND row_id<$3"
" ORDER BY row_id DESC, timestamp DESC"
" LIMIT $4",
4),
GNUNET_PQ_make_prepare ("find_contract_terms_by_date_and_range_future",
"SELECT"
" contract_terms"
",order_id"
",row_id"
" FROM merchant_contract_terms"
" WHERE"
" timestamp>$1"
" AND merchant_pub=$2"
" AND row_id>$3"
" ORDER BY row_id DESC, timestamp DESC"
" LIMIT $4",
4),
GNUNET_PQ_make_prepare ("find_transaction",
"SELECT"
" exchange_uri"
",h_wire"
",timestamp"
",refund_deadline"
",total_amount_val"
",total_amount_frac"
",total_amount_curr"
" FROM merchant_transactions"
" WHERE h_contract_terms=$1"
" AND merchant_pub=$2",
2),
GNUNET_PQ_make_prepare ("find_deposits",
"SELECT"
" coin_pub"
",amount_with_fee_val"
",amount_with_fee_frac"
",amount_with_fee_curr"
",deposit_fee_val"
",deposit_fee_frac"
",deposit_fee_curr"
",exchange_proof"
" FROM merchant_deposits"
" WHERE h_contract_terms=$1"
" AND merchant_pub=$2",
2),
GNUNET_PQ_make_prepare ("find_deposits_by_hash_and_coin",
"SELECT"
" amount_with_fee_val"
",amount_with_fee_frac"
",amount_with_fee_curr"
",deposit_fee_val"
",deposit_fee_frac"
",deposit_fee_curr"
",exchange_proof"
" FROM merchant_deposits"
" WHERE h_contract_terms=$1"
" AND merchant_pub=$2"
" AND coin_pub=$3",
3),
GNUNET_PQ_make_prepare ("find_transfers_by_hash",
"SELECT"
" coin_pub"
",wtid"
",merchant_proofs.execution_time"
",merchant_proofs.proof"
" FROM merchant_transfers"
" JOIN merchant_proofs USING (wtid)"
" WHERE h_contract_terms=$1",
1),
GNUNET_PQ_make_prepare ("find_deposits_by_wtid",
"SELECT"
" merchant_transfers.h_contract_terms"
",merchant_transfers.coin_pub"
",merchant_deposits.amount_with_fee_val"
",merchant_deposits.amount_with_fee_frac"
",merchant_deposits.amount_with_fee_curr"
",merchant_deposits.deposit_fee_val"
",merchant_deposits.deposit_fee_frac"
",merchant_deposits.deposit_fee_curr"
",merchant_deposits.exchange_proof"
" FROM merchant_transfers"
" JOIN merchant_deposits"
" ON (merchant_deposits.h_contract_terms = merchant_transfers.h_contract_terms"
" AND"
" merchant_deposits.coin_pub = merchant_transfers.coin_pub)"
" WHERE wtid=$1",
1),
GNUNET_PQ_make_prepare ("find_proof_by_wtid",
"SELECT"
" proof"
" FROM merchant_proofs"
" WHERE wtid=$1"
" AND exchange_uri=$2",
2),
GNUNET_PQ_PREPARED_STATEMENT_END
};
if (GNUNET_OK !=
GNUNET_PQ_exec_statements (pg->conn,
es))
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
if (GNUNET_OK !=
GNUNET_PQ_prepare_statements (pg->conn,
ps))
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
return GNUNET_OK;
}
/**
* Retrieve proposal data given its proposal data's hashcode
*
* @param cls closure
* @param contract_terms where to store the retrieved proposal data
* @param h_contract_terms proposal data's hashcode that will be used to
* perform the lookup
* @return #GNUNET_OK on success, #GNUNET_NO if no proposal is
* found, #GNUNET_SYSERR upon error
*/
static int
postgres_find_contract_terms_from_hash (void *cls,
json_t **contract_terms,
const struct GNUNET_HashCode *h_contract_terms,
const struct TALER_MerchantPublicKeyP *merchant_pub)
{
struct PostgresClosure *pg = cls;
enum GNUNET_PQ_QueryStatus res;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
GNUNET_PQ_query_param_auto_from_type (merchant_pub),
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_ResultSpec rs[] = {
TALER_PQ_result_spec_json ("contract_terms",
contract_terms),
GNUNET_PQ_result_spec_end
};
res = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"find_contract_terms_from_hash",
params,
rs);
if (res < 0)
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
if (1 < res)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Mupltiple proposal data hash the same hashcode!\n");
return GNUNET_SYSERR;
}
if (0 == res)
return GNUNET_NO;
return GNUNET_OK;
}
/**
* Retrieve proposal data given its order id.
*
* @param cls closure
* @param[out] contract_terms where to store the retrieved proposal data
* @param order id order id used to perform the lookup
* @return #GNUNET_OK on success, #GNUNET_NO if no proposal is
* found, #GNUNET_SYSERR upon error
*/
static int
postgres_find_contract_terms (void *cls,
json_t **contract_terms,
const char *order_id,
const struct TALER_MerchantPublicKeyP *merchant_pub)
{
struct PostgresClosure *pg = cls;
enum GNUNET_PQ_QueryStatus res;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (order_id),
GNUNET_PQ_query_param_auto_from_type (merchant_pub),
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_ResultSpec rs[] = {
TALER_PQ_result_spec_json ("contract_terms",
contract_terms),
GNUNET_PQ_result_spec_end
};
*contract_terms = NULL;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Finding contract term, order_id: '%s', merchant_pub: '%s'.\n",
order_id,
TALER_B2S (merchant_pub));
res = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"find_contract_terms",
params,
rs);
if (res < 0)
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
if (res > 1)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Mupltiple proposal data share the same hashcode.\n");
return GNUNET_SYSERR;
}
if (0 == res)
return GNUNET_NO;
return GNUNET_OK;
}
/**
* Insert proposal data and its hashcode into db
*
* @param cls closure
* @param order_id identificator of the proposal being stored
* @param contract_terms proposal data to store
* @return #GNUNET_OK on success, #GNUNET_SYSERR upon error
*/
static int
postgres_insert_contract_terms (void *cls,
const char *order_id,
const struct TALER_MerchantPublicKeyP *merchant_pub,
struct GNUNET_TIME_Absolute timestamp,
const json_t *contract_terms)
{
struct PostgresClosure *pg = cls;
PGresult *result;
int ret;
struct GNUNET_HashCode h_contract_terms;
if (GNUNET_OK != TALER_JSON_hash (contract_terms,
&h_contract_terms))
return GNUNET_SYSERR;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"inserting contract_terms: order_id: %s, merchant_pub: %s, h_contract_terms: %s.\n",
order_id,
TALER_B2S (merchant_pub),
GNUNET_h2s (&h_contract_terms));
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (order_id),
GNUNET_PQ_query_param_auto_from_type (merchant_pub),
GNUNET_PQ_query_param_absolute_time (×tamp),
TALER_PQ_query_param_json (contract_terms),
GNUNET_PQ_query_param_auto_from_type (&h_contract_terms),
GNUNET_PQ_query_param_end
};
result = GNUNET_PQ_exec_prepared (pg->conn,
"insert_contract_terms",
params);
/**
* We don't treat a unique_violation (code '23505') error as
* an actual error, since there is no problem if a frontend tries
* to store twice the same proposal. That is especially needed
* when DB-less frontends perform replayed payments.
*/
if (PGRES_COMMAND_OK != PQresultStatus (result)
&& (0 != memcmp ("23505",
EXTRACT_DB_ERROR (result),
5)))
{
ret = GNUNET_SYSERR;
BREAK_DB_ERR (result);
}
else
{
ret = GNUNET_OK;
}
PQclear (result);
return ret;
}
/**
* Insert transaction data into the database.
*
* @param cls closure
* @param h_contract_terms hashcode of the proposal data associated with the
* transaction being stored
* @param merchant_pub merchant's public key
* @param exchange_uri URI of the exchange
* @param h_wire hash of our wire details
* @param timestamp time of the confirmation
* @param refund refund deadline
* @param total_amount total amount we receive for the contract after fees
* @return #GNUNET_OK on success, #GNUNET_SYSERR upon error
*/
static int
postgres_store_transaction (void *cls,
const struct GNUNET_HashCode *h_contract_terms,
const struct TALER_MerchantPublicKeyP *merchant_pub,
const char *exchange_uri,
const struct GNUNET_HashCode *h_wire,
struct GNUNET_TIME_Absolute timestamp,
struct GNUNET_TIME_Absolute refund,
const struct TALER_Amount *total_amount)
{
struct PostgresClosure *pg = cls;
PGresult *result;
int ret;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
GNUNET_PQ_query_param_string (exchange_uri),
GNUNET_PQ_query_param_auto_from_type (merchant_pub),
GNUNET_PQ_query_param_auto_from_type (h_wire),
GNUNET_PQ_query_param_absolute_time (×tamp),
GNUNET_PQ_query_param_absolute_time (&refund),
TALER_PQ_query_param_amount (total_amount),
GNUNET_PQ_query_param_end
};
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Storing transaction with h_contract_terms '%s', merchant_pub '%s'.\n",
GNUNET_h2s (h_contract_terms),
TALER_B2S (merchant_pub));
result = GNUNET_PQ_exec_prepared (pg->conn,
"insert_transaction",
params);
if (PGRES_COMMAND_OK != PQresultStatus (result))
{
ret = GNUNET_SYSERR;
BREAK_DB_ERR (result);
}
else
{
ret = GNUNET_OK;
}
PQclear (result);
return ret;
}
/**
* Insert payment confirmation from the exchange into the database.
*
* @param cls closure
* @param order_id identificator of the proposal associated with this revenue
* @param merchant_pub merchant's public key
* @param coin_pub public key of the coin
* @param amount_with_fee amount the exchange will deposit for this coin
* @param deposit_fee fee the exchange will charge for this coin
* @param refund_fee fee the exchange will charge for refunding this coin
* @param signkey_pub public key used by the exchange for @a exchange_proof
* @param exchange_proof proof from exchange that coin was accepted
* @return #GNUNET_OK on success, #GNUNET_SYSERR upon error
*/
static int
postgres_store_deposit (void *cls,
const struct GNUNET_HashCode *h_contract_terms,
const struct TALER_MerchantPublicKeyP *merchant_pub,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_Amount *amount_with_fee,
const struct TALER_Amount *deposit_fee,
const struct TALER_Amount *refund_fee,
const struct TALER_ExchangePublicKeyP *signkey_pub,
const json_t *exchange_proof)
{
struct PostgresClosure *pg = cls;
PGresult *result;
int ret;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
GNUNET_PQ_query_param_auto_from_type (merchant_pub),
GNUNET_PQ_query_param_auto_from_type (coin_pub),
TALER_PQ_query_param_amount (amount_with_fee),
TALER_PQ_query_param_amount (deposit_fee),
TALER_PQ_query_param_amount (refund_fee),
GNUNET_PQ_query_param_auto_from_type (signkey_pub),
TALER_PQ_query_param_json (exchange_proof),
GNUNET_PQ_query_param_end
};
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"storing payment for h_contract_terms '%s'\n",
GNUNET_h2s (h_contract_terms));
result = GNUNET_PQ_exec_prepared (pg->conn,
"insert_deposit",
params);
if (PGRES_COMMAND_OK != PQresultStatus (result))
{
ret = GNUNET_SYSERR;
BREAK_DB_ERR (result);
}
else
{
ret = GNUNET_OK;
}
PQclear (result);
return ret;
}
/**
* Insert mapping of @a coin_pub and @a h_contract_terms to
* corresponding @a wtid.
*
* @param cls closure
* @param h_contract_terms hashcode of the proposal data paid by @a coin_pub
* @param coin_pub public key of the coin
* @param wtid identifier of the wire transfer in which the exchange
* send us the money for the coin deposit
* @return #GNUNET_OK on success, #GNUNET_SYSERR upon error
*/
static int
postgres_store_coin_to_transfer (void *cls,
const struct GNUNET_HashCode *h_contract_terms,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_WireTransferIdentifierRawP *wtid)
{
struct PostgresClosure *pg = cls;
PGresult *result;
int ret;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
GNUNET_PQ_query_param_auto_from_type (coin_pub),
GNUNET_PQ_query_param_auto_from_type (wtid),
GNUNET_PQ_query_param_end
};
result = GNUNET_PQ_exec_prepared (pg->conn,
"insert_transfer",
params);
if (PGRES_COMMAND_OK != PQresultStatus (result))
{
ret = GNUNET_SYSERR;
BREAK_DB_ERR (result);
}
else
{
ret = GNUNET_OK;
}
PQclear (result);
return ret;
}
/**
* Insert wire transfer confirmation from the exchange into the database.
*
* @param cls closure
* @param exchange_uri URI of the exchange
* @param wtid identifier of the wire transfer
* @param execution_time when was @a wtid executed
* @param signkey_pub public key used by the exchange for @a exchange_proof
* @param exchange_proof proof from exchange about what the deposit was for
* @return #GNUNET_OK on success, #GNUNET_SYSERR upon error
*/
static int
postgres_store_transfer_to_proof (void *cls,
const char *exchange_uri,
const struct TALER_WireTransferIdentifierRawP *wtid,
struct GNUNET_TIME_Absolute execution_time,
const struct TALER_ExchangePublicKeyP *signkey_pub,
const json_t *exchange_proof)
{
struct PostgresClosure *pg = cls;
PGresult *result;
int ret;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (exchange_uri),
GNUNET_PQ_query_param_auto_from_type (wtid),
GNUNET_PQ_query_param_absolute_time (&execution_time),
GNUNET_PQ_query_param_auto_from_type (signkey_pub),
TALER_PQ_query_param_json (exchange_proof),
GNUNET_PQ_query_param_end
};
result = GNUNET_PQ_exec_prepared (pg->conn,
"insert_proof",
params);
if (PGRES_COMMAND_OK != PQresultStatus (result))
{
ret = GNUNET_SYSERR;
BREAK_DB_ERR (result);
}
else
{
ret = GNUNET_OK;
}
PQclear (result);
return ret;
}
/**
* Lookup for a proposal, respecting the signature used by the
* /history's db methods.
*
* @param cls db plugin handle
* @param order_id order id used to search for the proposal data
* @param merchant_pub public key of the merchant using this method
* @param cb the callback
* @param cb_cls closure to pass to the callback
* @return GNUNET_YES, GNUNET_NO, GNUNET_SYSERR according to the
* query being successful, unsuccessful, or generated errors.
*/
static int
postgres_find_contract_terms_history (void *cls,
const char *order_id,
const struct TALER_MerchantPublicKeyP *merchant_pub,
TALER_MERCHANTDB_ProposalDataCallback cb,
void *cb_cls)
{
struct PostgresClosure *pg = cls;
PGresult *result;
unsigned int i;
json_t *contract_terms;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (order_id),
GNUNET_PQ_query_param_auto_from_type (merchant_pub),
GNUNET_PQ_query_param_end
};
result = GNUNET_PQ_exec_prepared (pg->conn,
"find_contract_terms",
params);
i = PQntuples (result);
if (1 < i)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Mupltiple proposal data share the same hashcode.\n");
return GNUNET_SYSERR;
}
if (0 == i)
return GNUNET_NO;
struct GNUNET_PQ_ResultSpec rs[] = {
TALER_PQ_result_spec_json ("contract_terms",
&contract_terms),
GNUNET_PQ_result_spec_end
};
if (GNUNET_OK !=
GNUNET_PQ_extract_result (result,
rs,
0))
{
GNUNET_break (0);
PQclear (result);
return GNUNET_SYSERR;
}
cb (cb_cls,
order_id,
0,
contract_terms);
PQclear (result);
return GNUNET_OK;
}
/**
* Return proposals whose timestamp are older than `date`.
* Among those proposals, only those ones being between the
* start-th and (start-nrows)-th record are returned. The rows
* are sorted having the youngest first.
*
* @param cls our plugin handle.
* @param date only results older than this date are returned.
* @param merchant_pub instance's public key; only rows related to this
* instance are returned.
* @param start only rows with serial id less than start are returned.
* In other words, you lower `start` to get older records. The tipical
* usage is to firstly call `find_contract_terms_by_date`, so that you get
* the `nrows` youngest records. The oldest of those records will tell you
* from which timestamp and `start` you can query the DB in order to get
* furtherly older records, and so on. Alternatively, you can use always
* the same timestamp and just go behind in history by tuning `start`.
* @param nrows only nrows rows are returned.
* @param future if set to GNUNET_YES, retrieves rows younger than `date`.
* This is tipically used to show live updates on the merchant's backoffice
* Web interface.
* @param cb function to call with transaction data, can be NULL.
* @param cb_cls closure for @a cb
* @return numer of found tuples, #GNUNET_SYSERR upon error
*/
static int
postgres_find_contract_terms_by_date_and_range (void *cls,
struct GNUNET_TIME_Absolute date,
const struct TALER_MerchantPublicKeyP *merchant_pub,
unsigned int start,
unsigned int nrows,
unsigned int future,
TALER_MERCHANTDB_ProposalDataCallback cb,
void *cb_cls)
{
uint64_t s64 = start;
uint64_t r64 = nrows;
struct PostgresClosure *pg = cls;
PGresult *result;
unsigned int n;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_absolute_time (&date),
GNUNET_PQ_query_param_auto_from_type (merchant_pub),
GNUNET_PQ_query_param_uint64 (&s64),
GNUNET_PQ_query_param_uint64 (&r64),
GNUNET_PQ_query_param_end
};
if (GNUNET_YES == future)
result = GNUNET_PQ_exec_prepared (pg->conn,
"find_contract_terms_by_date_and_range_future",
params);
else
result = GNUNET_PQ_exec_prepared (pg->conn,
"find_contract_terms_by_date_and_range",
params);
if (PGRES_TUPLES_OK != PQresultStatus (result))
{
BREAK_DB_ERR (result);
PQclear (result);
return GNUNET_SYSERR;
}
if ( (0 == (n = PQntuples (result))) ||
(NULL == cb) )
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"No records found.\n");
PQclear (result);
return n;
}
for (unsigned int i = 0; i < n; i++)
{
char *order_id;
json_t *contract_terms;
uint64_t row_id;
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_string ("order_id",
&order_id),
TALER_PQ_result_spec_json ("contract_terms",
&contract_terms),
GNUNET_PQ_result_spec_uint64 ("row_id",
&row_id),
GNUNET_PQ_result_spec_end
};
if (GNUNET_OK !=
GNUNET_PQ_extract_result (result,
rs,
i))
{
GNUNET_break (0);
PQclear (result);
return GNUNET_SYSERR;
}
cb (cb_cls,
order_id,
row_id,
contract_terms);
GNUNET_PQ_cleanup_result (rs);
}
PQclear (result);
return n;
}
/**
* Return proposals whose timestamp are older than `date`.
* The rows are sorted having the youngest first.
*
* @param cls our plugin handle.
* @param date only results older than this date are returned.
* @param merchant_pub instance's public key; only rows related to this
* instance are returned.
* @param nrows at most nrows rows are returned.
* @param cb function to call with transaction data, can be NULL.
* @param cb_cls closure for @a cb
* @return numer of found tuples, #GNUNET_SYSERR upon error
*/
static int
postgres_find_contract_terms_by_date (void *cls,
struct GNUNET_TIME_Absolute date,
const struct TALER_MerchantPublicKeyP *merchant_pub,
unsigned int nrows,
TALER_MERCHANTDB_ProposalDataCallback cb,
void *cb_cls)
{
uint64_t r64 = nrows;
struct PostgresClosure *pg = cls;
PGresult *result;
unsigned int n;
unsigned int i;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_absolute_time (&date),
GNUNET_PQ_query_param_auto_from_type (merchant_pub),
GNUNET_PQ_query_param_uint64 (&r64),
GNUNET_PQ_query_param_end
};
result = GNUNET_PQ_exec_prepared (pg->conn,
"find_contract_terms_by_date",
params);
if (PGRES_TUPLES_OK != PQresultStatus (result))
{
BREAK_DB_ERR (result);
PQclear (result);
return GNUNET_SYSERR;
}
if (0 == (n = PQntuples (result)) || NULL == cb)
{
PQclear (result);
return n;
}
for (i = 0; i < n; i++)
{
char *order_id;
json_t *contract_terms;
uint64_t row_id;
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_string ("order_id",
&order_id),
TALER_PQ_result_spec_json ("contract_terms",
&contract_terms),
GNUNET_PQ_result_spec_uint64 ("row_id",
&row_id),
GNUNET_PQ_result_spec_end
};
if (GNUNET_OK !=
GNUNET_PQ_extract_result (result,
rs,
i))
{
GNUNET_break (0);
PQclear (result);
return GNUNET_SYSERR;
}
cb (cb_cls,
order_id,
row_id,
contract_terms);
GNUNET_PQ_cleanup_result (rs);
}
PQclear (result);
return n;
}
/**
* Find information about a transaction.
*
* @param cls our plugin handle
* @param h_contract_terms value used to perform the lookup
* @param merchant_pub merchant's public key
* @param cb function to call with transaction data
* @param cb_cls closure for @a cb
* @return #GNUNET_OK if found, #GNUNET_NO if not, #GNUNET_SYSERR
* upon error
*/
static int
postgres_find_transaction (void *cls,
const struct GNUNET_HashCode *h_contract_terms,
const struct TALER_MerchantPublicKeyP *merchant_pub,
TALER_MERCHANTDB_TransactionCallback cb,
void *cb_cls)
{
struct PostgresClosure *pg = cls;
PGresult *result;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
GNUNET_PQ_query_param_auto_from_type (merchant_pub),
GNUNET_PQ_query_param_end
};
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Finding transaction for h_contract_terms '%s', merchant_pub: '%s'.\n",
GNUNET_h2s (h_contract_terms),
TALER_B2S (merchant_pub));
result = GNUNET_PQ_exec_prepared (pg->conn,
"find_transaction",
params);
if (PGRES_TUPLES_OK != PQresultStatus (result))
{
BREAK_DB_ERR (result);
PQclear (result);
return GNUNET_SYSERR;
}
if (0 == PQntuples (result))
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Could NOT find transaction for h_contract_terms '%s'\n",
GNUNET_h2s (h_contract_terms));
PQclear (result);
return GNUNET_NO;
}
if (1 != PQntuples (result))
{
GNUNET_break (0);
PQclear (result);
return GNUNET_SYSERR;
}
{
char *exchange_uri;
struct GNUNET_HashCode h_wire;
struct GNUNET_TIME_Absolute timestamp;
struct GNUNET_TIME_Absolute refund_deadline;
struct TALER_Amount total_amount;
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_string ("exchange_uri",
&exchange_uri),
GNUNET_PQ_result_spec_auto_from_type ("h_wire",
&h_wire),
GNUNET_PQ_result_spec_absolute_time ("timestamp",
×tamp),
GNUNET_PQ_result_spec_absolute_time ("refund_deadline",
&refund_deadline),
TALER_PQ_result_spec_amount ("total_amount",
&total_amount),
GNUNET_PQ_result_spec_end
};
if (GNUNET_OK !=
GNUNET_PQ_extract_result (result,
rs,
0))
{
GNUNET_break (0);
PQclear (result);
return GNUNET_SYSERR;
}
cb (cb_cls,
merchant_pub,
exchange_uri,
h_contract_terms,
&h_wire,
timestamp,
refund_deadline,
&total_amount);
GNUNET_PQ_cleanup_result (rs);
}
PQclear (result);
return GNUNET_OK;
}
/**
* Lookup information about coin payments by proposal data hash
* (and @a merchant_pub)
*
* @param cls closure
* @param h_contract_terms key for the search
* @param merchant_pub merchant's public key
* @param cb function to call with payment data
* @param cb_cls closure for @a cb
* @return #GNUNET_OK on success, #GNUNET_NO if transaction Id is unknown,
* #GNUNET_SYSERR on hard errors
*/
static int
postgres_find_payments (void *cls,
const struct GNUNET_HashCode *h_contract_terms,
const struct TALER_MerchantPublicKeyP *merchant_pub,
TALER_MERCHANTDB_CoinDepositCallback cb,
void *cb_cls)
{
struct PostgresClosure *pg = cls;
PGresult *result;
unsigned int i;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
GNUNET_PQ_query_param_auto_from_type (merchant_pub),
GNUNET_PQ_query_param_end
};
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"finding payment for h_contract_terms '%s'\n",
GNUNET_h2s (h_contract_terms));
result = GNUNET_PQ_exec_prepared (pg->conn,
"find_deposits",
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;
}
for (i=0;iconn,
"find_deposits_by_hash_and_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;
}
for (i=0;iconn,
"find_transfers_by_hash",
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;
}
for (i=0;iconn,
"find_deposits_by_wtid",
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;
}
for (i=0;iconn,
"find_refunds_from_contract_terms_hash",
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;
}
for (i=0;iconn,
"find_proof_by_wtid",
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_break (0);
PQclear (result);
return GNUNET_SYSERR;
}
{
json_t *proof;
struct GNUNET_PQ_ResultSpec rs[] = {
TALER_PQ_result_spec_json ("proof",
&proof),
GNUNET_PQ_result_spec_end
};
if (GNUNET_OK !=
GNUNET_PQ_extract_result (result,
rs,
0))
{
GNUNET_break (0);
PQclear (result);
return GNUNET_SYSERR;
}
cb (cb_cls,
proof);
GNUNET_PQ_cleanup_result (rs);
}
PQclear (result);
return GNUNET_OK;
}
/**
* Initialize Postgres database subsystem.
*
* @param cls a configuration instance
* @return NULL on error, otherwise a `struct TALER_MERCHANTDB_Plugin`
*/
void *
libtaler_plugin_merchantdb_postgres_init (void *cls)
{
struct GNUNET_CONFIGURATION_Handle *cfg = cls;
struct PostgresClosure *pg;
struct TALER_MERCHANTDB_Plugin *plugin;
const char *ec;
pg = GNUNET_new (struct PostgresClosure);
ec = getenv ("TALER_MERCHANTDB_POSTGRES_CONFIG");
if (NULL != ec)
{
GNUNET_CONFIGURATION_set_value_string (cfg,
"merchantdb-postgres",
"CONFIG",
ec);
}
else
{
if (GNUNET_OK !=
GNUNET_CONFIGURATION_have_value (cfg,
"merchantdb-postgres",
"CONFIG"))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"merchantdb-postgres",
"CONFIG");
return NULL;
}
}
pg->conn = GNUNET_PQ_connect_with_cfg (cfg,
"merchantdb-postgres");
if (NULL == pg->conn)
{
GNUNET_free (pg);
return NULL;
}
plugin = GNUNET_new (struct TALER_MERCHANTDB_Plugin);
plugin->cls = pg;
plugin->drop_tables = &postgres_drop_tables;
plugin->initialize = &postgres_initialize;
plugin->store_transaction = &postgres_store_transaction;
plugin->store_deposit = &postgres_store_deposit;
plugin->store_coin_to_transfer = &postgres_store_coin_to_transfer;
plugin->store_transfer_to_proof = &postgres_store_transfer_to_proof;
plugin->find_transaction = &postgres_find_transaction;
plugin->find_payments_by_hash_and_coin = &postgres_find_payments_by_hash_and_coin;
plugin->find_payments = &postgres_find_payments;
plugin->find_transfers_by_hash = &postgres_find_transfers_by_hash;
plugin->find_deposits_by_wtid = &postgres_find_deposits_by_wtid;
plugin->find_proof_by_wtid = &postgres_find_proof_by_wtid;
plugin->insert_contract_terms = &postgres_insert_contract_terms;
plugin->find_contract_terms = &postgres_find_contract_terms;
plugin->find_contract_terms_history = &postgres_find_contract_terms_history;
plugin->find_contract_terms_by_date = &postgres_find_contract_terms_by_date;
plugin->find_contract_terms_by_date_and_range = &postgres_find_contract_terms_by_date_and_range;
plugin->find_contract_terms_from_hash = &postgres_find_contract_terms_from_hash;
plugin->get_refunds_from_contract_terms_hash = &postgres_get_refunds_from_contract_terms_hash;
plugin->increase_refund_for_contract = postgres_increase_refund_for_contract;
return plugin;
}
/**
* Shutdown Postgres database subsystem.
*
* @param cls a `struct TALER_MERCHANTDB_Plugin`
* @return NULL (always)
*/
void *
libtaler_plugin_merchantdb_postgres_done (void *cls)
{
struct TALER_MERCHANTDB_Plugin *plugin = cls;
struct PostgresClosure *pg = plugin->cls;
PQfinish (pg->conn);
GNUNET_free (pg);
GNUNET_free (plugin);
return NULL;
}
/* end of plugin_merchantdb_postgres.c */