merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

commit 37bd6899d24b234534926af5e40b112eb4565baa
parent 6eb1f758ead6eca02803cadf3017101ebdc338e0
Author: Christian Grothoff <christian@grothoff.org>
Date:   Fri, 11 Oct 2019 23:35:49 +0200

fix #5733 by migrating to new GNUnet PQ API

Diffstat:
Msrc/backend/taler-merchant-httpd.c | 8--------
Msrc/backenddb/plugin_merchantdb_postgres.c | 2000+++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/backenddb/test_merchantdb.c | 4++--
Msrc/include/taler_merchantdb_plugin.h | 9---------
Msrc/merchant-tools/taler-merchant-dbinit.c | 10++--------
5 files changed, 987 insertions(+), 1044 deletions(-)

diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c @@ -1452,14 +1452,6 @@ run (void *cls, GNUNET_SCHEDULER_shutdown (); return; } - if (GNUNET_OK != - db->initialize (db->cls)) - { - GNUNET_break (0); - GNUNET_SCHEDULER_shutdown (); - return; - } - { const char *choices[] = {"tcp", diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c @@ -54,6 +54,8 @@ #define TALER_PQ_RESULT_SPEC_AMOUNT_NBO(field, \ amountp) TALER_PQ_result_spec_amount_nbo ( \ field,pg->currency,amountp) + + /** * Wrapper macro to add the currency from the plugin's state * when fetching amounts from the database. @@ -75,7 +77,7 @@ struct PostgresClosure /** * Postgres connection handle. */ - PGconn *conn; + struct GNUNET_PQ_Context *conn; /** * Which currency do we deal in? @@ -140,698 +142,83 @@ postgres_drop_tables (void *cls) /** - * Initialize merchant tables + * Check that the database connection is still up. * - * @param cls closure our `struct Plugin` - * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + * @param pg connection to check + */ +static void +check_connection (struct PostgresClosure *pg) +{ + GNUNET_PQ_reconnect_if_down (pg->conn); +} + + +/** + * Do a pre-flight check that we are not in an uncommitted transaction. + * If we are, try to commit the previous transaction and output a warning. + * Does not return anything, as we will continue regardless of the outcome. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + */ +static void +postgres_preflight (void *cls) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_ExecuteStatement es[] = { + GNUNET_PQ_make_execute ("COMMIT"), + GNUNET_PQ_EXECUTE_STATEMENT_END + }; + + if (NULL == pg->transaction_name) + return; /* all good */ + if (GNUNET_OK == + GNUNET_PQ_exec_statements (pg->conn, + es)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "BUG: Preflight check committed transaction `%s'!\n", + pg->transaction_name); + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "BUG: Preflight check failed to commit transaction `%s'!\n", + pg->transaction_name); + } + pg->transaction_name = NULL; +} + + +/** + * Start a transaction. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param name unique name identifying the transaction (for debugging), + * must point to a constant + * @return #GNUNET_OK on success */ static int -postgres_initialize (void *cls) +postgres_start (void *cls, + const char *name) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_ExecuteStatement es[] = { - /* Orders created by the frontend, not signed or given a nonce yet. - The contract terms will change (nonce will be added) when moved to the - contract terms table */ - GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS merchant_orders (" - "order_id VARCHAR NOT NULL" - ",merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)" - ",contract_terms BYTEA NOT NULL" - ",timestamp INT8 NOT NULL" - ",PRIMARY KEY (order_id, merchant_pub)" - ");"), - /* Offers we made to customers */ - GNUNET_PQ_make_execute ( - "CREATE TABLE IF NOT EXISTS merchant_contract_terms (" - "order_id VARCHAR NOT NULL" - ",merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)" - ",contract_terms BYTEA NOT NULL" - ",h_contract_terms BYTEA NOT NULL CHECK (LENGTH(h_contract_terms)=64)" - ",timestamp INT8 NOT NULL" - ",row_id BIGSERIAL UNIQUE" - ",paid boolean DEFAULT FALSE NOT NULL" - ",PRIMARY KEY (order_id, merchant_pub)" - ",UNIQUE (h_contract_terms, merchant_pub)" - ");"), - /* Table with the proofs for each coin we deposited at the exchange */ - 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)" - ",coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)" - ",exchange_url VARCHAR NOT NULL" - ",amount_with_fee_val INT8 NOT NULL" - ",amount_with_fee_frac INT4 NOT NULL" - ",deposit_fee_val INT8 NOT NULL" - ",deposit_fee_frac INT4 NOT NULL" - ",refund_fee_val INT8 NOT NULL" - ",refund_fee_frac INT4 NOT NULL" - ",wire_fee_val INT8 NOT NULL" - ",wire_fee_frac INT4 NOT NULL" - ",signkey_pub BYTEA NOT NULL CHECK (LENGTH(signkey_pub)=32)" - ",exchange_proof BYTEA NOT NULL" - ",PRIMARY KEY (h_contract_terms, coin_pub)" - ",FOREIGN KEY (h_contract_terms, merchant_pub) REFERENCES merchant_contract_terms (h_contract_terms, merchant_pub)" - ");"), - GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS merchant_proofs (" - " exchange_url 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_url)" - ");"), - /* 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_make_execute ("CREATE TABLE IF NOT EXISTS exchange_wire_fees (" - " exchange_pub BYTEA NOT NULL CHECK (length(exchange_pub)=32)" - ",h_wire_method BYTEA NOT NULL CHECK (length(h_wire_method)=64)" - ",wire_fee_val INT8 NOT NULL" - ",wire_fee_frac INT4 NOT NULL" - ",closing_fee_val INT8 NOT NULL" - ",closing_fee_frac INT4 NOT NULL" - ",start_date INT8 NOT NULL" - ",end_date INT8 NOT NULL" - ",exchange_sig BYTEA NOT NULL CHECK (length(exchange_sig)=64)" - ",PRIMARY KEY (exchange_pub,h_wire_method,start_date,end_date)" - ");"), - GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS merchant_refunds (" - " rtransaction_id BIGSERIAL UNIQUE" - ",merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)" - ",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 INT4 NOT NULL" - ",refund_fee_val INT8 NOT NULL" - ",refund_fee_frac INT4 NOT NULL" - ");"), - /* balances of the reserves available for tips */ - GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS merchant_tip_reserves (" - " reserve_priv BYTEA NOT NULL CHECK (LENGTH(reserve_priv)=32)" - ",expiration INT8 NOT NULL" - ",balance_val INT8 NOT NULL" - ",balance_frac INT4 NOT NULL" - ",PRIMARY KEY (reserve_priv)" - ");"), - /* table where we remember when tipping reserves where established / enabled */ - GNUNET_PQ_make_execute ( - "CREATE TABLE IF NOT EXISTS merchant_tip_reserve_credits (" - " reserve_priv BYTEA NOT NULL CHECK (LENGTH(reserve_priv)=32)" - ",credit_uuid BYTEA UNIQUE NOT NULL CHECK (LENGTH(credit_uuid)=64)" - ",timestamp INT8 NOT NULL" - ",amount_val INT8 NOT NULL" - ",amount_frac INT4 NOT NULL" - ",PRIMARY KEY (credit_uuid)" - ");"), - /* tips that have been authorized */ - GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS merchant_tips (" - " reserve_priv BYTEA NOT NULL CHECK (LENGTH(reserve_priv)=32)" - ",tip_id BYTEA NOT NULL CHECK (LENGTH(tip_id)=64)" - ",exchange_url VARCHAR NOT NULL" - ",justification VARCHAR NOT NULL" - ",extra BYTEA NOT NULL" - ",timestamp INT8 NOT NULL" - ",amount_val INT8 NOT NULL" /* overall tip amount */ - ",amount_frac INT4 NOT NULL" - ",left_val INT8 NOT NULL" /* tip amount not yet picked up */ - ",left_frac INT4 NOT NULL" - ",PRIMARY KEY (tip_id)" - ");"), - /* tips that have been picked up */ - GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS merchant_tip_pickups (" - " tip_id BYTEA NOT NULL REFERENCES merchant_tips (tip_id) ON DELETE CASCADE" - ",pickup_id BYTEA NOT NULL CHECK (LENGTH(pickup_id)=64)" - ",amount_val INT8 NOT NULL" - ",amount_frac INT4 NOT NULL" - ",PRIMARY KEY (pickup_id)" - ");"), - /* sessions and their order_id/fulfillment_url mapping */ - GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS merchant_session_info (" - " session_id VARCHAR NOT NULL" - ",fulfillment_url VARCHAR NOT NULL" - ",order_id VARCHAR NOT NULL" - ",merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)" - ",timestamp INT8 NOT NULL" - ",PRIMARY KEY (session_id, fulfillment_url, merchant_pub)" - ",UNIQUE (session_id, fulfillment_url, order_id, merchant_pub)" - ");"), + GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL SERIALIZABLE"), GNUNET_PQ_EXECUTE_STATEMENT_END }; - struct GNUNET_PQ_PreparedStatement ps[] = { - GNUNET_PQ_make_prepare ("insert_deposit", - "INSERT INTO merchant_deposits" - "(h_contract_terms" - ",merchant_pub" - ",coin_pub" - ",exchange_url" - ",amount_with_fee_val" - ",amount_with_fee_frac" - ",deposit_fee_val" - ",deposit_fee_frac" - ",refund_fee_val" - ",refund_fee_frac" - ",wire_fee_val" - ",wire_fee_frac" - ",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_refund", - "INSERT INTO merchant_refunds" - "(merchant_pub" - ",h_contract_terms" - ",coin_pub" - ",reason" - ",refund_amount_val" - ",refund_amount_frac" - ",refund_fee_val" - ",refund_fee_frac" - ") VALUES" - "($1, $2, $3, $4, $5, $6, $7, $8)", - 8), - GNUNET_PQ_make_prepare ("insert_proof", - "INSERT INTO merchant_proofs" - "(exchange_url" - ",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)", - 5), - GNUNET_PQ_make_prepare ("insert_order", - "INSERT INTO merchant_orders" - "(order_id" - ",merchant_pub" - ",timestamp" - ",contract_terms)" - " VALUES " - "($1, $2, $3, $4)", - 4), - GNUNET_PQ_make_prepare ("insert_session_info", - "INSERT INTO merchant_session_info" - "(session_id" - ",fulfillment_url" - ",order_id" - ",merchant_pub" - ",timestamp)" - " VALUES " - "($1, $2, $3, $4, $5)", - 5), - GNUNET_PQ_make_prepare ("mark_proposal_paid", - "UPDATE merchant_contract_terms SET" - " paid=TRUE" - " WHERE h_contract_terms=$1" - " AND merchant_pub=$2", - 2), - GNUNET_PQ_make_prepare ("insert_wire_fee", - "INSERT INTO exchange_wire_fees" - "(exchange_pub" - ",h_wire_method" - ",wire_fee_val" - ",wire_fee_frac" - ",closing_fee_val" - ",closing_fee_frac" - ",start_date" - ",end_date" - ",exchange_sig)" - " VALUES " - "($1, $2, $3, $4, $5, $6, $7, $8, $9)", - 9), - GNUNET_PQ_make_prepare ("lookup_wire_fee", - "SELECT" - " wire_fee_val" - ",wire_fee_frac" - ",closing_fee_val" - ",closing_fee_frac" - ",start_date" - ",end_date" - ",exchange_sig" - " FROM exchange_wire_fees" - " WHERE exchange_pub=$1" - " AND h_wire_method=$2" - " AND start_date <= $3" - " AND end_date > $3", - 1), - 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_paid_contract_terms_from_hash", - "SELECT" - " contract_terms" - " FROM merchant_contract_terms" - " WHERE h_contract_terms=$1" - " AND merchant_pub=$2" - " AND paid=TRUE", - 2), - GNUNET_PQ_make_prepare ("end_transaction", - "COMMIT", - 0), - - GNUNET_PQ_make_prepare ("find_refunds", - "SELECT" - " refund_amount_val" - ",refund_amount_frac" - " FROM merchant_refunds" - " WHERE coin_pub=$1", - 1), - GNUNET_PQ_make_prepare ("find_contract_terms_history", - "SELECT" - " contract_terms" - " FROM merchant_contract_terms" - " WHERE" - " order_id=$1" - " AND merchant_pub=$2" - " AND paid=TRUE", - 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_order", - "SELECT" - " contract_terms" - " FROM merchant_orders" - " WHERE" - " order_id=$1" - " AND merchant_pub=$2", - 2), - GNUNET_PQ_make_prepare ("find_session_info", - "SELECT" - " order_id" - " FROM merchant_session_info" - " WHERE" - " fulfillment_url=$1" - " AND session_id=$2" - " AND merchant_pub=$3", - 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" - " AND paid=TRUE" - " ORDER BY row_id DESC, timestamp DESC" - " LIMIT $3", - 3), - GNUNET_PQ_make_prepare ("find_refunds_from_contract_terms_hash", - "SELECT" - " coin_pub" - ",rtransaction_id" - ",refund_amount_val" - ",refund_amount_frac" - ",refund_fee_val" - ",refund_fee_frac" - ",reason" - " FROM merchant_refunds" - " WHERE merchant_pub=$1" - " AND h_contract_terms=$2", - 2), - GNUNET_PQ_make_prepare ("find_contract_terms_by_date_and_range_asc", - "SELECT" - " contract_terms" - ",order_id" - ",row_id" - " FROM merchant_contract_terms" - " WHERE" - " timestamp>$1" - " AND merchant_pub=$2" - " AND row_id>$3" - " AND paid=TRUE" - " ORDER BY row_id ASC, timestamp ASC" - " LIMIT $4", - 4), - 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" - " AND paid=TRUE" - " ORDER BY row_id DESC, timestamp DESC" - " LIMIT $4", - 4), - GNUNET_PQ_make_prepare ("find_contract_terms_by_date_and_range_past_asc", - "SELECT" - " contract_terms" - ",order_id" - ",row_id" - " FROM merchant_contract_terms" - " WHERE" - " timestamp<$1" - " AND merchant_pub=$2" - " AND row_id<$3" - " AND paid=TRUE" - " ORDER BY row_id ASC, timestamp ASC" - " LIMIT $4", - 4), - GNUNET_PQ_make_prepare ("find_contract_terms_by_date_and_range_past", - "SELECT" - " contract_terms" - ",order_id" - ",row_id" - " FROM merchant_contract_terms" - " WHERE" - " timestamp<$1" - " AND merchant_pub=$2" - " AND row_id<$3" - " AND paid=TRUE" - " ORDER BY row_id DESC, timestamp DESC" - " LIMIT $4", - 4), - GNUNET_PQ_make_prepare ("find_deposits", - "SELECT" - " coin_pub" - ",exchange_url" - ",amount_with_fee_val" - ",amount_with_fee_frac" - ",deposit_fee_val" - ",deposit_fee_frac" - ",refund_fee_val" - ",refund_fee_frac" - ",wire_fee_val" - ",wire_fee_frac" - ",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" - ",deposit_fee_val" - ",deposit_fee_frac" - ",refund_fee_val" - ",refund_fee_frac" - ",wire_fee_val" - ",wire_fee_frac" - ",exchange_url" - ",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.deposit_fee_val" - ",merchant_deposits.deposit_fee_frac" - ",merchant_deposits.refund_fee_val" - ",merchant_deposits.refund_fee_frac" - ",merchant_deposits.wire_fee_val" - ",merchant_deposits.wire_fee_frac" - ",merchant_deposits.exchange_url" - ",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_url=$2", - 2), - GNUNET_PQ_make_prepare ("lookup_tip_reserve_balance", - "SELECT" - " expiration" - ",balance_val" - ",balance_frac" - " FROM merchant_tip_reserves" - " WHERE reserve_priv=$1", - 1), - GNUNET_PQ_make_prepare ("find_tip_authorizations", - "SELECT" - " amount_val" - ",amount_frac" - ",justification" - ",extra" - ",tip_id" - " FROM merchant_tips" - " WHERE reserve_priv=$1", - 1), - GNUNET_PQ_make_prepare ("update_tip_reserve_balance", - "UPDATE merchant_tip_reserves SET" - " expiration=$2" - ",balance_val=$3" - ",balance_frac=$4" - " WHERE reserve_priv=$1", - 4), - GNUNET_PQ_make_prepare ("insert_tip_reserve_balance", - "INSERT INTO merchant_tip_reserves" - "(reserve_priv" - ",expiration" - ",balance_val" - ",balance_frac" - ") VALUES " - "($1, $2, $3, $4)", - 4), - GNUNET_PQ_make_prepare ("insert_tip_justification", - "INSERT INTO merchant_tips" - "(reserve_priv" - ",tip_id" - ",exchange_url" - ",justification" - ",extra" - ",timestamp" - ",amount_val" - ",amount_frac" - ",left_val" - ",left_frac" - ") VALUES " - "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", - 10), - GNUNET_PQ_make_prepare ("lookup_reserve_by_tip_id", - "SELECT" - " reserve_priv" - ",left_val" - ",left_frac" - " FROM merchant_tips" - " WHERE tip_id=$1", - 1), - GNUNET_PQ_make_prepare ("lookup_amount_by_pickup", - "SELECT" - " amount_val" - ",amount_frac" - " FROM merchant_tip_pickups" - " WHERE pickup_id=$1" - " AND tip_id=$2", - 2), - GNUNET_PQ_make_prepare ("find_tip_by_id", - "SELECT" - " exchange_url" - ",extra" - ",timestamp" - ",amount_val" - ",amount_frac" - ",left_val" - ",left_frac" - " FROM merchant_tips" - " WHERE tip_id=$1", - 1), - GNUNET_PQ_make_prepare ("update_tip_balance", - "UPDATE merchant_tips SET" - " left_val=$2" - ",left_frac=$3" - " WHERE tip_id=$1", - 3), - GNUNET_PQ_make_prepare ("insert_pickup_id", - "INSERT INTO merchant_tip_pickups" - "(tip_id" - ",pickup_id" - ",amount_val" - ",amount_frac" - ") VALUES " - "($1, $2, $3, $4)", - 4), - GNUNET_PQ_make_prepare ("insert_tip_credit_uuid", - "INSERT INTO merchant_tip_reserve_credits" - "(reserve_priv" - ",credit_uuid" - ",timestamp" - ",amount_val" - ",amount_frac" - ") VALUES " - "($1, $2, $3, $4, $5)", - 5), - GNUNET_PQ_make_prepare ("lookup_tip_credit_uuid", - "SELECT 1 " - "FROM merchant_tip_reserve_credits " - "WHERE credit_uuid=$1 AND reserve_priv=$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; -} - - -/** - * Check that the database connection is still up. - * - * @param pg connection to check - */ -static void -check_connection (struct PostgresClosure *pg) -{ - if (CONNECTION_BAD != PQstatus (pg->conn)) - return; - PQfinish (pg->conn); - pg->conn = GNUNET_PQ_connect_with_cfg (pg->cfg, - "merchantdb-postgres"); - GNUNET_break (NULL != pg->conn); - GNUNET_break (GNUNET_OK == - postgres_initialize (pg)); -} - - -/** - * Do a pre-flight check that we are not in an uncommitted transaction. - * If we are, try to commit the previous transaction and output a warning. - * Does not return anything, as we will continue regardless of the outcome. - * - * @param cls the `struct PostgresClosure` with the plugin-specific state - */ -static void -postgres_preflight (void *cls) -{ - struct PostgresClosure *pg = cls; - PGresult *result; - ExecStatusType status; - - if (NULL == pg->transaction_name) - return; /* all good */ - result = PQexec (pg->conn, - "COMMIT"); - status = PQresultStatus (result); - if (PGRES_COMMAND_OK == status) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "BUG: Preflight check committed transaction `%s'!\n", - pg->transaction_name); - } - else - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "BUG: Preflight check failed to commit transaction `%s'!\n", - pg->transaction_name); - } - pg->transaction_name = NULL; - PQclear (result); -} - - -/** - * Start a transaction. - * - * @param cls the `struct PostgresClosure` with the plugin-specific state - * @param name unique name identifying the transaction (for debugging), - * must point to a constant - * @return #GNUNET_OK on success - */ -static int -postgres_start (void *cls, - const char *name) -{ - struct PostgresClosure *pg = cls; - PGresult *result; - ExecStatusType ex; check_connection (pg); postgres_preflight (pg); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Starting merchant DB transaction\n"); - result = PQexec (pg->conn, - "START TRANSACTION ISOLATION LEVEL SERIALIZABLE"); - if (PGRES_COMMAND_OK != - (ex = PQresultStatus (result))) + if (GNUNET_OK != + GNUNET_PQ_exec_statements (pg->conn, + es)) { - TALER_LOG_ERROR ("Failed to start transaction (%s): %s\n", - PQresStatus (ex), - PQerrorMessage (pg->conn)); + TALER_LOG_ERROR ("Failed to start transaction\n"); GNUNET_break (0); - PQclear (result); return GNUNET_SYSERR; } - PQclear (result); pg->transaction_name = name; return GNUNET_OK; } @@ -847,15 +234,16 @@ static void postgres_rollback (void *cls) { struct PostgresClosure *pg = cls; - PGresult *result; + struct GNUNET_PQ_ExecuteStatement es[] = { + GNUNET_PQ_make_execute ("ROLLBACK"), + GNUNET_PQ_EXECUTE_STATEMENT_END + }; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Rolling back merchant DB transaction\n"); - result = PQexec (pg->conn, - "ROLLBACK"); - GNUNET_break (PGRES_COMMAND_OK == - PQresultStatus (result)); - PQclear (result); + GNUNET_break (GNUNET_OK == + GNUNET_PQ_exec_statements (pg->conn, + es)); pg->transaction_name = NULL; } @@ -3282,393 +2670,969 @@ postgres_authorize_tip_TR (void *cls, struct GNUNET_TIME_Absolute old_expiration; struct TALER_Amount old_balance; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_absolute_time ("expiration", - &old_expiration), - TALER_PQ_RESULT_SPEC_AMOUNT ("balance", - &old_balance), + GNUNET_PQ_result_spec_absolute_time ("expiration", + &old_expiration), + TALER_PQ_RESULT_SPEC_AMOUNT ("balance", + &old_balance), + GNUNET_PQ_result_spec_end + }; + enum GNUNET_DB_QueryStatus qs; + struct TALER_Amount new_balance; + unsigned int retries; + + retries = 0; + check_connection (pg); +RETRY: + if (MAX_RETRIES < ++retries) + return TALER_EC_TIP_AUTHORIZE_DB_SOFT_ERROR; + if (GNUNET_OK != + postgres_start (pg, + "authorize tip")) + { + GNUNET_break (0); + return TALER_EC_TIP_AUTHORIZE_DB_HARD_ERROR; + } + qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "lookup_tip_reserve_balance", + params, + rs); + if (0 >= qs) + { + /* reserve unknown */ + postgres_rollback (pg); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + goto RETRY; + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + return TALER_EC_TIP_AUTHORIZE_INSUFFICIENT_FUNDS; + return TALER_EC_TIP_AUTHORIZE_DB_HARD_ERROR; + } + if (0 == GNUNET_TIME_absolute_get_remaining (old_expiration).rel_value_us) + { + /* reserve expired, can't be used */ + postgres_rollback (pg); + return TALER_EC_TIP_AUTHORIZE_RESERVE_EXPIRED; + } + if (GNUNET_SYSERR == + TALER_amount_subtract (&new_balance, + &old_balance, + amount)) + { + /* insufficient funds left in reserve */ + postgres_rollback (pg); + return TALER_EC_TIP_AUTHORIZE_INSUFFICIENT_FUNDS; + } + /* Update reserve balance */ + { + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (reserve_priv), + GNUNET_PQ_query_param_absolute_time (&old_expiration), + TALER_PQ_query_param_amount (&new_balance), + GNUNET_PQ_query_param_end + }; + + qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, + "update_tip_reserve_balance", + params); + if (0 > qs) + { + postgres_rollback (pg); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + goto RETRY; + return TALER_EC_TIP_AUTHORIZE_DB_HARD_ERROR; + } + } + /* Generate and store tip ID */ + *expiration = old_expiration; + GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_STRONG, + tip_id); + { + struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (reserve_priv), + GNUNET_PQ_query_param_auto_from_type (tip_id), + GNUNET_PQ_query_param_string (exchange_url), + GNUNET_PQ_query_param_string (justification), + TALER_PQ_query_param_json (extra), + GNUNET_PQ_query_param_absolute_time (&now), + TALER_PQ_query_param_amount (amount), /* overall amount */ + TALER_PQ_query_param_amount (amount), /* amount left */ + GNUNET_PQ_query_param_end + }; + + (void) GNUNET_TIME_round_abs (&now); + qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, + "insert_tip_justification", + params); + if (0 > qs) + { + postgres_rollback (pg); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + goto RETRY; + return TALER_EC_TIP_AUTHORIZE_DB_HARD_ERROR; + } + } + qs = postgres_commit (pg); + if (0 <= qs) + return TALER_EC_NONE; /* success! */ + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + goto RETRY; + return TALER_EC_TIP_AUTHORIZE_DB_HARD_ERROR; +} + + +/** + * Find out tip authorization details associated with @a tip_id + * + * @param cls closure, typically a connection to the d + * @param tip_id the unique ID for the tip + * @param[out] exchange_url set to the URL of the exchange (unless NULL) + * @param[out] extra extra data to pass to the wallet (unless NULL) + * @param[out] amount set to the authorized amount (unless NULL) + * @param[out] amount_left set to the amount left (unless NULL) + * @param[out] timestamp set to the timestamp of the tip authorization (unless NULL) + * @return transaction status, usually + * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success + * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if @a credit_uuid already known + */ +static enum GNUNET_DB_QueryStatus +postgres_lookup_tip_by_id (void *cls, + const struct GNUNET_HashCode *tip_id, + char **exchange_url, + json_t **extra, + struct TALER_Amount *amount, + struct TALER_Amount *amount_left, + struct GNUNET_TIME_Absolute *timestamp) +{ + struct PostgresClosure *pg = cls; + char *res_exchange_url; + json_t *res_extra; + struct TALER_Amount res_amount; + struct TALER_Amount res_amount_left; + struct GNUNET_TIME_Absolute res_timestamp; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (tip_id), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_string ("exchange_url", + &res_exchange_url), + GNUNET_PQ_result_spec_absolute_time ("timestamp", + &res_timestamp), + TALER_PQ_result_spec_json ("extra", + &res_extra), + TALER_PQ_RESULT_SPEC_AMOUNT ("amount", + &res_amount), + TALER_PQ_RESULT_SPEC_AMOUNT ("left", + &res_amount_left), + GNUNET_PQ_result_spec_end + }; + enum GNUNET_DB_QueryStatus qs; + + qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "find_tip_by_id", + params, + rs); + if (0 >= qs) + { + if (NULL != exchange_url) + *exchange_url = NULL; + return qs; + } + if (NULL != exchange_url) + *exchange_url = strdup (res_exchange_url); + if (NULL != amount) + *amount = res_amount; + if (NULL != amount_left) + *amount_left = res_amount_left; + if (NULL != timestamp) + *timestamp = res_timestamp; + if (NULL != extra) + { + json_incref (res_extra); + *extra = res_extra; + } + GNUNET_PQ_cleanup_result (rs); + return qs; +} + + +/** + * Pickup a tip over @a amount using pickup id @a pickup_id. + * + * @param cls closure, typically a connection to the db + * @param amount how high is the amount picked up (with fees) + * @param tip_id the unique ID from the tip authorization + * @param pickup_id the unique ID identifying the pick up operation + * (to allow replays, hash over the coin envelope and denomination key) + * @param[out] reserve_priv which reserve key to use to sign + * @return taler error code + * #TALER_EC_TIP_PICKUP_ID_UNKNOWN if @a tip_id is unknown + * #TALER_EC_TIP_PICKUP_NO_FUNDS if @a tip_id has insufficient funds left + * #TALER_EC_TIP_PICKUP_DB_ERROR_HARD on hard database errors + * #TALER_EC_TIP_PICKUP_AMOUNT_CHANGED if @a amount is different for known @a pickup_id + * #TALER_EC_TIP_PICKUP_DB_ERROR_SOFT on soft database errors (client should retry) + * #TALER_EC_NONE upon success (@a reserve_priv was set) + */ +static enum TALER_ErrorCode +postgres_pickup_tip_TR (void *cls, + const struct TALER_Amount *amount, + const struct GNUNET_HashCode *tip_id, + const struct GNUNET_HashCode *pickup_id, + struct TALER_ReservePrivateKeyP *reserve_priv) +{ + struct PostgresClosure *pg = cls; + struct TALER_Amount left_amount; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (tip_id), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_auto_from_type ("reserve_priv", + reserve_priv), + TALER_PQ_RESULT_SPEC_AMOUNT ("left", + &left_amount), GNUNET_PQ_result_spec_end }; enum GNUNET_DB_QueryStatus qs; - struct TALER_Amount new_balance; unsigned int retries; retries = 0; check_connection (pg); RETRY: if (MAX_RETRIES < ++retries) - return TALER_EC_TIP_AUTHORIZE_DB_SOFT_ERROR; + return TALER_EC_TIP_PICKUP_DB_ERROR_SOFT; if (GNUNET_OK != postgres_start (pg, - "authorize tip")) + "pickup tip")) { GNUNET_break (0); - return TALER_EC_TIP_AUTHORIZE_DB_HARD_ERROR; + return TALER_EC_TIP_PICKUP_DB_ERROR_HARD; } qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_tip_reserve_balance", + "lookup_reserve_by_tip_id", params, rs); if (0 >= qs) { - /* reserve unknown */ + /* tip ID unknown */ + memset (reserve_priv, + 0, + sizeof (*reserve_priv)); postgres_rollback (pg); + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + return TALER_EC_TIP_PICKUP_TIP_ID_UNKNOWN; if (GNUNET_DB_STATUS_SOFT_ERROR == qs) goto RETRY; - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) - return TALER_EC_TIP_AUTHORIZE_INSUFFICIENT_FUNDS; - return TALER_EC_TIP_AUTHORIZE_DB_HARD_ERROR; - } - if (0 == GNUNET_TIME_absolute_get_remaining (old_expiration).rel_value_us) - { - /* reserve expired, can't be used */ - postgres_rollback (pg); - return TALER_EC_TIP_AUTHORIZE_RESERVE_EXPIRED; - } - if (GNUNET_SYSERR == - TALER_amount_subtract (&new_balance, - &old_balance, - amount)) - { - /* insufficient funds left in reserve */ - postgres_rollback (pg); - return TALER_EC_TIP_AUTHORIZE_INSUFFICIENT_FUNDS; + return TALER_EC_TIP_PICKUP_DB_ERROR_HARD; } - /* Update reserve balance */ + + /* Check if pickup_id already exists */ { + struct TALER_Amount existing_amount; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (reserve_priv), - GNUNET_PQ_query_param_absolute_time (&old_expiration), - TALER_PQ_query_param_amount (&new_balance), + GNUNET_PQ_query_param_auto_from_type (pickup_id), + GNUNET_PQ_query_param_auto_from_type (tip_id), GNUNET_PQ_query_param_end }; + struct GNUNET_PQ_ResultSpec rs[] = { + TALER_PQ_RESULT_SPEC_AMOUNT ("amount", + &existing_amount), + GNUNET_PQ_result_spec_end + }; - qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "update_tip_reserve_balance", - params); + qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "lookup_amount_by_pickup", + params, + rs); if (0 > qs) { + /* DB error */ + memset (reserve_priv, + 0, + sizeof (*reserve_priv)); postgres_rollback (pg); if (GNUNET_DB_STATUS_SOFT_ERROR == qs) goto RETRY; - return TALER_EC_TIP_AUTHORIZE_DB_HARD_ERROR; + return TALER_EC_TIP_PICKUP_DB_ERROR_HARD; + } + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) + { + if (0 != + TALER_amount_cmp (&existing_amount, + amount)) + { + GNUNET_break_op (0); + postgres_rollback (pg); + return TALER_EC_TIP_PICKUP_AMOUNT_CHANGED; + } + postgres_commit (pg); + return TALER_EC_NONE; /* we are done! */ } } - /* Generate and store tip ID */ - *expiration = old_expiration; - GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_STRONG, - tip_id); + + /* Calculate new balance */ { - struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (reserve_priv), - GNUNET_PQ_query_param_auto_from_type (tip_id), - GNUNET_PQ_query_param_string (exchange_url), - GNUNET_PQ_query_param_string (justification), - TALER_PQ_query_param_json (extra), - GNUNET_PQ_query_param_absolute_time (&now), - TALER_PQ_query_param_amount (amount), /* overall amount */ - TALER_PQ_query_param_amount (amount), /* amount left */ - GNUNET_PQ_query_param_end - }; + struct TALER_Amount new_left; - (void) GNUNET_TIME_round_abs (&now); - qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_tip_justification", - params); - if (0 > qs) + if (GNUNET_SYSERR == + TALER_amount_subtract (&new_left, + &left_amount, + amount)) { + /* attempt to take more tips than the tipping amount */ + GNUNET_break_op (0); + memset (reserve_priv, + 0, + sizeof (*reserve_priv)); postgres_rollback (pg); - if (GNUNET_DB_STATUS_SOFT_ERROR == qs) - goto RETRY; - return TALER_EC_TIP_AUTHORIZE_DB_HARD_ERROR; + return TALER_EC_TIP_PICKUP_NO_FUNDS; + } + + /* Update DB: update balance */ + { + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (tip_id), + TALER_PQ_query_param_amount (&new_left), + GNUNET_PQ_query_param_end + }; + + qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, + "update_tip_balance", + params); + if (0 > qs) + { + postgres_rollback (pg); + memset (reserve_priv, + 0, + sizeof (*reserve_priv)); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + goto RETRY; + return TALER_EC_TIP_PICKUP_DB_ERROR_HARD; + } + } + + /* Update DB: remember pickup_id */ + { + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (tip_id), + GNUNET_PQ_query_param_auto_from_type (pickup_id), + TALER_PQ_query_param_amount (amount), + GNUNET_PQ_query_param_end + }; + + qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, + "insert_pickup_id", + params); + if (0 > qs) + { + postgres_rollback (pg); + memset (reserve_priv, + 0, + sizeof (*reserve_priv)); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + goto RETRY; + return TALER_EC_TIP_PICKUP_DB_ERROR_HARD; + } } } qs = postgres_commit (pg); if (0 <= qs) - return TALER_EC_NONE; /* success! */ + return TALER_EC_NONE; /* success */ if (GNUNET_DB_STATUS_SOFT_ERROR == qs) goto RETRY; - return TALER_EC_TIP_AUTHORIZE_DB_HARD_ERROR; + return TALER_EC_TIP_PICKUP_DB_ERROR_HARD; } /** - * Find out tip authorization details associated with @a tip_id + * Initialize Postgres database subsystem. * - * @param cls closure, typically a connection to the d - * @param tip_id the unique ID for the tip - * @param[out] exchange_url set to the URL of the exchange (unless NULL) - * @param[out] extra extra data to pass to the wallet (unless NULL) - * @param[out] amount set to the authorized amount (unless NULL) - * @param[out] amount_left set to the amount left (unless NULL) - * @param[out] timestamp set to the timestamp of the tip authorization (unless NULL) - * @return transaction status, usually - * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success - * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if @a credit_uuid already known + * @param cls a configuration instance + * @return NULL on error, otherwise a `struct TALER_MERCHANTDB_Plugin` */ -static enum GNUNET_DB_QueryStatus -postgres_lookup_tip_by_id (void *cls, - const struct GNUNET_HashCode *tip_id, - char **exchange_url, - json_t **extra, - struct TALER_Amount *amount, - struct TALER_Amount *amount_left, - struct GNUNET_TIME_Absolute *timestamp) +void * +libtaler_plugin_merchantdb_postgres_init (void *cls) { - struct PostgresClosure *pg = cls; - char *res_exchange_url; - json_t *res_extra; - struct TALER_Amount res_amount; - struct TALER_Amount res_amount_left; - struct GNUNET_TIME_Absolute res_timestamp; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (tip_id), - GNUNET_PQ_query_param_end - }; - struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_string ("exchange_url", - &res_exchange_url), - GNUNET_PQ_result_spec_absolute_time ("timestamp", - &res_timestamp), - TALER_PQ_result_spec_json ("extra", - &res_extra), - TALER_PQ_RESULT_SPEC_AMOUNT ("amount", - &res_amount), - TALER_PQ_RESULT_SPEC_AMOUNT ("left", - &res_amount_left), - GNUNET_PQ_result_spec_end + struct GNUNET_CONFIGURATION_Handle *cfg = cls; + struct PostgresClosure *pg; + struct TALER_MERCHANTDB_Plugin *plugin; + const char *ec; + struct GNUNET_PQ_ExecuteStatement es[] = { + /* Orders created by the frontend, not signed or given a nonce yet. + The contract terms will change (nonce will be added) when moved to the + contract terms table */ + GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS merchant_orders (" + "order_id VARCHAR NOT NULL" + ",merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)" + ",contract_terms BYTEA NOT NULL" + ",timestamp INT8 NOT NULL" + ",PRIMARY KEY (order_id, merchant_pub)" + ");"), + /* Offers we made to customers */ + GNUNET_PQ_make_execute ( + "CREATE TABLE IF NOT EXISTS merchant_contract_terms (" + "order_id VARCHAR NOT NULL" + ",merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)" + ",contract_terms BYTEA NOT NULL" + ",h_contract_terms BYTEA NOT NULL CHECK (LENGTH(h_contract_terms)=64)" + ",timestamp INT8 NOT NULL" + ",row_id BIGSERIAL UNIQUE" + ",paid boolean DEFAULT FALSE NOT NULL" + ",PRIMARY KEY (order_id, merchant_pub)" + ",UNIQUE (h_contract_terms, merchant_pub)" + ");"), + /* Table with the proofs for each coin we deposited at the exchange */ + 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)" + ",coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)" + ",exchange_url VARCHAR NOT NULL" + ",amount_with_fee_val INT8 NOT NULL" + ",amount_with_fee_frac INT4 NOT NULL" + ",deposit_fee_val INT8 NOT NULL" + ",deposit_fee_frac INT4 NOT NULL" + ",refund_fee_val INT8 NOT NULL" + ",refund_fee_frac INT4 NOT NULL" + ",wire_fee_val INT8 NOT NULL" + ",wire_fee_frac INT4 NOT NULL" + ",signkey_pub BYTEA NOT NULL CHECK (LENGTH(signkey_pub)=32)" + ",exchange_proof BYTEA NOT NULL" + ",PRIMARY KEY (h_contract_terms, coin_pub)" + ",FOREIGN KEY (h_contract_terms, merchant_pub) REFERENCES merchant_contract_terms (h_contract_terms, merchant_pub)" + ");"), + GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS merchant_proofs (" + " exchange_url 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_url)" + ");"), + /* 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_make_execute ("CREATE TABLE IF NOT EXISTS exchange_wire_fees (" + " exchange_pub BYTEA NOT NULL CHECK (length(exchange_pub)=32)" + ",h_wire_method BYTEA NOT NULL CHECK (length(h_wire_method)=64)" + ",wire_fee_val INT8 NOT NULL" + ",wire_fee_frac INT4 NOT NULL" + ",closing_fee_val INT8 NOT NULL" + ",closing_fee_frac INT4 NOT NULL" + ",start_date INT8 NOT NULL" + ",end_date INT8 NOT NULL" + ",exchange_sig BYTEA NOT NULL CHECK (length(exchange_sig)=64)" + ",PRIMARY KEY (exchange_pub,h_wire_method,start_date,end_date)" + ");"), + GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS merchant_refunds (" + " rtransaction_id BIGSERIAL UNIQUE" + ",merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)" + ",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 INT4 NOT NULL" + ",refund_fee_val INT8 NOT NULL" + ",refund_fee_frac INT4 NOT NULL" + ");"), + /* balances of the reserves available for tips */ + GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS merchant_tip_reserves (" + " reserve_priv BYTEA NOT NULL CHECK (LENGTH(reserve_priv)=32)" + ",expiration INT8 NOT NULL" + ",balance_val INT8 NOT NULL" + ",balance_frac INT4 NOT NULL" + ",PRIMARY KEY (reserve_priv)" + ");"), + /* table where we remember when tipping reserves where established / enabled */ + GNUNET_PQ_make_execute ( + "CREATE TABLE IF NOT EXISTS merchant_tip_reserve_credits (" + " reserve_priv BYTEA NOT NULL CHECK (LENGTH(reserve_priv)=32)" + ",credit_uuid BYTEA UNIQUE NOT NULL CHECK (LENGTH(credit_uuid)=64)" + ",timestamp INT8 NOT NULL" + ",amount_val INT8 NOT NULL" + ",amount_frac INT4 NOT NULL" + ",PRIMARY KEY (credit_uuid)" + ");"), + /* tips that have been authorized */ + GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS merchant_tips (" + " reserve_priv BYTEA NOT NULL CHECK (LENGTH(reserve_priv)=32)" + ",tip_id BYTEA NOT NULL CHECK (LENGTH(tip_id)=64)" + ",exchange_url VARCHAR NOT NULL" + ",justification VARCHAR NOT NULL" + ",extra BYTEA NOT NULL" + ",timestamp INT8 NOT NULL" + ",amount_val INT8 NOT NULL" /* overall tip amount */ + ",amount_frac INT4 NOT NULL" + ",left_val INT8 NOT NULL" /* tip amount not yet picked up */ + ",left_frac INT4 NOT NULL" + ",PRIMARY KEY (tip_id)" + ");"), + /* tips that have been picked up */ + GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS merchant_tip_pickups (" + " tip_id BYTEA NOT NULL REFERENCES merchant_tips (tip_id) ON DELETE CASCADE" + ",pickup_id BYTEA NOT NULL CHECK (LENGTH(pickup_id)=64)" + ",amount_val INT8 NOT NULL" + ",amount_frac INT4 NOT NULL" + ",PRIMARY KEY (pickup_id)" + ");"), + /* sessions and their order_id/fulfillment_url mapping */ + GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS merchant_session_info (" + " session_id VARCHAR NOT NULL" + ",fulfillment_url VARCHAR NOT NULL" + ",order_id VARCHAR NOT NULL" + ",merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)" + ",timestamp INT8 NOT NULL" + ",PRIMARY KEY (session_id, fulfillment_url, merchant_pub)" + ",UNIQUE (session_id, fulfillment_url, order_id, merchant_pub)" + ");"), + GNUNET_PQ_EXECUTE_STATEMENT_END }; - enum GNUNET_DB_QueryStatus qs; - - qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "find_tip_by_id", - params, - rs); - if (0 >= qs) - { - if (NULL != exchange_url) - *exchange_url = NULL; - return qs; - } - if (NULL != exchange_url) - *exchange_url = strdup (res_exchange_url); - if (NULL != amount) - *amount = res_amount; - if (NULL != amount_left) - *amount_left = res_amount_left; - if (NULL != timestamp) - *timestamp = res_timestamp; - if (NULL != extra) - { - json_incref (res_extra); - *extra = res_extra; - } - GNUNET_PQ_cleanup_result (rs); - return qs; -} - + struct GNUNET_PQ_PreparedStatement ps[] = { + GNUNET_PQ_make_prepare ("insert_deposit", + "INSERT INTO merchant_deposits" + "(h_contract_terms" + ",merchant_pub" + ",coin_pub" + ",exchange_url" + ",amount_with_fee_val" + ",amount_with_fee_frac" + ",deposit_fee_val" + ",deposit_fee_frac" + ",refund_fee_val" + ",refund_fee_frac" + ",wire_fee_val" + ",wire_fee_frac" + ",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_refund", + "INSERT INTO merchant_refunds" + "(merchant_pub" + ",h_contract_terms" + ",coin_pub" + ",reason" + ",refund_amount_val" + ",refund_amount_frac" + ",refund_fee_val" + ",refund_fee_frac" + ") VALUES" + "($1, $2, $3, $4, $5, $6, $7, $8)", + 8), + GNUNET_PQ_make_prepare ("insert_proof", + "INSERT INTO merchant_proofs" + "(exchange_url" + ",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)", + 5), + GNUNET_PQ_make_prepare ("insert_order", + "INSERT INTO merchant_orders" + "(order_id" + ",merchant_pub" + ",timestamp" + ",contract_terms)" + " VALUES " + "($1, $2, $3, $4)", + 4), + GNUNET_PQ_make_prepare ("insert_session_info", + "INSERT INTO merchant_session_info" + "(session_id" + ",fulfillment_url" + ",order_id" + ",merchant_pub" + ",timestamp)" + " VALUES " + "($1, $2, $3, $4, $5)", + 5), + GNUNET_PQ_make_prepare ("mark_proposal_paid", + "UPDATE merchant_contract_terms SET" + " paid=TRUE" + " WHERE h_contract_terms=$1" + " AND merchant_pub=$2", + 2), + GNUNET_PQ_make_prepare ("insert_wire_fee", + "INSERT INTO exchange_wire_fees" + "(exchange_pub" + ",h_wire_method" + ",wire_fee_val" + ",wire_fee_frac" + ",closing_fee_val" + ",closing_fee_frac" + ",start_date" + ",end_date" + ",exchange_sig)" + " VALUES " + "($1, $2, $3, $4, $5, $6, $7, $8, $9)", + 9), + GNUNET_PQ_make_prepare ("lookup_wire_fee", + "SELECT" + " wire_fee_val" + ",wire_fee_frac" + ",closing_fee_val" + ",closing_fee_frac" + ",start_date" + ",end_date" + ",exchange_sig" + " FROM exchange_wire_fees" + " WHERE exchange_pub=$1" + " AND h_wire_method=$2" + " AND start_date <= $3" + " AND end_date > $3", + 1), + 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_paid_contract_terms_from_hash", + "SELECT" + " contract_terms" + " FROM merchant_contract_terms" + " WHERE h_contract_terms=$1" + " AND merchant_pub=$2" + " AND paid=TRUE", + 2), + GNUNET_PQ_make_prepare ("end_transaction", + "COMMIT", + 0), -/** - * Pickup a tip over @a amount using pickup id @a pickup_id. - * - * @param cls closure, typically a connection to the db - * @param amount how high is the amount picked up (with fees) - * @param tip_id the unique ID from the tip authorization - * @param pickup_id the unique ID identifying the pick up operation - * (to allow replays, hash over the coin envelope and denomination key) - * @param[out] reserve_priv which reserve key to use to sign - * @return taler error code - * #TALER_EC_TIP_PICKUP_ID_UNKNOWN if @a tip_id is unknown - * #TALER_EC_TIP_PICKUP_NO_FUNDS if @a tip_id has insufficient funds left - * #TALER_EC_TIP_PICKUP_DB_ERROR_HARD on hard database errors - * #TALER_EC_TIP_PICKUP_AMOUNT_CHANGED if @a amount is different for known @a pickup_id - * #TALER_EC_TIP_PICKUP_DB_ERROR_SOFT on soft database errors (client should retry) - * #TALER_EC_NONE upon success (@a reserve_priv was set) - */ -static enum TALER_ErrorCode -postgres_pickup_tip_TR (void *cls, - const struct TALER_Amount *amount, - const struct GNUNET_HashCode *tip_id, - const struct GNUNET_HashCode *pickup_id, - struct TALER_ReservePrivateKeyP *reserve_priv) -{ - struct PostgresClosure *pg = cls; - struct TALER_Amount left_amount; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (tip_id), - GNUNET_PQ_query_param_end - }; - struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_auto_from_type ("reserve_priv", - reserve_priv), - TALER_PQ_RESULT_SPEC_AMOUNT ("left", - &left_amount), - GNUNET_PQ_result_spec_end + GNUNET_PQ_make_prepare ("find_refunds", + "SELECT" + " refund_amount_val" + ",refund_amount_frac" + " FROM merchant_refunds" + " WHERE coin_pub=$1", + 1), + GNUNET_PQ_make_prepare ("find_contract_terms_history", + "SELECT" + " contract_terms" + " FROM merchant_contract_terms" + " WHERE" + " order_id=$1" + " AND merchant_pub=$2" + " AND paid=TRUE", + 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_order", + "SELECT" + " contract_terms" + " FROM merchant_orders" + " WHERE" + " order_id=$1" + " AND merchant_pub=$2", + 2), + GNUNET_PQ_make_prepare ("find_session_info", + "SELECT" + " order_id" + " FROM merchant_session_info" + " WHERE" + " fulfillment_url=$1" + " AND session_id=$2" + " AND merchant_pub=$3", + 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" + " AND paid=TRUE" + " ORDER BY row_id DESC, timestamp DESC" + " LIMIT $3", + 3), + GNUNET_PQ_make_prepare ("find_refunds_from_contract_terms_hash", + "SELECT" + " coin_pub" + ",rtransaction_id" + ",refund_amount_val" + ",refund_amount_frac" + ",refund_fee_val" + ",refund_fee_frac" + ",reason" + " FROM merchant_refunds" + " WHERE merchant_pub=$1" + " AND h_contract_terms=$2", + 2), + GNUNET_PQ_make_prepare ("find_contract_terms_by_date_and_range_asc", + "SELECT" + " contract_terms" + ",order_id" + ",row_id" + " FROM merchant_contract_terms" + " WHERE" + " timestamp>$1" + " AND merchant_pub=$2" + " AND row_id>$3" + " AND paid=TRUE" + " ORDER BY row_id ASC, timestamp ASC" + " LIMIT $4", + 4), + 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" + " AND paid=TRUE" + " ORDER BY row_id DESC, timestamp DESC" + " LIMIT $4", + 4), + GNUNET_PQ_make_prepare ("find_contract_terms_by_date_and_range_past_asc", + "SELECT" + " contract_terms" + ",order_id" + ",row_id" + " FROM merchant_contract_terms" + " WHERE" + " timestamp<$1" + " AND merchant_pub=$2" + " AND row_id<$3" + " AND paid=TRUE" + " ORDER BY row_id ASC, timestamp ASC" + " LIMIT $4", + 4), + GNUNET_PQ_make_prepare ("find_contract_terms_by_date_and_range_past", + "SELECT" + " contract_terms" + ",order_id" + ",row_id" + " FROM merchant_contract_terms" + " WHERE" + " timestamp<$1" + " AND merchant_pub=$2" + " AND row_id<$3" + " AND paid=TRUE" + " ORDER BY row_id DESC, timestamp DESC" + " LIMIT $4", + 4), + GNUNET_PQ_make_prepare ("find_deposits", + "SELECT" + " coin_pub" + ",exchange_url" + ",amount_with_fee_val" + ",amount_with_fee_frac" + ",deposit_fee_val" + ",deposit_fee_frac" + ",refund_fee_val" + ",refund_fee_frac" + ",wire_fee_val" + ",wire_fee_frac" + ",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" + ",deposit_fee_val" + ",deposit_fee_frac" + ",refund_fee_val" + ",refund_fee_frac" + ",wire_fee_val" + ",wire_fee_frac" + ",exchange_url" + ",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.deposit_fee_val" + ",merchant_deposits.deposit_fee_frac" + ",merchant_deposits.refund_fee_val" + ",merchant_deposits.refund_fee_frac" + ",merchant_deposits.wire_fee_val" + ",merchant_deposits.wire_fee_frac" + ",merchant_deposits.exchange_url" + ",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_url=$2", + 2), + GNUNET_PQ_make_prepare ("lookup_tip_reserve_balance", + "SELECT" + " expiration" + ",balance_val" + ",balance_frac" + " FROM merchant_tip_reserves" + " WHERE reserve_priv=$1", + 1), + GNUNET_PQ_make_prepare ("find_tip_authorizations", + "SELECT" + " amount_val" + ",amount_frac" + ",justification" + ",extra" + ",tip_id" + " FROM merchant_tips" + " WHERE reserve_priv=$1", + 1), + GNUNET_PQ_make_prepare ("update_tip_reserve_balance", + "UPDATE merchant_tip_reserves SET" + " expiration=$2" + ",balance_val=$3" + ",balance_frac=$4" + " WHERE reserve_priv=$1", + 4), + GNUNET_PQ_make_prepare ("insert_tip_reserve_balance", + "INSERT INTO merchant_tip_reserves" + "(reserve_priv" + ",expiration" + ",balance_val" + ",balance_frac" + ") VALUES " + "($1, $2, $3, $4)", + 4), + GNUNET_PQ_make_prepare ("insert_tip_justification", + "INSERT INTO merchant_tips" + "(reserve_priv" + ",tip_id" + ",exchange_url" + ",justification" + ",extra" + ",timestamp" + ",amount_val" + ",amount_frac" + ",left_val" + ",left_frac" + ") VALUES " + "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", + 10), + GNUNET_PQ_make_prepare ("lookup_reserve_by_tip_id", + "SELECT" + " reserve_priv" + ",left_val" + ",left_frac" + " FROM merchant_tips" + " WHERE tip_id=$1", + 1), + GNUNET_PQ_make_prepare ("lookup_amount_by_pickup", + "SELECT" + " amount_val" + ",amount_frac" + " FROM merchant_tip_pickups" + " WHERE pickup_id=$1" + " AND tip_id=$2", + 2), + GNUNET_PQ_make_prepare ("find_tip_by_id", + "SELECT" + " exchange_url" + ",extra" + ",timestamp" + ",amount_val" + ",amount_frac" + ",left_val" + ",left_frac" + " FROM merchant_tips" + " WHERE tip_id=$1", + 1), + GNUNET_PQ_make_prepare ("update_tip_balance", + "UPDATE merchant_tips SET" + " left_val=$2" + ",left_frac=$3" + " WHERE tip_id=$1", + 3), + GNUNET_PQ_make_prepare ("insert_pickup_id", + "INSERT INTO merchant_tip_pickups" + "(tip_id" + ",pickup_id" + ",amount_val" + ",amount_frac" + ") VALUES " + "($1, $2, $3, $4)", + 4), + GNUNET_PQ_make_prepare ("insert_tip_credit_uuid", + "INSERT INTO merchant_tip_reserve_credits" + "(reserve_priv" + ",credit_uuid" + ",timestamp" + ",amount_val" + ",amount_frac" + ") VALUES " + "($1, $2, $3, $4, $5)", + 5), + GNUNET_PQ_make_prepare ("lookup_tip_credit_uuid", + "SELECT 1 " + "FROM merchant_tip_reserve_credits " + "WHERE credit_uuid=$1 AND reserve_priv=$2", + 2), + GNUNET_PQ_PREPARED_STATEMENT_END }; - enum GNUNET_DB_QueryStatus qs; - unsigned int retries; - - retries = 0; - check_connection (pg); -RETRY: - if (MAX_RETRIES < ++retries) - return TALER_EC_TIP_PICKUP_DB_ERROR_SOFT; - if (GNUNET_OK != - postgres_start (pg, - "pickup tip")) - { - GNUNET_break (0); - return TALER_EC_TIP_PICKUP_DB_ERROR_HARD; - } - qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_reserve_by_tip_id", - params, - rs); - if (0 >= qs) - { - /* tip ID unknown */ - memset (reserve_priv, - 0, - sizeof (*reserve_priv)); - postgres_rollback (pg); - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) - return TALER_EC_TIP_PICKUP_TIP_ID_UNKNOWN; - if (GNUNET_DB_STATUS_SOFT_ERROR == qs) - goto RETRY; - return TALER_EC_TIP_PICKUP_DB_ERROR_HARD; - } - - /* Check if pickup_id already exists */ - { - struct TALER_Amount existing_amount; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (pickup_id), - GNUNET_PQ_query_param_auto_from_type (tip_id), - GNUNET_PQ_query_param_end - }; - struct GNUNET_PQ_ResultSpec rs[] = { - TALER_PQ_RESULT_SPEC_AMOUNT ("amount", - &existing_amount), - GNUNET_PQ_result_spec_end - }; - - qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_amount_by_pickup", - params, - rs); - if (0 > qs) - { - /* DB error */ - memset (reserve_priv, - 0, - sizeof (*reserve_priv)); - postgres_rollback (pg); - if (GNUNET_DB_STATUS_SOFT_ERROR == qs) - goto RETRY; - return TALER_EC_TIP_PICKUP_DB_ERROR_HARD; - } - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) - { - if (0 != - TALER_amount_cmp (&existing_amount, - amount)) - { - GNUNET_break_op (0); - postgres_rollback (pg); - return TALER_EC_TIP_PICKUP_AMOUNT_CHANGED; - } - postgres_commit (pg); - return TALER_EC_NONE; /* we are done! */ - } - } - - /* Calculate new balance */ - { - struct TALER_Amount new_left; - - if (GNUNET_SYSERR == - TALER_amount_subtract (&new_left, - &left_amount, - amount)) - { - /* attempt to take more tips than the tipping amount */ - GNUNET_break_op (0); - memset (reserve_priv, - 0, - sizeof (*reserve_priv)); - postgres_rollback (pg); - return TALER_EC_TIP_PICKUP_NO_FUNDS; - } - - /* Update DB: update balance */ - { - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (tip_id), - TALER_PQ_query_param_amount (&new_left), - GNUNET_PQ_query_param_end - }; - - qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "update_tip_balance", - params); - if (0 > qs) - { - postgres_rollback (pg); - memset (reserve_priv, - 0, - sizeof (*reserve_priv)); - if (GNUNET_DB_STATUS_SOFT_ERROR == qs) - goto RETRY; - return TALER_EC_TIP_PICKUP_DB_ERROR_HARD; - } - } - - /* Update DB: remember pickup_id */ - { - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (tip_id), - GNUNET_PQ_query_param_auto_from_type (pickup_id), - TALER_PQ_query_param_amount (amount), - GNUNET_PQ_query_param_end - }; - - qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_pickup_id", - params); - if (0 > qs) - { - postgres_rollback (pg); - memset (reserve_priv, - 0, - sizeof (*reserve_priv)); - if (GNUNET_DB_STATUS_SOFT_ERROR == qs) - goto RETRY; - return TALER_EC_TIP_PICKUP_DB_ERROR_HARD; - } - } - } - qs = postgres_commit (pg); - if (0 <= qs) - return TALER_EC_NONE; /* success */ - if (GNUNET_DB_STATUS_SOFT_ERROR == qs) - goto RETRY; - return TALER_EC_TIP_PICKUP_DB_ERROR_HARD; -} - - -/** - * 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"); @@ -3694,7 +3658,9 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) } pg->cfg = cfg; pg->conn = GNUNET_PQ_connect_with_cfg (cfg, - "merchantdb-postgres"); + "merchantdb-postgres", + es, + ps); if (NULL == pg->conn) { GNUNET_free (pg); @@ -3709,13 +3675,13 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "taler", "CURRENCY"); + GNUNET_PQ_disconnect (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_deposit = &postgres_store_deposit; plugin->store_coin_to_transfer = &postgres_store_coin_to_transfer; plugin->store_transfer_to_proof = &postgres_store_transfer_to_proof; @@ -3772,7 +3738,7 @@ libtaler_plugin_merchantdb_postgres_done (void *cls) struct TALER_MERCHANTDB_Plugin *plugin = cls; struct PostgresClosure *pg = plugin->cls; - PQfinish (pg->conn); + GNUNET_PQ_disconnect (pg->conn); GNUNET_free (pg->currency); GNUNET_free (pg); GNUNET_free (plugin); diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c @@ -749,7 +749,6 @@ run (void *cls) result = 77; return; } - if (GNUNET_OK != plugin->drop_tables (plugin->cls)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, @@ -757,7 +756,8 @@ run (void *cls) result = 77; return; } - if (GNUNET_OK != plugin->initialize (plugin->cls)) + TALER_MERCHANTDB_plugin_unload (plugin); + if (NULL == (plugin = TALER_MERCHANTDB_plugin_load (cfg))) { result = 77; return; diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h @@ -192,15 +192,6 @@ struct TALER_MERCHANTDB_Plugin (*drop_tables) (void *cls); /** - * Initialize merchant tables - * - * @param cls closure - * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure - */ - int - (*initialize) (void *cls); - - /** * Insert order into db. * * @param cls closure diff --git a/src/merchant-tools/taler-merchant-dbinit.c b/src/merchant-tools/taler-merchant-dbinit.c @@ -60,17 +60,11 @@ run (void *cls, return; } if (reset_db) - (void) plugin->drop_tables (plugin->cls); - if (GNUNET_OK != - plugin->initialize (plugin->cls)) { - fprintf (stderr, - "Failed to initialize database.\n"); + (void) plugin->drop_tables (plugin->cls); TALER_MERCHANTDB_plugin_unload (plugin); - global_ret = 1; - return; + plugin = TALER_MERCHANTDB_plugin_load (cfg); } - TALER_MERCHANTDB_plugin_unload (plugin); }